如何在 iOS 的 UIImageView 中裁剪圆圈内的图像 [英] How to crop image inside the circle in UIImageView in iOS

查看:31
本文介绍了如何在 iOS 的 UIImageView 中裁剪圆圈内的图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个应用程序,其中有一个 UIImageView 显示主图像,另一个 UIImageView 用作掩码,显示一个透明且在其不透明之外的圆,可以使用 UIPanGestureRecognizer 移动这个圆圈,我想知道将圆圈内的图像裁剪成新图像的方法.这是附加的代码和屏幕截图

- (void)viewDidLoad{[超级视图DidLoad];//加载视图后做任何额外的设置.//创建平移手势UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self动作:@selector(handlePan:)];[self.view addGestureRecognizer:pan];CAShapeLayer *shapeLayer = [CAShapeLayer 层];shapeLayer.path = [[self makeCircleAtLocation:self.view.center radius:100.0] CGPath];shapeLayer.strokeColor = [[UIColor clearColor] CGColor];shapeLayer.fillColor = nil;shapeLayer.lineWidth = 3.0;//将 CAShapeLayer 添加到我们的视图中[self.view.layer addSublayer:shapeLayer];//将此形状图层保存在类属性中以供将来参考,//也就是说,如果我们稍后点击屏幕上的其他地方,我们可以将其删除.self.circleLayer = shapeLayer;}- (void)didReceiveMemoryWarning{[super didReceiveMemoryWarning];//处理所有可以重新创建的资源.}//创建一个 UIBezierPath,它是一个在某个半径的某个位置的圆.//这也将圆的中心和半径保存到类属性中以供将来参考.- (UIBezierPath *)makeCircleAtLocation:(CGPoint)位置半径:(CGFloat)radius{self.circleCenter = 位置;self.circleRadius = 半径;UIBezierPath *path = [UIBezierPath bezierPath];[路径 addArcWithCenter:self.circleCenter半径:self.circleRadius起始角度:0.0endAngle:M_PI * 2.0顺时针:是];返回路径;}- (void)handlePan:(UIPanGestureRecognizer *)gesture{静态 CGPoint oldCenter;if (gesture.state == UIGestureRecognizerStateBegan){//如果我们要开始平底锅,请确保我们在圆圈内.//所以,计算圆心和圆心之间的距离//手势开始位置,我们将其与//圆的半径.CGPoint location = [gesture locationInView:gesture.view];CGPoint 翻译 = [手势翻译InView:gesture.view];位置.x -= 翻译.x;位置.y -= 翻译.y;CGFloat x = location.x - self.circleCenter.x;CGFloat y = location.y - self.circleCenter.y;CGF 浮动距离 = sqrtf(x*x + y*y);//如果我们在圆圈外,取消手势.//如果我们在里面,跟踪圆圈的位置.oldCenter = self.circleCenter;}else if (gesture.state == UIGestureRecognizerStateChanged){//让我们通过添加//translationInView 到旧的圆心.CGPoint 翻译 = [手势翻译InView:gesture.view];CGPoint newCenter = CGPointMake(oldCenter.x + translation.x, oldCenter.y + translation.y);//CGPoint newCenter = [gesture locationInView:self.view];if (newCenter.x <160) {新中心.x = 160;}else if (newCenter.x > self.view.frame.size.width - 160) {newCenter.x = self.view.frame.size.width - 160;}if (newCenter.y < 242) {新中心.y = 242;}else if (newCenter.y > self.view.frame.size.height - imageMain.center.y) {newCenter.y = self.view.frame.size.height - imageMain.center.y;}//更新我们的 CAShapeLayer 的路径//self.circleLayer.path = [[self makeCircleAtLocation:newCenter radius:self.circleRadius] CGPath];imageCircle.center = newCenter;}}@结尾

结果是

解决方案

要保存蒙版图像,可以使用

这是一个示例实现:

//ViewController.swift//CircleMaskDemo////由 Robert Ryan 于 2018 年 4 月 18 日创建.//版权所有 © 2018-2022 罗伯特·瑞安.版权所有.//导入 UIKit类视图控制器:UIViewController {@IBOutlet 弱 var imageView:UIImageView!@IBOutlet 弱变量捏:UIPinchGestureRecognizer!@IBOutlet 弱 var pan:UIPanGestureRecognizer!私有让 maskLayer = CAShapeLayer()私有惰性变量半径:CGFloat = min(view.bounds.width, view.bounds.height) * 0.3私有惰性变量中心:CGPoint = CGPoint(x: view.bounds.midX, y: view.bounds.midY)私有让 pathLayer: CAShapeLayer = {让 _pathLayer = CAShapeLayer()_pathLayer.fillColor = UIColor.clear.cgColor_pathLayer.strokeColor = UIColor.black.cgColor_pathLayer.lineWidth = 3返回_pathLayer}()覆盖 func viewDidLoad() {super.viewDidLoad()imageView.layer.addSublayer(pathLayer)imageView.layer.mask = maskLayerimageView.isUserInteractionEnabled = trueimageView.addGestureRecognizer(捏)imageView.addGestureRecognizer(pan)}覆盖 func viewDidAppear(_ animated: Bool) {super.viewDidAppear(动画)updateCirclePath(在:中心,半径:半径)}私人 var oldCenter:CGPoint!私有变量 oldRadius:CGFloat!}//MARK: - 动作扩展视图控制器 {@IBAction func handlePinch(_ 手势:UIPinchGestureRecognizer) {让比例=手势.比例如果gesture.state == .began { oldRadius = radius }updateCirclePath(at: center, radius: oldRadius * scale)}@IBAction func handlePan(_ 手势: UIPanGestureRecognizer) {让翻译 = 手势.翻译(在:手势.view)如果gesture.state == .began { oldCenter = center }让 newCenter = CGPoint(x: oldCenter.x + translation.x, y: oldCenter.y + translation.y)updateCirclePath(在:newCenter,半径:半径)}@IBAction func handleTap(_ 手势:UITapGestureRecognizer) {让 fileURL = 试试!文件管理器.default.url(for: .documentDirectory, in: .userDomainMask, properFor: nil, create: true).appendingPathComponent("image.png")让 scale = imageView.window!.screen.scale让半径 = self.radius * 比例让 center = CGPoint(x: self.center.x * scale, y: self.center.y * scale)让 frame = CGRect(x: center.x - 半径,y: center.y - 半径,宽度:半径 * 2.0,高度:半径 * 2.0)//暂时移除circleLayer让 saveLayer = pathLayersaveLayer.removeFromSuperlayer()//渲染裁剪后的图像让 image = UIGraphicsImageRenderer(size: imageView.frame.size).image { _ inimageView.drawHierarchy(in: imageView.bounds, afterScreenUpdates: true)}//添加circleLayerimageView.layer.addSublayer(saveLayer)//裁剪图像警卫让 imageRef = image.cgImage?.cropping(to: frame),让数据 = UIImage(cgImage: imageRef).pngData()别的 {返回}//保存图片尝试?data.write(到:fileURL)//告诉用户我们完成了let alert = UIAlertController(title: nil, message: "已保存", preferredStyle: .alert)alert.addAction(UIAlertAction(title: "OK", style: .default))存在(警报,动画:真)}}//MARK: - 私有实用方法私有扩展 ViewController {func updateCirclePath(在中心:CGPoint,半径:CGFloat){self.center = 中心self.radius = 半径let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, 顺时针: true)maskLayer.path = path.cgPathpathLayer.path = path.cgPath}}//MARK: - UIGestureRecognizerDelegate扩展 ViewController: UIGestureRecognizerDelegate {funcgestureRecognizer(_gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) ->布尔 {让 tuple = (gestureRecognizer, otherGestureRecognizer)返回元组 == (pan, pinch) ||元组==(捏,平移)}}

如果你不想在圆圈周围画边框,那就更简单了,因为你可以拉出任何与 circleLayer 相关的东西.

如果您对 Objective-C 示例感兴趣,请参阅此答案的上一版本.

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

And the result is

解决方案

To save the masked image, one would use drawHierarchy(in:afterScreenUpdates:). You might also want to crop the image with cropping(to:). See my handleTap below for an example.

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:

Here is a sample implementation:

//  ViewController.swift
//  CircleMaskDemo
//
//  Created by Robert Ryan on 4/18/18.
//  Copyright © 2018-2022 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
        imageView.isUserInteractionEnabled = true
        imageView.addGestureRecognizer(pinch)
        imageView.addGestureRecognizer(pan)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        updateCirclePath(at: center, radius: radius)
    }

    private var oldCenter: CGPoint!
    private var oldRadius: CGFloat!
}

// MARK: - Actions

extension ViewController {
    @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

        let image = UIGraphicsImageRenderer(size: imageView.frame.size).image { _ in
            imageView.drawHierarchy(in: imageView.bounds, afterScreenUpdates: true)
        }

        // add the circleLayer back

        imageView.layer.addSublayer(saveLayer)

        // crop the image

        guard
            let imageRef = image.cgImage?.cropping(to: frame),
            let data = UIImage(cgImage: imageRef).pngData()
        else {
            return
        }

        // save the image

        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)
    }
}

// MARK: - Private utility methods

private extension ViewController {
    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
    }
}

// MARK: - UIGestureRecognizerDelegate

extension ViewController: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        let tuple = (gestureRecognizer, otherGestureRecognizer)
        return tuple == (pan, pinch) || tuple == (pinch, pan)
    }
}

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.

If you're interested in Objective-C example, see previous revision of this answer.

这篇关于如何在 iOS 的 UIImageView 中裁剪圆圈内的图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆