如何在“方面填充”模式下将 UIImageView 裁剪为新的 UIImage?
Posted
技术标签:
【中文标题】如何在“方面填充”模式下将 UIImageView 裁剪为新的 UIImage?【英文标题】:How to crop a UIImageView to a new UIImage in 'aspect fill' mode? 【发布时间】:2017-05-01 14:26:04 【问题描述】:我正在尝试使用覆盖层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
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 使用下面的 Matt 的答案解决了
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")
最终结果
【问题讨论】:
但是你的新素材显示了一个屏幕截图,其中原始图像不受aspectFill
或aspectFit
的影响;正在显示整个图像!我认为您的图像视图的配置方式与您想象的不一样。
我仔细检查了两个图像视图都设置为aspectFill
。可能是因为我直接将aspectCroppedImage
分配给destinationImageView
?
为了清晰起见,我将快速更新我的实现和屏幕截图。
那么您在完全错误的时间创建aspectCroppedImage
。
实际应该何时调用计算它的代码?
【参考方案1】:
让我们把问题分成两部分:
给定 UIImageView 的大小及其 UIImage 的大小,如果 UIImageView 的内容模式是 Aspect Fill, UIImage 中适合 UIImageView 的部分是什么?实际上,我们需要裁剪原始图像以匹配 UIImageView 实际显示的内容。
给定 UIImageView 中的任意矩形,它对应于裁剪图像的哪一部分(源自第 1 部分)?
第一部分是有趣的部分,所以让我们尝试一下。 (然后第二部分将变得微不足道。)
这是我将使用的原始图像:
https://static1.squarespace.com/static/54e8ba93e4b07c3f655b452e/t/56c2a04520c64707756f4267/1455596221531/
该图像为 1000x611。这是缩小后的样子(但请记住,我将始终使用原始图像):
但是,我的图像视图将是 139x182,并设置为 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))
结果是这个图像(忽略灰色边框):
但是你瞧,这是正确的答案!我从原始图像中准确地提取了图像视图内部描绘的区域。
所以现在您拥有所需的所有信息。 croppedIm
是实际显示在图像视图的剪切区域中的 UIImage。 scale
是图像视图和该图像之间的比例。因此,您可以轻松解决您最初提出的问题!给定图像视图上的任何矩形,在图像视图的边界坐标中,您只需应用比例(即,将其所有四个属性除以scale
) - 现在您拥有与croppedIm
的一部分相同的矩形。
(请注意,我们真的不需要裁剪原始图像以获得croppedIm
;实际上,知道如何执行该操作就足够了裁剪。重要的信息是scale
以及croppedImRect
的origin
;根据这些信息,您可以将矩形施加在图像视图上,对其进行缩放、并将其偏移到获取原始图像的所需矩形。)
编辑我添加了一个小截屏视频,只是为了表明我的方法可以作为概念验证:
EDIT还在此处创建了一个可下载的示例项目:
https://github.com/mattneub/Programming-ios-Book-Examples/blob/39cc800d18aa484d17c26ffcbab8bbe51c614573/bk2ch02p058cropImageView/Cropper/ViewController.swift
但请注意,我不能保证 URL 将永远存在,因此请阅读上面的讨论以了解所使用的方法。
【讨论】:
我了解您发布的内容,但您能否详细说明“只需应用比例,现在您拥有与croppedIm 的一部分相同的矩形”是什么意思。我的裁剪视图在大小和位置上是动态的,即使使用croppedIm 并使用我在裁剪上方提供的功能仍然关闭,我不太清楚为什么。 如果您的代码适用于切面匹配,那么如果您替换croppedIm
并使用切面匹配,它将起作用。那么为什么不这样做呢?这就是我将其简化为以前解决的问题的意思。
是的,我明白你在说什么。我将使用代码更新我的原始问题,包括您的上下文解决方案。
我添加了一个小截屏视频,以表明我的方法可以正确地将图像实际裁剪为指定的矩形。
用可行的解决方案和一个演示功能的 gif 更新了我的帖子。【参考方案2】:
马特完美地回答了这个问题。我正在创建一个全屏相机,并且需要使最终输出与全屏预览相匹配。在这里提供了 Matt 在 Swift 5 中的整体答案的紧凑扩展,以便其他人使用。推荐阅读马特的回答,因为它很好地解释了事情。
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
【讨论】:
它似乎根本不起作用 - 我总是得到相同的矩形。无论您传递其他来源或其他大小 - 结果始终相同 我希望回答您的问题,但您不只是提出问题,而是选择否决可行的解决方案,因为它可能不适合您的特定用例或错误。祝你好运。 是的,也许我的代码包含错误/错误,但无论如何确认的答案有效,您的答案无效。以上是关于如何在“方面填充”模式下将 UIImageView 裁剪为新的 UIImage?的主要内容,如果未能解决你的问题,请参考以下文章
swift UIImage使用UIImageView调整方面填充和裁剪