如何在iOS中的UIImageView中裁剪圆圈内的图像 [英] How to crop image inside the circle in UIImageView in iOS
问题描述
我有一个应用程序,我有一个 UIImageView
显示主图像,另一个 UIImageView
用作面具它显示了一个透明且在其不透明之外的圆圈,可以使用 UIPanGestureRecognizer
移动此圆圈,我想知道将圆圈内的图像裁剪成新的方法图片。这是附加的代码和屏幕截图
I have an app where I have a UIImageView
which displays main image and another UIImageView
being used as a mask which shows a circle which is transparent and outside its opaque, this circle can be moved using a UIPanGestureRecognizer
, I want to know a wayout to crop the image inside the circle into a new image. Here is the attached code and screen shot
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// create pan gesture
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(handlePan:)];
[self.view addGestureRecognizer:pan];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [[self makeCircleAtLocation:self.view.center radius:100.0] CGPath];
shapeLayer.strokeColor = [[UIColor clearColor] CGColor];
shapeLayer.fillColor = nil;
shapeLayer.lineWidth = 3.0;
// Add CAShapeLayer to our view
[self.view.layer addSublayer:shapeLayer];
// Save this shape layer in a class property for future reference,
// namely so we can remove it later if we tap elsewhere on the screen.
self.circleLayer = shapeLayer;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// Create a UIBezierPath which is a circle at a certain location of a certain radius.
// This also saves the circle's center and radius to class properties for future reference.
- (UIBezierPath *)makeCircleAtLocation:(CGPoint)location radius:(CGFloat)radius
{
self.circleCenter = location;
self.circleRadius = radius;
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:self.circleCenter
radius:self.circleRadius
startAngle:0.0
endAngle:M_PI * 2.0
clockwise:YES];
return path;
}
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static CGPoint oldCenter;
if (gesture.state == UIGestureRecognizerStateBegan)
{
// If we're starting a pan, make sure we're inside the circle.
// So, calculate the distance between the circle's center and
// the gesture start location and we'll compare that to the
// radius of the circle.
CGPoint location = [gesture locationInView:gesture.view];
CGPoint translation = [gesture translationInView:gesture.view];
location.x -= translation.x;
location.y -= translation.y;
CGFloat x = location.x - self.circleCenter.x;
CGFloat y = location.y - self.circleCenter.y;
CGFloat distance = sqrtf(x*x + y*y);
// If we're outside the circle, cancel the gesture.
// If we're inside it, keep track of where the circle was.
oldCenter = self.circleCenter;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
// Let's calculate the new center of the circle by adding the
// the translationInView to the old circle center.
CGPoint translation = [gesture translationInView:gesture.view];
CGPoint newCenter = CGPointMake(oldCenter.x + translation.x, oldCenter.y + translation.y);
// CGPoint newCenter = [gesture locationInView:self.view];
if (newCenter.x < 160) {
newCenter.x = 160;
}
else if (newCenter.x > self.view.frame.size.width - 160) {
newCenter.x = self.view.frame.size.width - 160;
}
if (newCenter.y < 242) {
newCenter.y = 242;
}
else if (newCenter.y > self.view.frame.size.height - imageMain.center.y) {
newCenter.y = self.view.frame.size.height - imageMain.center.y;
}
// Update the path for our CAShapeLayer
// self.circleLayer.path = [[self makeCircleAtLocation:newCenter radius:self.circleRadius] CGPath];
imageCircle.center = newCenter;
}
}
@end
结果是
推荐答案
要保存蒙面图像,在iOS 7中你要使用 drawViewHierarchyInRect
,在早期版本的iOS中,您将使用 renderInContext
。您可能还想使用 CGImageCreateWithImageInRect
裁剪图像。请参阅下面的 handleTap
作为示例。
To save the masked image, in iOS 7 you'd use drawViewHierarchyInRect
, and in earlier versions of iOS, you'd use renderInContext
. You might also want to crop the image with CGImageCreateWithImageInRect
. See my handleTap
below for an example.
但我注意到您显然是通过覆盖图像来屏蔽。我建议使用 UIBezierPath
作为图像视图的图层蒙版的基础,以及 CAShapeLayer
你会用来绘制圆圈(假设你画圆圈时想要一个边框。如果你的面具是一个规则的形状(如圆圈),它可能更灵活,使它成为 CAShapeLayer
带有 UIBezierPath
(而不是图像),因为这样你不仅可以用平移手势移动它,还可以缩放它,用捏手势:
But I note that you are apparently masking by overlaying an image. I might suggest using a UIBezierPath
for the basis of both a layer mask for the image view, as well as the CAShapeLayer
you'll use to draw the circle (assuming you want a border as you draw the circle. If your mask is a regular shape (such as a circle), it's probably more flexible to make it a CAShapeLayer
with a UIBezierPath
(rather than an image), because that way you can not only move it around with a pan gesture, but also scale it, too, with a pinch gesture:
< img src =https://i.stack.imgur.com/ss7IP.gifalt =在此处输入图像说明>
这是示例实现:
// ViewController.swift
// CircleMaskDemo
//
// Created by Robert Ryan on 4/18/18.
// Copyright © 2018 Robert Ryan. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var pinch: UIPinchGestureRecognizer!
@IBOutlet weak var pan: UIPanGestureRecognizer!
private let maskLayer = CAShapeLayer()
private lazy var radius: CGFloat = min(view.bounds.width, view.bounds.height) * 0.3
private lazy var center: CGPoint = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
private let pathLayer: CAShapeLayer = {
let _pathLayer = CAShapeLayer()
_pathLayer.fillColor = UIColor.clear.cgColor
_pathLayer.strokeColor = UIColor.black.cgColor
_pathLayer.lineWidth = 3
return _pathLayer
}()
override func viewDidLoad() {
super.viewDidLoad()
imageView.layer.addSublayer(pathLayer)
imageView.layer.mask = maskLayer
updateCirclePath(at: center, radius: radius)
// imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(pinch)
imageView.addGestureRecognizer(pan)
}
private var oldCenter: CGPoint!
private var oldRadius: CGFloat!
@IBAction func handlePinch(_ gesture: UIPinchGestureRecognizer) {
let scale = gesture.scale
if gesture.state == .began { oldRadius = radius }
updateCirclePath(at: center, radius: oldRadius * scale)
}
@IBAction func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: gesture.view)
if gesture.state == .began { oldCenter = center }
let newCenter = CGPoint(x: oldCenter.x + translation.x, y: oldCenter.y + translation.y)
updateCirclePath(at: newCenter, radius: radius)
}
@IBAction func handleTap(_ gesture: UITapGestureRecognizer) {
let fileURL = try! FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("image.png")
let scale = imageView.window!.screen.scale
let radius = self.radius * scale
let center = CGPoint(x: self.center.x * scale, y: self.center.y * scale)
let frame = CGRect(x: center.x - radius,
y: center.y - radius,
width: radius * 2.0,
height: radius * 2.0)
// temporarily remove the circleLayer
let saveLayer = pathLayer
saveLayer.removeFromSuperlayer()
// render the clipped image
UIGraphicsBeginImageContextWithOptions(imageView.frame.size, false, 0)
imageView.drawHierarchy(in: imageView.bounds, afterScreenUpdates: true)
// capture the image and close the context
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
// add the circleLayer back
imageView.layer.addSublayer(saveLayer)
// crop the image
let imageRef = image.cgImage!.cropping(to: frame)!
let cropped = UIImage(cgImage: imageRef)
// save the image
let data = UIImagePNGRepresentation(cropped)!
try? data.write(to: fileURL)
// tell the user we're done
let alert = UIAlertController(title: nil, message: "Saved", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
private func updateCirclePath(at center: CGPoint, radius: CGFloat) {
self.center = center
self.radius = radius
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
maskLayer.path = path.cgPath
pathLayer.path = path.cgPath
}
}
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return (gestureRecognizer == pinch && otherGestureRecognizer == pan) ||
(gestureRecognizer == pan && otherGestureRecognizer == pinch)
}
}
如果您不想在圆圈周围绘制边框,那么它就更容易了,因为您可以提取与 circleLayer
相关的任何内容。
If you don't want to draw the border around the circle, then it's even easier, as you can pull anything related to circleLayer
.
如果您对Objective-C示例感兴趣,请参阅此答案的先前修订版。
If you're interested in Objective-C example, see previous revision of this answer.
这篇关于如何在iOS中的UIImageView中裁剪圆圈内的图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!