如何在“方面填充"模式下将 UIImageView 裁剪为新的 UIImage? [英] How to crop a UIImageView to a new UIImage in 'aspect fill' mode?

查看:20
本文介绍了如何在“方面填充"模式下将 UIImageView 裁剪为新的 UIImage?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用叠加层 UIView 裁剪图像视图的子图像,该叠加层可以放置在 UIImageView 中的任何位置.当 UIImageView 内容模式为Aspect Fit"时,我从类似的帖子中借用了一个解决方案.建议的解决方案是:

 func computeCropRect(for sourceFrame : CGRect) ->CGRect {让 widthScale = bounds.size.width/image!.size.width让 heightScale = bounds.size.height/image!.size.heightvar x : CGFloat = 0var y : CGFloat = 0变量宽度:CGFloat = 0变量高度:CGFloat = 0变量偏移量:CGFloat = 0如果宽度缩放 <高度标尺 {offSet = (bounds.size.height - (image!.size.height * widthScale))/2x = sourceFrame.origin.x/widthScaley = (sourceFrame.origin.y - offSet)/widthScale宽度 = sourceFrame.size.width/widthScale高度 = sourceFrame.size.height/widthScale} 别的 {offSet = (bounds.size.width - (image!.size.width * heightScale))/2x = (sourceFrame.origin.x - offSet)/heightScaley = sourceFrame.origin.y/heightScale宽度 = sourceFrame.size.width/heightScale高度 = sourceFrame.size.height/heightScale}返回CGRect(x:x,y:y,宽度:宽度,高度:高度)}

问题在于,当图像视图为纵横填充时使用此解决方案会导致裁剪的片段与覆盖层 UIView 的位置不完全对齐.我不太确定如何调整此代码以适应 Aspect Fill 或重新定位我的叠加层 UIView 以便它与我要裁剪的片段 1:1 对齐.

UPDATE 使用下面的 Matt 的回答解决了

类 ViewController: UIViewController {@IBOutlet 弱 var catImageView:UIImageView!私人 var cropView : CropView!覆盖 func viewDidLoad() {super.viewDidLoad()cropView = CropView(frame: CGRect(x: 0, y: 0, width: 45, height: 45))catImageView.image = UIImage(命名:猫")catImageView.clipsToBounds = truecatImageView.layer.borderColor = UIColor.purple.cgColorcatImageView.layer.borderWidth = 2.0catImageView.backgroundColor = UIColor.yellowcatImageView.addSubview(cropView)让 imageSize = catImageView.image!.size让 imageViewSize = catImageView.bounds.size变量比例:CGFloat = imageViewSize.width/imageSize.width如果 imageSize.height * 比例 <imageViewSize.height {比例 = imageViewSize.height/imageSize.height}让croppedImageSize = CGSize(宽度:imageViewSize.width/scale,高度:imageViewSize.height/scale)让croppedImrect =CGRect(origin: CGPoint(x: (imageSize.width-croppedImageSize.width)/2.0,y: (imageSize.height-croppedImageSize.height)/2.0),大小:croppedImageSize)让渲染器 = UIGraphicsImageRenderer(大小:croppedImageSize)让 _ = renderer.image { _ incatImageView.image!.draw(at: CGPoint(x:-croppedImrect.origin.x, y:-croppedImrect.origin.y))}}@IBAction func performCrop(_ sender: Any) {letcropFrame = catImageView.computeCropRect(for:cropView.frame)如果让 imageRef = catImageView.image?.cgImage?.cropping(to:croppingFrame) {catImageView.image = UIImage(cgImage: imageRef)}}@IBAction func resetCrop(_ sender: Any) {catImageView.image = UIImage(命名:猫")}}

最终结果

解决方案

我们把问题分成两部分:

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

  2. 给定 UIImageView 中的任意矩形,它对应于裁剪图像的哪一部分(源自第 1 部分)?

第一部分是有趣的部分,所以让我们尝试一下.(然后第二部分将变得微不足道.)

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

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

我们要解决的问题是:如果我的图像视图设置为纵横比填充,原始图像的哪一部分显示在我的图像视图中?

我们开始吧.假设 iv 是图像视图:

让 imsize = iv.image!.size让 ivsize = iv.bounds.size变量比例:CGFloat = ivsize.width/imsize.width如果 imsize.height * 比例 <ivsize.height {比例 = ivsize.height/imsize.height}让croppedImsize = CGSize(宽度:ivsize.width/scale, height:ivsize.height/scale)让croppedImrect =CGRect(origin: CGPoint(x: (imsize.width-croppedImsize.width)/2.0,y: (imsize.height-croppedImsize.height)/2.0),尺寸:croppedImsize)

所以现在我们已经解决了这个问题:croppedImrect 是图像视图中显示的原始 图像的区域.让我们继续使用我们的知识,将图像实际裁剪为与图像视图中显示的内容相匹配的新图像:

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

结果是这个图像(忽略灰色边框):

但是你瞧,这是正确的答案!我从原始图像中准确地提取了图像视图内部描绘的区域.

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

(请注意,我们并不真的需要裁剪原始图像来获得croppedIm;实际上,知道如何就足够了em> 来执行该裁剪.重要的信息是 scale 以及 croppedImRectorigin;给定这些信息,您可以采用矩形施加在图像视图上,对其进行缩放、和偏移以获得所需的原始图像矩形.)

<小时>

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

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

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

但请注意,我不能保证 URL 将永远存在,因此请阅读上面的讨论以了解所使用的方法.

I'm trying to crop a sub-image of a image view using an overlay UIView that can be positioned anywhere in the UIImageView. I'm borrowing a solution from a similar post on how to solve this when the UIImageView content mode is '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. 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.

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? We need, in effect, to crop the original image to match what the UIImageView is actually displaying.

  2. Given an arbitrary rect within the UIImageView, what part of the cropped image (derived in part 1) does it correspond to?

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/

That image is 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. 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:

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. 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. scale is the scale between the image view and that image. 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 (i.e. divide all four of its attributes by scale) — and now you have the same rectangle as a portion of 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.)


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

But note that I can't guarantee that URL will last forever, so please read the discussion above to understand the approach used.

这篇关于如何在“方面填充"模式下将 UIImageView 裁剪为新的 UIImage?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆