Разрежьте UIImage в круг

Я хочу сократить UIImage в круг, чтобы я мог использовать его в качестве аннотации. Каждый ответ на этом сайте описывает создание UIImageView, затем изменив и отобразив его, но вы не можете установить изображение аннотации в UIImageViewтолько UIImage, Как я должен идти об этом?

13 ответов

Решение

Обязательно импортируйте QuarzCore, если это необходимо.

 func maskRoundedImage(image: UIImage, radius: CGFloat) -> UIImage {
        let imageView: UIImageView = UIImageView(image: image)
        let layer = imageView.layer
        layer.masksToBounds = true
        layer.cornerRadius = radius
        UIGraphicsBeginImageContext(imageView.bounds.size)
        layer.render(in: UIGraphicsGetCurrentContext()!)
        let roundedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return roundedImage!
    }

Xcode 8.2 • Swift 3.0.2

extension UIImage {
    var isPortrait:  Bool    { return size.height > size.width }
    var isLandscape: Bool    { return size.width > size.height }
    var breadth:     CGFloat { return min(size.width, size.height) }
    var breadthSize: CGSize  { return CGSize(width: breadth, height: breadth) }
    var breadthRect: CGRect  { return CGRect(origin: .zero, size: breadthSize) }
    var circleMasked: UIImage? {
        UIGraphicsBeginImageContextWithOptions(breadthSize, false, scale)
        defer { UIGraphicsEndImageContext() }
        guard let cgImage = cgImage?.cropping(to: CGRect(origin: CGPoint(x: isLandscape ? floor((size.width - size.height) / 2) : 0, y: isPortrait  ? floor((size.height - size.width) / 2) : 0), size: breadthSize)) else { return nil }
        UIBezierPath(ovalIn: breadthRect).addClip()
        UIImage(cgImage: cgImage, scale: 1, orientation: imageOrientation).draw(in: breadthRect) 
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

Тестирование детской площадки

let profilePicture = UIImage(data: try! Data(contentsOf: URL(string:"https://stackru.com/images/ef9dfaef8c3bec7e67a69584eb37fe5a3e115132.jpg")!))!
profilePicture.circleMasked

Расширение UIImage:

extension UIImage {

    func circularImage(size size: CGSize?) -> UIImage {
        let newSize = size ?? self.size

        let minEdge = min(newSize.height, newSize.width)
        let size = CGSize(width: minEdge, height: minEdge)

        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        let context = UIGraphicsGetCurrentContext()

        self.drawInRect(CGRect(origin: CGPoint.zero, size: size), blendMode: .Copy, alpha: 1.0)

        CGContextSetBlendMode(context, .Copy)
        CGContextSetFillColorWithColor(context, UIColor.clearColor().CGColor)

        let rectPath = UIBezierPath(rect: CGRect(origin: CGPoint.zero, size: size))
        let circlePath = UIBezierPath(ovalInRect: CGRect(origin: CGPoint.zero, size: size))
        rectPath.appendPath(circlePath)
        rectPath.usesEvenOddFillRule = true
        rectPath.fill()

        let result = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return result
    }

}

Использование:

UIImageView:

@IBDesignable class CircularImageView: UIImageView {

    override var image: UIImage? {
        didSet {
            super.image = image?.circularImage(size: nil)
        }
    }

}

UIButton:

@IBDesignable class CircularImageButton: UIButton {

    override func setImage(image: UIImage?, forState state: UIControlState) {
        let circularImage = image?.circularImage(size: nil)
        super.setImage(circularImage, forState: state)
    }

}

На основании ответа Никоса:

public extension UIImage {

func roundedImage() -> UIImage {
    let imageView: UIImageView = UIImageView(image: self)
    let layer = imageView.layer
    layer.masksToBounds = true
    layer.cornerRadius = imageView.frame.width / 2
    UIGraphicsBeginImageContext(imageView.bounds.size)
    layer.render(in: UIGraphicsGetCurrentContext()!)
    let roundedImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return roundedImage!
    }

}

//Usage

let roundedImage = image.roundedImage()

Вы можете использовать этот код, чтобы обвести изображение

extension UIImage {
func circleImage(_ cornerRadius: CGFloat, size: CGSize) -> UIImage? {
    let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    if let context = UIGraphicsGetCurrentContext() {
        var path: UIBezierPath
        if size.height == size.width {
            if cornerRadius == size.width/2 {
                path = UIBezierPath(arcCenter: CGPoint(x: size.width/2, y: size.height/2), radius: cornerRadius, startAngle: 0, endAngle: 2.0*CGFloat(Double.pi), clockwise: true)
            }else {
                path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
            }
        }else {
            path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
        }
        context.addPath(path.cgPath)
        context.clip()
        self.draw(in: rect)
        // 从上下文上获取剪裁后的照片
        guard let uncompressedImage = UIGraphicsGetImageFromCurrentImageContext() else {
            UIGraphicsEndImageContext()
            return nil
        }
        // 关闭上下文
        UIGraphicsEndImageContext()
        return uncompressedImage
    }else {
        return nil
    }
}}

Все эти ответы были действительно сложными для прямого решения. Я просто скопировал свой код Objective-C и настроил для Swift.

self.myImageView?.layer.cornerRadius = (self.myImageView?.frame.size.width)! / 2;
self.myImageView?.clipsToBounds = true

Swift 5.3, Xcode 12.2, обрабатывает все изображения

На основании ответа Лео Дабус

Спасибо, отлично работает! НО только для изображений с imageOrientation .up или .down. Для изображений с ориентацией вправо или влево возникают искажения. А с камеры iPhone/iPad для исходных фотографий изначально получаем правильную ориентацию.

В приведенном ниже коде учитывается свойство imageOrientation:

extension UIImage {

    func cropToCircle() -> UIImage? {
    
        let isLandscape = size.width > size.height
        let isUpOrDownImageOrientation = [0,1,4,5].contains(imageOrientation.rawValue)
    
        let breadth: CGFloat = min(size.width, size.height)
        let breadthSize = CGSize(width: breadth, height: breadth)
        let breadthRect = CGRect(origin: .zero, size: breadthSize)
    
        let xOriginPoint = CGFloat(isLandscape ?
                                (isUpOrDownImageOrientation ? ((size.width-size.height)/2).rounded(.down) : 0) :
                                (isUpOrDownImageOrientation ? 0 : ((size.height-size.width)/2).rounded(.down)))
        let yOriginPoint = CGFloat(isLandscape ?
                                (isUpOrDownImageOrientation ? 0 : ((size.width-size.height)/2).rounded(.down)) :
                                (isUpOrDownImageOrientation ? ((size.height-size.width)/2).rounded(.down) : 0))
    
        guard let cgImage = cgImage?.cropping(to: CGRect(origin: CGPoint(x: xOriginPoint, y: yOriginPoint),
                                                     size: breadthSize)) else { return nil }
        let format = imageRendererFormat
        format.opaque = false
    
        return UIGraphicsImageRenderer(size: breadthSize, format: format).image {_ in
            UIBezierPath(ovalIn: breadthRect).addClip()
            UIImage(cgImage: cgImage, scale: format.scale, orientation: imageOrientation).draw(in: CGRect(origin: .zero, size: breadthSize))
        }
    }
}

Мне удалось ответить на мой собственный вопрос, найдя применение BezierPath!

if let xyz = UIImage(contentsOfFile: readPath) {
     var Rect: CGRect = CGRectMake(0, 0, xyz.size.width, xyz.size.height)
     var x = UIBezierPath(roundedRect: Rect, cornerRadius: 200).addClip()

     UIGraphicsBeginImageContextWithOptions(xyz.size, false, xyz.scale)
     xyz.drawInRect(Rect)
     var ImageNew = UIGraphicsGetImageFromCurrentImageContext()
     UIGraphicsEndImageContext()
     annotation.image = ImageNew
}

Xcode 8.1, Swift 3.0.1

Мой код будет выглядеть так:

let image = yourImage.resize(CGSize(width: 20, height: 20))?.circled(forRadius: 20)

Добавьте расширение UIImage, затем:

func resize(_ size: CGSize) -> UIImage? {
    let rect = CGRect(origin: .zero, size: size)
    return redraw(in: rect)
}

func redraw(in rect: CGRect) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale)

    guard let context = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return nil }

    let rect = CGRect(origin: .zero, size: size)
    let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: rect.size.height)

    context.concatenate(flipVertical)
    context.draw(cgImage, in: rect)

    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image
}

func circled(forRadius radius: CGFloat) -> UIImage? {
    let rediusSize = CGSize(width: radius, height: radius)
    let rect = CGRect(origin: .zero, size: size)

    UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)

    guard let context = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return nil }

    let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: rect.size.height)
    context.concatenate(flipVertical)

    let bezierPath = UIBezierPath(roundedRect: rect, byRoundingCorners: [.allCorners], cornerRadii: rediusSize)
    context.addPath(bezierPath.cgPath)
    context.clip()

    context.drawPath(using: .fillStroke)
    context.draw(cgImage, in: rect)

    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return image
}

swift 3 соответствует шаблону MVC создать внешний файл

@IBDesignable
class RoundImage: UIImageView{


@IBInspectable var cornerRadius: CGFloat = 0 {
    didSet{
        self.layer.cornerRadius = cornerRadius
    }
}

// set border width
@IBInspectable var borderWidth: CGFloat = 0 {
    didSet{
        self.layer.borderWidth = borderWidth
    }
}

// set border color

@IBInspectable var borderColor: UIColor = UIColor.clear {
    didSet{
        self.layer.borderColor = borderColor.cgColor
    }
}

override func awakeFromNib() {
    self.clipsToBounds = true
}


}// class

колл класс в ИБ на раскадровке

колл класс в ИБ на раскадровке

установите угол, как вам угодно (1/2 ширины, если хотите круг)

установить атрибут

Готово!

Кажется, цель состоит в том, чтобы перейти от любого изображения к круговым изображениям профиля на экране. Я начал с отличного ответа @Leo Dabus, но он не обрабатывал ориентацию, масштабирование, а также обработку CGImage и обрезку, прежде чем обрезка показалась ненужной. Поэтому я переписал с помощью UIImage.draw(). Он обрабатывает масштабирование либо в процентах, либо до фиксированного размера.

Обратите внимание, что для результирующего изображения устанавливается масштаб 3,0, чтобы сохранить максимально возможное качество изображения при заданном размере на экране, независимо от масштаба исходного изображения.

      import UIKit

@objc
extension UIImage {
    
    var isPortrait:  Bool {return size.height > size.width }
    var isLandscape: Bool {return size.width  > size.height}
    
    func circleMask (breadth: CGFloat) -> UIImage {
        guard breadth > 0, size.width > 0, size.height > 0 else {return UIImage()}
        let scaleUp       = breadth / min(size.width, size.height)
        let breadthSize   = CGSize(width: breadth, height: breadth) 
        let breadthRect   = CGRect(origin: .zero, size: breadthSize)
        let excessPoints  = (scaleUp * abs(size.width-size.height)/2).rounded(.down)
        
        let scaledSize = CGSize(width: size.width*scaleUp, height: size.height * scaleUp)
        let cropRect = CGRect(
            origin: CGPoint(
                x: isLandscape ? -excessPoints : 0,
                y: isPortrait  ? 0             : -excessPoints),
            size: scaledSize)
        
        let format = imageRendererFormat
        format.opaque = false
        format.scale = 3.0
        return UIGraphicsImageRenderer(size: breadthSize, format: format).image { _ in
            UIBezierPath(ovalIn: breadthRect).addClip()
            self.draw(in: cropRect)
        }
    }
    
    func circleMask(scaleUp: CGFloat = 1.0 ) -> UIImage {
        return circleMask(breadth: scaleUp * min(size.width,size.height))
    }
}

Пример звонка:

      
func profilePict(at loc: String) -> UIImage? {
    guard let url = URL(string:loc) else { print ("Bad URL: \(loc)"); return nil }
    do {
        let data = try Data(contentsOf: url) 
        let profilePicture = UIImage(data: data)
        if let profilePicture {
            return profilePicture.circleMask(breadth:80)
//          return profilePicture.circleMask(scaleUp:0.5)
        } else {
            print("Invalid data for image at \(loc)")
            return nil
        }
    }
    catch { print("No data at \(url) error: \(error)") }
    return nil
}   
    
    
profilePict(at: "https://github.com/recurser/exif-orientation-examples/blob/219294e144531b0c01247913cb58b6f5531b5081/Landscape_7.jpg?raw=true")

Я использую класс RoundedImageView, проблема заключается в том, что при просмотре изображения из галереи изображение не отображается по кругу или кругу.. Я просто меняю свойства UIImageView/RoundedImageView -> view -> Content Mode -> Aspect Fillсм. Снимок экрана

Принятый ответ @Leo Dabus хорош, но вот лучший подход ✅

      import UIKit

public extension UIImage {
    /// Returns a circle image with diameter, color and optional padding
    class func circle(_ color: UIColor, diameter: CGFloat, padding: CGFloat = .zero) -> UIImage {
        let rectangle = CGSize(width: diameter + padding * 2, height: diameter + padding * 2)
        return UIGraphicsImageRenderer(size: rectangle).image { context in
            let rect = CGRect(x: padding, y: padding, width: diameter + padding, height: diameter + padding)
            color.setFill()
            UIBezierPath(ovalIn: rect).fill()
        }
    }
}

Как использовать

      let image = UIImage.circle(.black, diameter: 8.0)
  • Меньше строк кода
  • Возможность добавления отступов
  • Необязательный результат
Другие вопросы по тегам