简体   繁体   English

如何在“方面填充”模式下将 UIImageView 裁剪为新的 UIImage?

[英]How to crop a UIImageView to a new UIImage in 'aspect fill' mode?

I'm trying to crop a sub-image of a image view using an overlay UIView that can be positioned anywhere in the UIImageView .我正在尝试使用覆盖UIView图像视图的子图像,该覆盖可以位于UIImageView的任何位置。 I'm borrowing a solution from a similar post on how to solve this when the UIImageView content mode is 'Aspect Fit'.当 UIImageView 内容模式为“Aspect Fit”时,我从类似的帖子中借用了一个解决方案。 That proposed solution is:建议的解决方案是:

  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)
    }

The problem is that using this solution when the image view is aspect fill causes the cropped segment to not line up exactly with where the overlay UIView was positioned.问题是,当图像视图为纵横填充时,使用此解决方案会导致裁剪段与覆盖UIView的位置不完全对齐。 I'm not quite sure how to adapt this code to accommodate for Aspect Fill or reposition my overlay UIView so that it lines up 1:1 with the segment I'm trying to crop.我不太确定如何调整此代码以适应 Aspect Fill 或重新定位我的叠加层UIView以便它与我要裁剪的片段 1:1 对齐。

UPDATE Solved using Matt's answer below更新使用下面马特的答案解决

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")
    }
}

The Final Result最终结果在此处输入图像描述

Let's divide the problem into two parts:我们把问题分成两部分:

  1. Given the size of a UIImageView and the size of its UIImage, if the UIImageView's content mode is Aspect Fill, what is the part of the UIImage that fits into the UIImageView?给定一个 UIImageView 的大小和它的 UIImage 的大小,如果 UIImageView 的内容模式是 Aspect Fill,那么适合 UIImageView 的 UIImage 部分是什么? We need, in effect, to crop the original image to match what the UIImageView is actually displaying.实际上,我们需要裁剪原始图像以匹配 UIImageView 实际显示的内容。

  2. Given an arbitrary rect within the UIImageView, what part of the cropped image (derived in part 1) does it correspond to?给定 UIImageView 中的任意矩形,它对应于裁剪图像的哪一部分(在第 1 部分中派生)?

The first part is the interesting part, so let's try it.第一部分是有趣的部分,所以让我们尝试一下。 (The second part will then turn out to be trivial.) (然后第二部分将变得微不足道。)

Here's the original image I'll use:这是我将使用的原始图像:

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

That image is 1000x611.该图像是 1000x611。 Here's what it looks like scaled down (but keep in mind that I'm going to be using the original image throughout):这是缩小后的样子(但请记住,我将在整个过程中使用原始图像):

在此处输入图片说明

My image view, however, will be 139x182, and is set to Aspect Fill.然而,我的图像视图将是 139x182,并设置为纵横比填充。 When it displays the image, it looks like this:当它显示图像时,它看起来像这样:

在此处输入图片说明

The problem we want to solve is: what part of the original image is being displayed in my image view, if my image view is set to Aspect Fill?我们要解决的问题是:如果我的图像视图设置为纵横比填充,我的图像视图中将显示原始图像的哪一部分?

Here we go.开始了。 Assume that iv is the image view:假设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)

So now we have solved the problem: croppedImrect is the region of the original image that is showing in the image view.所以现在我们已经解决了这个问题: croppedImrect是在图像视图中显示的原始图像的区域。 Let's proceed to use our knowledge, by actually cropping the image to a new image matching what is shown in the image view:让我们继续使用我们的知识,通过实际将图像裁剪为与图像视图中显示的内容匹配的新图像:

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

The result is this image (ignore the gray border):结果是这个图像(忽略灰色边框):

在此处输入图片说明

But lo and behold, that is the correct answer!但是你瞧,这就是正确的答案! I have extracted from the original image exactly the region portrayed in the interior of the image view.我已经从原始图像中准确提取了图像视图内部描绘的区域。

So now you have all the information you need.所以现在你拥有了你需要的所有信息。 croppedIm is the UIImage actually displayed in the clipped area of the image view. croppedIm是实际显示在图像视图的裁剪区域中的 UIImage。 scale is the scale between the image view and that image. scale是图像视图和该图像之间的比例。 Therefore, you can easily solve the problem you originally proposed!因此,您可以轻松解决您最初提出的问题! Given any rectangle imposed upon the image view, in the image view's bounds coordinates, you simply apply the scale (ie divide all four of its attributes by scale ) — and now you have the same rectangle as a portion of croppedIm .给定施加在图像视图上的任何矩形,在图像视图的边界坐标中,您只需应用比例(即,将其所有四个属性除以scale )——现在您拥有与croppedIm的一部分相同的矩形。

(Observe that we didn't really need to crop the original image to get croppedIm ; it was sufficient, in reality, to know how to perform that crop. The important information is the scale along with the origin of croppedImRect ; given that information, you can take the rectangle imposed upon the image view, scale it, and offset it to get the desired rectangle of the original image.) (注意,我们并不真的需要裁剪原始图像以获得croppedIm ;实际上,知道如何执行该裁剪就足够。重要的信息是scale以及croppedImRectorigin ;鉴于这些信息,您可以将施加在图像视图上的矩形,缩放,并偏移它以获得原始图像的所需矩形。)


EDIT I added a little screencast just to show that my approach works as a proof of concept:编辑我添加了一个小截屏视频只是为了表明我的方法可以作为概念证明:

在此处输入图片说明

EDIT Also created a downloadable example project here:编辑还在此处创建了一个可下载的示例项目:

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

But note that I can't guarantee that URL will last forever, so please read the discussion above to understand the approach used.但请注意,我不能保证 URL 会永远持续下去,因此请阅读上面的讨论以了解所使用的方法。

Matt answered the question perfectly.马特完美地回答了这个问题。 I was creating a full-screen camera and had a need to make the final output match the full-screen preview.我正在创建一个全屏相机,并且需要使最终输出与全屏预览相匹配。 Offering here a compact extension of Matt's overall answer in Swift 5 for easy use by others.在此提供 Matt 在 Swift 5 中的整体答案的紧凑扩展,以供其他人使用。 Recommend reading Matt's answer as it explains things very well.推荐阅读马特的回答,因为它很好地解释了事情。

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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM