如何在一个方面填充'将UIImageView裁剪为新的UIImage。模式?

时间:2017-05-01 14:26:04

标签: ios objective-c iphone uiview uiimageview

我尝试使用可以放置在UIView中任意位置的叠加层UIImageView裁剪图像视图的子图像。当UIImageView内容模式为“适合方面”时,我正在借用类似帖子中的解决方案来解决这个问题。提出的解决方案是:

  func computeCropRect(for sourceFrame : CGRect) -> CGRect {

        let widthScale = bounds.size.width / image!.size.width
        let heightScale = bounds.size.height / image!.size.height

        var x : CGFloat = 0
        var y : CGFloat = 0
        var width : CGFloat = 0
        var height : CGFloat = 0
        var offSet : CGFloat = 0

        if widthScale < heightScale {
            offSet = (bounds.size.height - (image!.size.height * widthScale))/2
            x = sourceFrame.origin.x / widthScale
            y = (sourceFrame.origin.y - offSet) / widthScale
            width = sourceFrame.size.width / widthScale
            height = sourceFrame.size.height / widthScale
        } else {
            offSet = (bounds.size.width - (image!.size.width * heightScale))/2
            x = (sourceFrame.origin.x - offSet) / heightScale
            y = sourceFrame.origin.y / heightScale
            width = sourceFrame.size.width / heightScale
            height = sourceFrame.size.height / heightScale
        }

        return CGRect(x: x, y: y, width: width, height: height)
    }

问题是当图像视图是宽高比填充时使用此解决方案会导致裁剪的段不与叠加UIView所在的位置完全对齐。我不太确定如何调整此代码以适应Aspect Fill或重新定位我的叠加UIView,以便它与我尝试裁剪的片段1:1对齐。

UPDATE使用下面的马特答案

解决
class ViewController: UIViewController {

    @IBOutlet weak var catImageView: UIImageView!
    private var cropView : CropView!

    override func viewDidLoad() {
        super.viewDidLoad()

        cropView = CropView(frame: CGRect(x: 0, y: 0, width: 45, height: 45))

        catImageView.image = UIImage(named: "cat")
        catImageView.clipsToBounds = true

        catImageView.layer.borderColor = UIColor.purple.cgColor
        catImageView.layer.borderWidth = 2.0
        catImageView.backgroundColor = UIColor.yellow

        catImageView.addSubview(cropView)

        let imageSize = catImageView.image!.size
        let imageViewSize = catImageView.bounds.size

        var scale : CGFloat = imageViewSize.width / imageSize.width

        if imageSize.height * scale < imageViewSize.height {
            scale = imageViewSize.height / imageSize.height
        }

        let croppedImageSize = CGSize(width: imageViewSize.width/scale, height: imageViewSize.height/scale)

        let croppedImrect =
            CGRect(origin: CGPoint(x: (imageSize.width-croppedImageSize.width)/2.0,
                                   y: (imageSize.height-croppedImageSize.height)/2.0),
                   size: croppedImageSize)

        let renderer = UIGraphicsImageRenderer(size:croppedImageSize)

        let _ = renderer.image { _ in
            catImageView.image!.draw(at: CGPoint(x:-croppedImrect.origin.x, y:-croppedImrect.origin.y))
        }
    }


    @IBAction func performCrop(_ sender: Any) {
        let cropFrame = catImageView.computeCropRect(for: cropView.frame)
        if let imageRef = catImageView.image?.cgImage?.cropping(to: cropFrame) {
            catImageView.image = UIImage(cgImage: imageRef)
        }
    }

    @IBAction func resetCrop(_ sender: Any) {
        catImageView.image = UIImage(named: "cat")
    }
}

最终结果 enter image description here

2 个答案:

答案 0 :(得分:7)

让我们将问题分为两部分:

  1. 考虑到UIImageView的大小及其UIImage的大小,如果UIImageView的内容模式是Aspect Fill, UIImage中适合UIImageView的部分是什么?实际上,我们需要裁剪原始图像以匹配UIImageView实际显示的内容。

  2. 在UIImageView中给定一个任意的矩形,裁剪图像的部分(在第1部分中派生)是否对应于?

  3. 第一部分是有趣的部分,所以让我们试试吧。 (第二部分将变得微不足道。)

    这是我将使用的原始图片:

    https://static1.squarespace.com/static/54e8ba93e4b07c3f655b452e/t/56c2a04520c64707756f4267/1455596221531/

    该图像为1000x611。这是缩小尺寸的原因(但请记住,我将在整个过程中使用原始图像):

    enter image description here

    然而,我的图像视图将是139x182,并设置为Aspect Fill。当它显示图像时,它看起来像这样:

    enter image description here

    我们要解决的问题是:如果我的图片视图设置为Aspect Fill,我的图片视图中会显示原始图像的哪个部分?

    我们走了。假设iv是图像视图:

    let imsize = iv.image!.size
    let ivsize = iv.bounds.size
    
    var scale : CGFloat = ivsize.width / imsize.width
    if imsize.height * scale < ivsize.height {
        scale = ivsize.height / imsize.height
    }
    
    let croppedImsize = CGSize(width:ivsize.width/scale, height:ivsize.height/scale)
    let croppedImrect =
        CGRect(origin: CGPoint(x: (imsize.width-croppedImsize.width)/2.0,
                               y: (imsize.height-croppedImsize.height)/2.0),
               size: croppedImsize)
    

    现在我们已经解决了问题:croppedImrect是图像视图中显示的原始图像的区域。让我们继续使用我们的知识,通过将图像实际裁剪为与图像视图中显示的图像匹配的新图像:

    let r = UIGraphicsImageRenderer(size:croppedImsize)
    let croppedIm = r.image { _ in
        iv.image!.draw(at: CGPoint(x:-croppedImrect.origin.x, y:-croppedImrect.origin.y))
    }
    

    结果是这张图片(忽略灰色边框):

    enter image description here

    但是,看哪,这是正确的答案!我从原始图像中提取了在图像视图内部描绘的区域。

    所以现在您拥有了所需的所有信息。 croppedIm是实际显示在图像视图的剪切区域中的UIImage。 scale是图像视图与该图像之间的比例。因此,您可以轻松解决您最初提出的问题!给定图像视图上的任何矩形,在图像视图的边界坐标中,您只需应用比例(即将所有四个属性除以scale) - 现在您具有与{{的一部分相同的矩形1}}。

    (观察我们真的需要裁剪原始图片才能获得croppedIm;实际上,知道如何就足够了执行该裁剪。重要信息是croppedIm以及scale的{​​{1}};根据该信息,您可以将强加在图像视图上的矩形缩放,并将其偏移以获得原始图像的所需矩形。)

    编辑我添加了一个小小的截屏视频,以表明我的方法可以作为概念证明:

    enter image description here

    编辑此处还创建了一个可下载的示例项目:

    https://github.com/mattneub/Programming-iOS-Book-Examples/blob/39cc800d18aa484d17c26ffcbab8bbe51c614573/bk2ch02p058cropImageView/Cropper/ViewController.swift

    但请注意,我无法保证网址会永久存在,因此请阅读上述讨论以了解所使用的方法。

答案 1 :(得分:0)

马特完美地回答了这个问题。我正在创建全屏摄像头,因此需要使最终输出与全屏预览相匹配。在此提供对Swift 5中Matt的整体答案的紧凑扩展,以方便他人使用。推荐阅读Matt的答案,因为它很好地解释了事情。

extension UIImage {
    func cropToRect(rect: CGRect) -> UIImage? {
        var scale = rect.width / self.size.width
        scale = self.size.height * scale < rect.height ? rect.height/self.size.height : scale

        let croppedImsize = CGSize(width:rect.width/scale, height:rect.height/scale)
        let croppedImrect = CGRect(origin: CGPoint(x: (self.size.width-croppedImsize.width)/2.0,
                                                   y: (self.size.height-croppedImsize.height)/2.0),
                                                   size: croppedImsize)
        UIGraphicsBeginImageContextWithOptions(croppedImsize, true, 0)
        self.draw(at: CGPoint(x:-croppedImrect.origin.x, y:-croppedImrect.origin.y))
        let croppedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return croppedImage
    }
}