裁剪图像并居中

55
在Swift中,如何裁剪图像并将其放置在中心位置?我已经裁剪了图像,但想将其放置在中心位置。
ImgView.image = OrigImage
var masklayer = CAShapeLayer()
masklayer.frame = ImgView.frame
masklayer.path = path.CGPath
masklayer.fillColor = UIColor.whiteColor().CGColor
masklayer.backgroundColor = UIColor.clearColor().CGColor
ImgView.layer.mask = masklayer

UIGraphicsBeginImageContext(ImgView.bounds.size);
   ImgView.layer.renderInContext(UIGraphicsGetCurrentContext())
   var image = UIGraphicsGetImageFromCurrentImageContext()
   ImgView.image = image
UIGraphicsEndImageContext();

1
请展示一下你已经尝试过的内容。 - Hayley Guillou
@jhayvi 请不要在回答中包含问题的解决方案(请另外发布一个单独的答案)。 - undefined
15个回答

83

要让裁剪图像居中,您可以将高度和宽度的差值除以2。然后,在检查图像的方向(哪一部分更长)之后,可以为新的宽度和高度分配边界。

func cropToBounds(image: UIImage, width: Double, height: Double) -> UIImage {

    let contextImage: UIImage = UIImage(CGImage: image.CGImage)!

    let contextSize: CGSize = contextImage.size

    var posX: CGFloat = 0.0
    var posY: CGFloat = 0.0
    var cgwidth: CGFloat = CGFloat(width)
    var cgheight: CGFloat = CGFloat(height)

    // See what size is longer and create the center off of that
    if contextSize.width > contextSize.height {
        posX = ((contextSize.width - contextSize.height) / 2)
        posY = 0
        cgwidth = contextSize.height
        cgheight = contextSize.height
    } else {
        posX = 0
        posY = ((contextSize.height - contextSize.width) / 2)
        cgwidth = contextSize.width
        cgheight = contextSize.width
    }

    let rect: CGRect = CGRectMake(posX, posY, cgwidth, cgheight)

    // Create bitmap image from context using the rect
    let imageRef: CGImageRef = CGImageCreateWithImageInRect(contextImage.CGImage, rect)

    // Create a new image based on the imageRef and rotate back to the original orientation
    let image: UIImage = UIImage(CGImage: imageRef, scale: image.scale, orientation: image.imageOrientation)!

    return image
}

我发现大部分信息都在这个网站上,如果你想要进一步阅读的话。

已更新至Swift 4

func cropToBounds(image: UIImage, width: Double, height: Double) -> UIImage {

        let cgimage = image.cgImage!
        let contextImage: UIImage = UIImage(cgImage: cgimage)
        let contextSize: CGSize = contextImage.size
        var posX: CGFloat = 0.0
        var posY: CGFloat = 0.0
        var cgwidth: CGFloat = CGFloat(width)
        var cgheight: CGFloat = CGFloat(height)

        // See what size is longer and create the center off of that
        if contextSize.width > contextSize.height {
            posX = ((contextSize.width - contextSize.height) / 2)
            posY = 0
            cgwidth = contextSize.height
            cgheight = contextSize.height
        } else {
            posX = 0
            posY = ((contextSize.height - contextSize.width) / 2)
            cgwidth = contextSize.width
            cgheight = contextSize.width
        }

        let rect: CGRect = CGRect(x: posX, y: posY, width: cgwidth, height: cgheight)

        // Create bitmap image from context using the rect
        let imageRef: CGImage = cgimage.cropping(to: rect)!

        // Create a new image based on the imageRef and rotate back to the original orientation
        let image: UIImage = UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)

        return image
    }

我理解了上面的一些代码,并使用path.bound将其放置在中心位置,它也起作用了...(呃...我似乎无法发布我的代码,不管怎样我会更新我的问题) - jhayvi
你的解决方案对我有用。只是想知道是否有任何方法来实现裁剪动画。 - Swift Rabbit
3
为什么你要有宽度/高度参数? - Coder1224
2
这两行代码是不必要的。您正在将UIImage转换为CGImage,然后再转回来。 - eharo2
让cgimage = image.cgImage!让contextImage:UIImage = UIImage(cgImage:cgimage)需要这两行来创建一个比例为1.0的contextImage。 - Alexey Sobolevsky
这个能用来获取图像的16:9吗? - Luke Irvin

29

接受的答案只对我进行了平方。我需要一个更加灵活的裁剪机制,所以我编写了以下扩展:

import UIKit

extension UIImage {

func crop(to:CGSize) -> UIImage {

    guard let cgimage = self.cgImage else { return self }

    let contextImage: UIImage = UIImage(cgImage: cgimage)

    guard let newCgImage = contextImage.cgImage else { return self }

    let contextSize: CGSize = contextImage.size

    //Set to square
    var posX: CGFloat = 0.0
    var posY: CGFloat = 0.0
    let cropAspect: CGFloat = to.width / to.height

    var cropWidth: CGFloat = to.width
    var cropHeight: CGFloat = to.height

    if to.width > to.height { //Landscape
        cropWidth = contextSize.width
        cropHeight = contextSize.width / cropAspect
        posY = (contextSize.height - cropHeight) / 2
    } else if to.width < to.height { //Portrait
        cropHeight = contextSize.height
        cropWidth = contextSize.height * cropAspect
        posX = (contextSize.width - cropWidth) / 2
    } else { //Square
        if contextSize.width >= contextSize.height { //Square on landscape (or square)
            cropHeight = contextSize.height
            cropWidth = contextSize.height * cropAspect
            posX = (contextSize.width - cropWidth) / 2
        }else{ //Square on portrait
            cropWidth = contextSize.width
            cropHeight = contextSize.width / cropAspect
            posY = (contextSize.height - cropHeight) / 2
        }
    }

    let rect: CGRect = CGRect(x: posX, y: posY, width: cropWidth, height: cropHeight)

    // Create bitmap image from context using the rect
    guard let imageRef: CGImage = newCgImage.cropping(to: rect) else { return self}

    // Create a new image based on the imageRef and rotate back to the original orientation
    let cropped: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)

    UIGraphicsBeginImageContextWithOptions(to, false, self.scale)
    cropped.draw(in: CGRect(x: 0, y: 0, width: to.width, height: to.height))
    let resized = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return resized ?? self
  }
}

你可以这样使用它:

let size = CGSize(width: 300, height: 200)
let image = UIImage(named: "my_great_photo")?.crop(size)

如果有人有关于如何更好地处理横向、纵向和正方形的想法,请告诉我。


这对我没用。我只得到一个黑色方框。变量cropped在任何地方都没有被使用。我猜那就是问题所在。 - aross
是的,抱歉,我打错了一个字。 现在底部的那一行代码应该是: cropped.drawInRect(CGRectMake(0, 0, to.width, to.height)) 这意味着裁剪后的图像将被重新调整大小。我有不同的调整大小和裁剪方法,因此之前是self.drawInRect。 - Tanel Teemusk
哇,谢谢。你帮我省了数百万小时的时间。这太有帮助了。我的一个问题是,在裁剪图像时,图像的分辨率会变小。是否可能在裁剪图像的同时保持图像质量? - nuynait
图片已经出现,但裁剪区域是黑色的。我该如何将其改为白色?谢谢。 - Kishor Pahalwani
@TanelTeemusk 当我从相机捕获图像时,图像会自动旋转。 - Ilesh P
显示剩余4条评论

13

你可以尝试这个答案。 它是用Swift 3编写的。

extension UIImage {
  func crop(to:CGSize) -> UIImage {
    guard let cgimage = self.cgImage else { return self }

    let contextImage: UIImage = UIImage(cgImage: cgimage)

    let contextSize: CGSize = contextImage.size

    //Set to square
    var posX: CGFloat = 0.0
    var posY: CGFloat = 0.0
    let cropAspect: CGFloat = to.width / to.height

    var cropWidth: CGFloat = to.width
    var cropHeight: CGFloat = to.height

    if to.width > to.height { //Landscape
        cropWidth = contextSize.width
        cropHeight = contextSize.width / cropAspect
        posY = (contextSize.height - cropHeight) / 2
    } else if to.width < to.height { //Portrait
        cropHeight = contextSize.height
        cropWidth = contextSize.height * cropAspect
        posX = (contextSize.width - cropWidth) / 2
    } else { //Square
        if contextSize.width >= contextSize.height { //Square on landscape (or square)
            cropHeight = contextSize.height
            cropWidth = contextSize.height * cropAspect
            posX = (contextSize.width - cropWidth) / 2
        }else{ //Square on portrait
            cropWidth = contextSize.width
            cropHeight = contextSize.width / cropAspect
            posY = (contextSize.height - cropHeight) / 2
        }
    }

    let rect: CGRect = CGRect(x : posX, y : posY, width : cropWidth, height : cropHeight)

    // Create bitmap image from context using the rect
    let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!

    // Create a new image based on the imageRef and rotate back to the original orientation
    let cropped: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)

    cropped.draw(in: CGRect(x : 0, y : 0, width : to.width, height : to.height))

    return cropped
  }
}

1
运行完美 - iOS.Lover
适用于大图片,同时对于小图片也会在两侧留出空白。 - Coder ACJHP

9

这是答案,感谢 @awolf (Cropping an UIImage)。它完美地处理了比例和方向。只需在要裁剪的图像上调用此方法,并传递裁剪的CGRect,无需担心比例或方向。请随意检查cgImage是否为空,而不像我在这里强制解包。

extension UIImage {
    func croppedInRect(rect: CGRect) -> UIImage {
        func rad(_ degree: Double) -> CGFloat {
            return CGFloat(degree / 180.0 * .pi)
        }

        var rectTransform: CGAffineTransform
        switch imageOrientation {
        case .left:
            rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
        case .right:
            rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
        case .down:
            rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
        default:
            rectTransform = .identity
        }
        rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)

        let imageRef = self.cgImage!.cropping(to: rect.applying(rectTransform))
        let result = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
        return result
    }
}

如果您想将裁剪矩形居中,只需进行简单的数学计算。大致如下:
let x = (image.width - croppingFrame.width) / 2

另外注意:如果你在使用嵌套在scrollView中的imageView,还需要考虑缩放比例。假设你的imageView跨越了整个scrollView的内容视图,并且你使用scrollView的边界作为裁剪框架,则可以获取裁剪后的图像如下:

let ratio = imageView.image!.size.height / scrollView.contentSize.height
let origin = CGPoint(x: scrollView.contentOffset.x * ratio, y: scrollView.contentOffset.y * ratio)
let size = CGSize(width: scrollView.bounds.size.width * ratio, let height: scrollView.bounds.size.height * ratio)
let cropFrame = CGRect(origin: origin, size: size)
let croppedImage = imageView.image!.croppedInRect(rect: cropFrame)

“let x = (image.width - croppingFrame.width) / 2” -> 我需要把这行代码放在哪里?谢谢。 - famfamfam

4
我设计了一段代码,可以根据需要裁剪视频的宽高比,而不管原视频尺寸大小(这段代码是从@Cole的答案中改编而来):
func cropImage(uncroppedImage: UIImage, cropWidth: CGFloat, cropHeight: CGFloat) -> UIImage {

        let contextImage: UIImage = UIImage(cgImage: uncroppedImage.cgImage!)

        let contextSize: CGSize = contextImage.size
        var cropX: CGFloat = 0.0
        var cropY: CGFloat = 0.0
        var cropRatio: CGFloat = CGFloat(cropWidth/cropHeight)
        var originalRatio: CGFloat = contextSize.width/contextSize.height
        var scaledCropHeight: CGFloat = 0.0
        var scaledCropWidth: CGFloat = 0.0

        // See what size is longer and set crop rect parameters
        if originalRatio > cropRatio {

            scaledCropHeight = contextSize.height
            scaledCropWidth = (contextSize.height/cropHeight) * cropWidth
            cropX = (contextSize.width - scaledCropWidth) / 2
            cropY = 0

        } else {
            scaledCropWidth = contextSize.width
            scaledCropHeight = (contextSize.width/cropWidth) * cropHeight
            cropY = (contextSize.height / scaledCropHeight) / 2
            cropX = 0
        }

        let rect: CGRect = CGRect(x: cropX, y: cropY, width: scaledCropWidth, height: scaledCropHeight)

        // Create bitmap image from context using the rect
        let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!

        // Create a new image based on the imageRef and rotate back to the original orientation

        let croppedImage: UIImage = UIImage(cgImage: imageRef, scale: uncroppedImage.scale, orientation: uncroppedImage.imageOrientation)

        return croppedImage
    }

希望能帮到你!

要从图像的顶部和底部等量裁剪(从中心Y轴裁剪),请更改cropY =(contextSize.height / scaledCropHeight)/ 2为cropY =(contextSize.height - scaledCropHeight)/ 2 - Teddichiiwa

3

您可以使用以下方法进行裁剪:

let croppedImage = yourImage.cgImage.cropping(to:rect)

3

工作中的 Swift 3 示例

extension UIImage {

    func crop(to:CGSize) -> UIImage {
        guard let cgimage = self.cgImage else { return self }

        let contextImage: UIImage = UIImage(cgImage: cgimage)

        let contextSize: CGSize = contextImage.size

        //Set to square
        var posX: CGFloat = 0.0
        var posY: CGFloat = 0.0
        let cropAspect: CGFloat = to.width / to.height

        var cropWidth: CGFloat = to.width
        var cropHeight: CGFloat = to.height

        if to.width > to.height { //Landscape
            cropWidth = contextSize.width
            cropHeight = contextSize.width / cropAspect
            posY = (contextSize.height - cropHeight) / 2
        } else if to.width < to.height { //Portrait
            cropHeight = contextSize.height
            cropWidth = contextSize.height * cropAspect
            posX = (contextSize.width - cropWidth) / 2
        } else { //Square
            if contextSize.width >= contextSize.height { //Square on landscape (or square)
                cropHeight = contextSize.height
                cropWidth = contextSize.height * cropAspect
                posX = (contextSize.width - cropWidth) / 2
            }else{ //Square on portrait
                cropWidth = contextSize.width
                cropHeight = contextSize.width / cropAspect
                posY = (contextSize.height - cropHeight) / 2
            }
        }

        let rect: CGRect = CGRect(x: posX, y: posY, width: cropWidth, height: cropHeight)
        // Create bitmap image from context using the rect
        let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!

        // Create a new image based on the imageRef and rotate back to the original orientation
        let cropped: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)

        UIGraphicsBeginImageContextWithOptions(to, true, self.scale)
        cropped.draw(in: CGRect(x: 0, y: 0, width: to.width, height: to.height))
        let resized = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return resized!
    }
}

3

3

向Cole致敬

Swift 3

func crop(image: UIImage, withWidth width: Double, andHeight height: Double) -> UIImage? {
    
    if let cgImage = image.cgImage {
        
        let contextImage: UIImage = UIImage(cgImage: cgImage)
        
        let contextSize: CGSize = contextImage.size
        
        var posX: CGFloat = 0.0
        var posY: CGFloat = 0.0
        var cgwidth: CGFloat = CGFloat(width)
        var cgheight: CGFloat = CGFloat(height)
        
        // See what size is longer and create the center off of that
        if contextSize.width > contextSize.height {
            posX = ((contextSize.width - contextSize.height) / 2)
            posY = 0
            cgwidth = contextSize.height
            cgheight = contextSize.height
        } else {
            posX = 0
            posY = ((contextSize.height - contextSize.width) / 2)
            cgwidth = contextSize.width
            cgheight = contextSize.width
        }
        
        let rect: CGRect = CGRect(x: posX, y: posY, width: cgwidth, height: cgheight)
        
        // Create bitmap image from context using the rect
        var croppedContextImage: CGImage? = nil
        if let contextImage = contextImage.cgImage {
            if let croppedImage = contextImage.cropping(to: rect) {
                croppedContextImage = croppedImage
            }
        }
        
        // Create a new image based on the imageRef and rotate back to the original orientation
        if let croppedImage:CGImage = croppedContextImage {
            let image: UIImage = UIImage(cgImage: croppedImage, scale: image.scale, orientation: image.imageOrientation)
            return image
        }
        
    }
    
    return nil
}

该方法的参数对结果没有任何影响。 - Ely

2

或者创建一个 UImage 扩展

extension UIImage {
    func cropped(boundingBox: CGRect) -> UIImage? {
        guard let cgImage = self.cgImage?.cropping(to: boundingBox) else {
            return nil
        }

        return UIImage(cgImage: cgImage)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接