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

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

问题描述

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

 func computeCropRect(for sourceFrame : CGRect) ->CG矩形{让 widthScale = bounds.size.width/image!.size.width让 heightScale = bounds.size.height/image!.size.heightvar x : CGFloat = 0变量 y : CGFloat = 0变量宽度:CGFloat = 0变量高度:CGFloat = 0var offSet : CGFloat = 0如果 widthScale <高度比例{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 的回答解决

class ViewController: UIViewController {@IBOutlet 弱 var catImageView:UIImageView!私有变量cropView:CropView!覆盖 func viewDidLoad() {super.viewDidLoad()cropView = CropView(frame: CGRect(x: 0, y: 0, width: 45, height: 45))catImageView.image = UIImage(named: "cat")catImageView.clipsToBounds = truecatImageView.layer.borderColor = UIColor.purple.cgColorcatImageView.layer.borderWidth = 2.0catImageView.backgroundColor = UIColor.yellowcatImageView.addSubview(cropView)让 imageSize = catImageView.image!.size让 imageViewSize = catImageView.bounds.sizevar 比例:CGFloat = imageViewSize.width/imageSize.width如果 imageSize.height * scale 

最终结果

解决方案

我们把问题分成两部分:

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

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

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

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

然而,我的图像视图将是 139x182,并设置为纵横比填充.当它显示图像时,它看起来像这样:

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

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

让 imsize = iv.image!.size让 ivsize = iv.bounds.sizevar 比例:CGFloat = ivsize.width/imsize.width如果 imsize.height * scale 

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

let 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 还在此处创建了一个可下载的示例项目:

nohttpsreferr.swift"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天全站免登陆