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

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

问题描述

我正在尝试使用覆盖 UIView 来裁剪图像视图的子图像,该覆盖可以放在 UIImageView中的任何位置。当UIImageView内容模式为'Aspect Fit'时,我正在借用类似帖子中的解决方案来解决这个问题。建议的解决方案是:

  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

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

返回CGRect(x:x,y:y,width:width,height:height )
}

问题是当图像视图是宽高比填充时使用此解决方案裁剪的细分不与涂层 UIView 定位。我不太确定如何调整此代码以适应Aspect Fill或重新定位我的覆盖 UIView ,以便它与我正在尝试的片段1:1对齐庄稼。



UPDATE使用下面的Matt答案解决

  class ViewController:UIViewController {

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

覆盖func viewDidLoad(){
super.viewDidLoad()

cropView = CropView(框架:CGRect(x:0,y:0,宽度:45) ,身高:45))

catImageView.image = UIImage(命名:cat)
catImageView.clipsToBounds = true

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

catImageView.addSubview(cropView)

让IMAGESIZE = catImageView.image!.size
let imageViewSize = catImageView.bounds.size

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

if imageSize。高度*比例< imageViewSize.height {
scale = imageViewSize.height / imageSize.height
}

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

let croppedImrect =
CGRect(来源: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(_发件人:任何){
让cropFrame = catImageView.computeCropRect(为:cropView.frame)
如果让imageRef = catImageView.image .cgImage .cropping(到?: cropFrame){
catImageView.image = UIImage(cgImage:imageRef)
}
}

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

最终结果

解决方案

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


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


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


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



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





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





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



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

 让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(来源:CGPoint(x:(imsize.width-croppedImsize.width)/2.0,
y:(imsize.height-croppedImsize.height)/2.0),
size:croppedImsize)

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

  let R = UIGraphicsImageRenderer(尺寸:croppedImsize)
让croppedIm = r.image {_在
iv.image .draw(在:CGPoint(X:-croppedImrect.origin.x,Y:-croppedImrect。 origin.y))
}

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





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



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



(注意我们没有真的需要裁剪原始图像以获得 croppedIm ;实际上,知道如何执行该裁剪就足够了。重要信息是比例以及 croppedImRect 来源;给出该信息,你可以采取强加在图像视图上的矩形,缩放它,并偏移它以获得原始图像的所需矩形。)






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





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



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



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


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天全站免登陆