UIImageView:如何在平移和缩放后仅捕获可见内容
Posted
技术标签:
【中文标题】UIImageView:如何在平移和缩放后仅捕获可见内容【英文标题】:UIImageView: How to capture just visible content after Pan & Zoom 【发布时间】:2020-09-21 15:49:43 【问题描述】:我有一个 UIImageView,它由 ImagePicker 加载,照片(3k x 4k 大小)使用 AspectFit 模式填充到视图中。然后我使用 GestureRecognizers 平移和缩放图像,它调整视图的变换比例和平移。这很好用,代码如下:
@objc private func startZooming(_ sender: UIPinchGestureRecognizer)
let scaleResult = sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale)
guard let scale = scaleResult, scale.a > 1, scale.d > 1 else return
sender.view?.transform = scale
sender.scale = 1
@objc private func startPanning(_ sender: UIPanGestureRecognizer)
let translate = sender.translation(in: mainImageView)
let center = mainImageView.center
mainImageView.center = CGPoint(x: center.x + translate.x, y: center.y + translate.y)
self.currImageTranslation.x += translate.x
self.currImageTranslation.y += translate.y
sender.setTranslation(CGPoint(x: 0, y: 0), in: mainImageView)
我的问题是我需要将转换的视觉结果作为 CGImage 输入视觉推理引擎。当我从 UIImageView 获取图像时:
let image = myUIImageView.image // I get the original untransformed image
当我尝试时:
UIGraphicsBeginImageContextWithOptions(image!.size, false, 0.0)
myImageView.layer.render(in: UIGraphicsGetCurrentContext()!)
let newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
newImage 也只是原始图像(未转换)
当我尝试时:
let rect = mainImageView.bounds
let scale = UIScreen.main.scale
var t = self.currImageTranslation
let tScale = mainImageView.transform.a
let zoomOffsetFactorX = ((t.x / rect.size.width) * tScale)
let zoomOffsetFactorY = ((t.y / rect.size.height) * tScale)
t.x = t.x - zoomOffsetFactorX
t.y = (t.y + 20.0) + zoomOffsetFactorY
// create a context
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
let context = UIGraphicsGetCurrentContext()!
let transform = mainImageView.transform
let imrect = CGRect(origin: t, size: rect.size)
context.concatenate(transform)
let tempImage = mainImageView.image
tempImage!.draw(in: imrect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
newImage 可以正确转换,但仅在 scale = 1.0 时,无论是否缩放都会创建分数图像,即剪辑到零、零原点。
我想生成用户在该 ImageView 屏幕上看到的内容的 UIImage。有人可以帮忙吗?
在@DonMag 的慷慨建议下,我尝试了以下方法:
func enableZoom()
let pinchGesture = UIPinchGestureRecognizer(target: self,
action: #selector(startZooming(_:)))
let panGesture = UIPanGestureRecognizer(target: self,
action: #selector(startPanning(_:)))
containerView.isUserInteractionEnabled = true
containerView.addGestureRecognizer(pinchGesture)
containerView.addGestureRecognizer(panGesture)
@objc private func startZooming(_ sender: UIPinchGestureRecognizer)
let scaleResult = sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale)
guard let scale = scaleResult, scale.a > 1, scale.d > 1 else return
sender.view?.transform = scale
newImage()
sender.scale = 1
@objc private func startPanning(_ sender: UIPanGestureRecognizer)
let translate = sender.translation(in: mainImageView)
let center = mainImageView.center
mainImageView.center = CGPoint(x: center.x + translate.x, y: center.y + translate.y)
self.currImageTranslation.x += translate.x
self.currImageTranslation.y += translate.y
sender.setTranslation(CGPoint(x: 0, y: 0), in: mainImageView)
newImage()
//MARK:- create cropped/panned image and infer
func newImage() -> Void
let format = UIGraphicsImageRendererFormat()
// we want a 1:1 points-to-pixels output
format.scale = 1
let renderer = UIGraphicsImageRenderer(size: containerView.bounds.size, format: format)
let image = renderer.image ctx in
containerView.drawHierarchy(in: containerView.bounds, afterScreenUpdates: true)
self.currCGImage = image.cgImage
//repeatInference() // runs only once every 0.3 s
return
现在“newImage”正确地反映了 panGesture 创建的翻译,但似乎忽略了 pinchGesture 的比例变化。 drawHierarchy 似乎忽略了 UIView 中强加的变换。我哪里错了?
【问题讨论】:
澄清...你想要原始图像大小吗?还是屏幕尺寸?因此,例如,如果您的滚动视图在 @2x 设备上为 200x200 pts,并且您正在缩放显示 3k x 4k 图像的 1600x1600 部分...您是否希望结果是 1600x1600 矩形缩小到400x400 像素的图像?或者您想从原始图像中剪裁出 1600x1600 像素 矩形? 感谢您的回复!为了尝试澄清,我没有使用滚动和播放内容大小。只需对图像视图中的 UIImage 进行转换/翻译。 uiimageview 在 plus 手机上约为 440x500,这是我希望返回的尺寸。本质上就像视图的位图。推理引擎将根据需要进行缩放/裁剪。 ***.com/a/66396409/2033377 这个答案有部分答案。肯定没有简短的答案。 【参考方案1】:我不完全确定你在用你的转换代码做什么,但这可能对你有用——而且更简单......
如果我理解正确,你有一个UIImageView
和.aspectFit
,它在一个UIView
“容器”中,你应用一个比例并将转换转换到图像视图:
let format = UIGraphicsImageRendererFormat()
// we want a 1:1 points-to-pixels output
format.scale = 1
let renderer = UIGraphicsImageRenderer(size: containerView.bounds.size, format: format)
let image = renderer.image ctx in
containerView.drawHierarchy(in: containerView.bounds, afterScreenUpdates: true)
这是一个完整的示例实现:
class TransImageViewController: UIViewController
let containerView = UIView()
let imageView = UIImageView()
override func viewDidLoad()
super.viewDidLoad()
guard let img = UIImage(named: "bkg_2400x1600") else
fatalError("Could not load the image!!!")
let stack = UIStackView()
stack.spacing = 20
stack.distribution = .fillEqually
let b1 = UIButton()
let b2 = UIButton()
[b1, b2].forEach b in
b.backgroundColor = .red
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.gray, for: .highlighted)
b1.setTitle("Transform", for: [])
b2.setTitle("Capture", for: [])
stack.addArrangedSubview(b1)
stack.addArrangedSubview(b2)
[stack, containerView, imageView].forEach
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
view.addSubview(containerView)
containerView.addSubview(imageView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// buttons stack at top
stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
// container 400x500 centered
containerView.widthAnchor.constraint(equalToConstant: 400),
containerView.heightAnchor.constraint(equalToConstant: 500),
containerView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
containerView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// imageView constrained all 4 sides to container
imageView.topAnchor.constraint(equalTo: containerView.topAnchor),
imageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
containerView.clipsToBounds = true
imageView.contentMode = .scaleAspectFit
imageView.image = img
view.backgroundColor = .blue
containerView.backgroundColor = .yellow
imageView.backgroundColor = .orange
b1.addTarget(self, action: #selector(self.doTransform), for: .touchUpInside)
b2.addTarget(self, action: #selector(self.grabImage), for: .touchUpInside)
@objc func doTransform() -> Void
var t = CGAffineTransform.identity
t = t.scaledBy(x: 4.0, y: 4.0)
t = t.translatedBy(x: -120, y: 40)
imageView.transform = t
@objc func grabImage() -> Void
let format = UIGraphicsImageRendererFormat()
// we want a 1:1 points-to-pixels output
format.scale = 1
let renderer = UIGraphicsImageRenderer(size: containerView.bounds.size, format: format)
let image = renderer.image ctx in
containerView.drawHierarchy(in: containerView.bounds, afterScreenUpdates: true)
// do what you want with the resulting image
print("Resulting image size:", image.size)
将此图片用作我的“bkg_2400x1600”图片资源:
上面的代码是这样开始的(我的“容器”视图是 400x500 pts):
点击“转换”按钮将应用.scaledBy(x: 4.0, y: 4.0)
和.translatedBy(x: -120, y: 40)
:
然后点击“Capture”给我这个 400x500 像素的图像:
【讨论】:
看起来完美!将在几个小时内对其进行测试,并很快将其标记为正确。非常感谢! 我修改了我的 ViewController 以将目标图像视图放置在容器 UIView 中,并将 userInteractions/GestureRecognizers 设置为容器 UIView。抓取的图像现在按预期在帧中移动,但是,捏合手势(即缩放或放大容器)在屏幕上确实发生了变化,但不会改变抓取的图像。抓取的图像不会放大。我正在修改 OP 以包含当前的相关代码。【参考方案2】:@DonMag 让我走上了正轨。但是,为了在抓取的图像中获得正确的缩放,我需要将 scaleBy 转换应用于 UIImageView,而不是容器视图。此外,我需要将格式比例设置为 pinchGesture 中的当前比例因子。这是工作版本:
func enableZoom()
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(startZooming(_:)))
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(startPanning(_:)))
containerView.isUserInteractionEnabled = true
containerView.addGestureRecognizer(pinchGesture)
containerView.addGestureRecognizer(panGesture)
@objc private func startZooming(_ sender: UIPinchGestureRecognizer)
// let scaleResult = sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale)
// guard let scale = scaleResult, scale.a > 1, scale.d > 1 else return
// sender.view?.transform = scale
let scaleResult = mainImageView?.transform.scaledBy(x: sender.scale, y: sender.scale)
guard let scale = scaleResult, scale.a > 1, scale.d > 1 else return
mainImageView.transform = scale
currImageScale = sender.scale
newImage()
sender.scale = 1
@objc private func startPanning(_ sender: UIPanGestureRecognizer)
let translate = sender.translation(in: mainImageView)
let center = mainImageView.center
mainImageView.center = CGPoint(x: center.x + translate.x, y: center.y + translate.y)
self.currImageTranslation.x += translate.x
self.currImageTranslation.y += translate.y
sender.setTranslation(CGPoint(x: 0, y: 0), in: mainImageView)
newImage()
//MARK:- create cropped/panned image and infer
func newImage() -> Void
let format = UIGraphicsImageRendererFormat()
// we want a 1:1 points-to-pixels output
format.scale = currImageScale
let renderer = UIGraphicsImageRenderer(size: containerView.bounds.size, format: format)
let image = renderer.image ctx in
containerView.drawHierarchy(in: containerView.bounds, afterScreenUpdates: true)
self.currCGImage = image.cgImage
repeatInference() // runs only once every 0.3 s
return
【讨论】:
以上是关于UIImageView:如何在平移和缩放后仅捕获可见内容的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Scrollview 中捕获缩放的 UIImageView 以在 Android 中进行裁剪?
UIScrollView 中的 UIImageView:平移失败