How to crop image inside the circle in UIImageView in iOS

后端 未结 3 775
梦谈多话
梦谈多话 2020-12-04 08:03

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 trans

相关标签:
3条回答
  • 2020-12-04 08:36

    Same code reused to play with Square, it may be helpful for other.

    #import "ViewController.h"
    
    @interface ViewController () <UIGestureRecognizerDelegate>
    
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    
    @property (nonatomic) CGFloat circleRadius;
    @property (nonatomic) CGPoint circleCenter;
    @property (nonatomic) CGRect frame;
    
    
    @property (nonatomic, weak) CAShapeLayer *maskLayer;
    @property (nonatomic, weak) CAShapeLayer *circleLayer;
    
    @property (nonatomic, weak) UIPinchGestureRecognizer *pinch;
    @property (nonatomic, weak) UIPanGestureRecognizer   *pan;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        // create layer mask for the image
    
        CAShapeLayer *maskLayer = [CAShapeLayer layer];
        self.imageView.layer.mask = maskLayer;
        self.maskLayer = maskLayer;
    
        // create shape layer for circle we'll draw on top of image (the boundary of the circle)
    
        CAShapeLayer *circleLayer = [CAShapeLayer layer];
        circleLayer.lineWidth = 3.0;
        circleLayer.fillColor = [[UIColor clearColor] CGColor];
        circleLayer.strokeColor = [[UIColor blackColor] CGColor];
        [self.imageView.layer addSublayer:circleLayer];
        self.circleLayer = circleLayer;
    
        // create circle path
    
        [self updateCirclePathAtLocation:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0) radius:self.view.bounds.size.width * 0.30];
    
        // create pan gesture
    
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
        pan.delegate = self;
        [self.imageView addGestureRecognizer:pan];
        self.imageView.userInteractionEnabled = YES;
        self.pan = pan;
    
        // create pan gesture
    
        UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
        pinch.delegate = self;
        [self.view addGestureRecognizer:pinch];
        self.pinch = pinch;
    }
    
    - (void)updateCirclePathAtLocation:(CGPoint)location radius:(CGFloat)radius
    {
        self.circleCenter = location;
        self.circleRadius = radius;
        self.frame =CGRectMake(self.circleCenter.x/2, self.circleCenter.y/2, self.circleRadius, self.circleRadius);
        UIBezierPath *path =     [UIBezierPath bezierPathWithRoundedRect:self.frame cornerRadius:0];
    //    [path addArcWithCenter:self.circleCenter
    //                    radius:self.circleRadius
    //                startAngle:0.0
    //                  endAngle:M_PI * 2.0
    //                 clockwise:YES];
    
    
        self.maskLayer.path = [path CGPath];
        self.circleLayer.path = [path CGPath];
    }
    
    - (IBAction)didTouchUpInsideSaveButton:(id)sender
    {
        NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
        NSString *path = [documentsPath stringByAppendingPathComponent:@"image.png"];
    
        CGFloat scale  = [[self.imageView.window screen] scale];
    
        CGRect frame = CGRectMake(self.frame.origin.x *scale,
                                  self.frame.origin.y *scale,
                                  self.frame.size.width*scale,
                                  self.frame.size.width*scale);
    
        // temporarily remove the circleLayer
    
        CALayer *circleLayer = self.circleLayer;
        [self.circleLayer removeFromSuperlayer];
    
        // render the clipped image
    
        UIGraphicsBeginImageContextWithOptions(self.imageView.frame.size, NO, 0.0);
        CGContextRef context = UIGraphicsGetCurrentContext();
        if ([self.imageView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)])
        {
            // if iOS 7, just draw it
    
            [self.imageView drawViewHierarchyInRect:self.imageView.bounds afterScreenUpdates:YES];
        }
        else
        {
            // if pre iOS 7, manually clip it
    
            CGContextAddRect(context, self.frame);
            CGContextClip(context);
            [self.imageView.layer renderInContext:context];
        }
    
        // capture the image and close the context
    
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    
        // add the circleLayer back
    
        [self.imageView.layer addSublayer:circleLayer];
    
        // crop the image
        NSLog(@"circle fram %@",NSStringFromCGRect(frame));
        NSLog(@"self fram %@",NSStringFromCGRect(self.frame));
    
        CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], frame);
        UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
    
        // save the image
    
        NSData *data = UIImagePNGRepresentation(croppedImage);
        [data writeToFile:path atomically:YES];
    
        // tell the user we're done
    
        [[[UIAlertView alloc] initWithTitle:nil message:@"Saved" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show];
    }
    
    
    #pragma mark - Gesture recognizers
    
    - (void)handlePan:(UIPanGestureRecognizer *)gesture
    {
        static CGPoint oldCenter;
        CGPoint tranlation = [gesture translationInView:gesture.view];
    
        if (gesture.state == UIGestureRecognizerStateBegan)
        {
            oldCenter = self.circleCenter;
        }
    
        CGPoint newCenter = CGPointMake(oldCenter.x + tranlation.x, oldCenter.y + tranlation.y);
    
        [self updateCirclePathAtLocation:newCenter radius:self.circleRadius];
    }
    
    - (void)handlePinch:(UIPinchGestureRecognizer *)gesture
    {
        static CGFloat oldRadius;
        CGFloat scale = [gesture scale];
    
        if (gesture.state == UIGestureRecognizerStateBegan)
        {
            oldRadius = self.circleRadius;
        }
    
        CGFloat newRadius = oldRadius * scale;
    
        [self updateCirclePathAtLocation:self.circleCenter radius:newRadius];
    }
    
    
    #pragma mark - UIGestureRecognizerDelegate
    
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    {
        if ((gestureRecognizer == self.pan   && otherGestureRecognizer == self.pinch) ||
            (gestureRecognizer == self.pinch && otherGestureRecognizer == self.pan))
        {
            return YES;
        }
    
        return NO;
    }
    
    @end
    
    0 讨论(0)
  • 2020-12-04 08:42
    #import "ViewController.h"
    
    @interface ViewController ()
    
    
     @property (weak, nonatomic) IBOutlet UIImageView *imageView;
     @property (weak, nonatomic) IBOutlet UIImageView *crppedImageView;
    
    
     @property (nonatomic) CGFloat circleRadius;
     @property (nonatomic) CGPoint circleCenter;
    
     @property (nonatomic, weak) CAShapeLayer *maskLayer;
     @property (nonatomic, weak) CAShapeLayer *maskSubLayer;
     @property (nonatomic, weak) CAShapeLayer *circleLayer;
    
     @property (nonatomic, weak) UIPinchGestureRecognizer *pinch;
     @property (nonatomic, weak) UIPanGestureRecognizer   *pan;
    
    
    @end
    
    @implementation ViewController
    
    
     - (void)viewDidLoad
    {
     [super viewDidLoad];
    
    // create layer mask for the image
    
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    CGRect maskRect = self.imageView.frame;
    
    // Create a path with the rectangle in it.
    CGPathRef path = CGPathCreateWithRect(maskRect, NULL);
    
          // Set the path to the mask layer.
           maskLayer.path = path;
    
       //    maskLayer.fillRule = kCAFillRuleEvenOdd;
      //    maskLayer.fillColor = [UIColor blueColor].CGColor;
      //    maskLayer.opacity = 0.5;
    
    self.imageView.layer.mask = maskLayer;
    self.maskLayer = maskLayer;
    
    CAShapeLayer *maskLayer1 = [CAShapeLayer layer];
    CGRect maskRect1 = self.imageView.frame;
    
    // Create a path with the rectangle in it.
    CGPathRef path1 = CGPathCreateWithRect(maskRect1, NULL);
    
    // Set the path to the mask layer.
    maskLayer1.path = path1;
    
    [self.imageView.layer.mask addSublayer:maskLayer1];
    self.maskSubLayer = maskLayer1;
    
    
    // create shape layer for circle we'll draw on top of image (the boundary of the circle)
    
    CAShapeLayer *circleLayer = [CAShapeLayer layer];
    circleLayer.lineWidth = 3.0;
    circleLayer.fillColor = [[UIColor blueColor] CGColor];
    circleLayer.fillRule = kCAFillRuleEvenOdd;
    circleLayer.opacity = 0.5;
    circleLayer.strokeColor = [[UIColor blackColor] CGColor];
    [self.imageView.layer addSublayer:circleLayer];
    self.circleLayer = circleLayer;
    
    // create circle path
    
    [self updateCirclePathAtLocation:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0) radius:self.view.bounds.size.width * 0.30];
    
    // create pan gesture
    
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    pan.delegate = self;
    [self.imageView addGestureRecognizer:pan];
    self.imageView.userInteractionEnabled = YES;
    self.pan = pan;
    
    // create pan gesture
    
    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
    pinch.delegate = self;
    [self.view addGestureRecognizer:pinch];
    self.pinch = pinch;
     }
    
        - (void)updateCirclePathAtLocation:(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];
    
    UIBezierPath *path1 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.imageView.bounds.size.width, self.imageView.bounds.size.height) cornerRadius:0];
    [path1 appendPath:path];
    [path1 setUsesEvenOddFillRule:YES];
    
    
    self.maskSubLayer.path = [path1 CGPath];
        self.circleLayer.path = [path1 CGPath];
    
    }
    
        - (IBAction)didTouchUpInsideSaveButton:(id)sender
       {
    NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *path = [documentsPath stringByAppendingPathComponent:@"image.png"];
    
    CGFloat scale  = [[self.imageView.window screen] scale];
    CGFloat radius = self.circleRadius * scale;
    CGPoint center = CGPointMake(self.circleCenter.x * scale, self.circleCenter.y * scale);
    
    CGRect frame = CGRectMake(center.x - radius,
                              center.y - radius,
                              radius * 2.0,
                              radius * 2.0);
    
    // temporarily remove the circleLayer
    
    CALayer *circleLayer = self.circleLayer;
    [self.circleLayer removeFromSuperlayer];
    
    // render the clipped image
    
    UIGraphicsBeginImageContextWithOptions(self.imageView.frame.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    if ([self.imageView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)])
    {
        // if iOS 7, just draw it
    
        [self.imageView drawViewHierarchyInRect:self.imageView.bounds afterScreenUpdates:YES];
    }
    else
    {
        // if pre iOS 7, manually clip it
    
        CGContextAddArc(context, self.circleCenter.x, self.circleCenter.y, self.circleRadius, 0, M_PI * 2.0, YES);
        CGContextClip(context);
        [self.imageView.layer renderInContext:context];
    }
    
    // capture the image and close the context
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    // add the circleLayer back
    
    [self.imageView.layer addSublayer:circleLayer];
    
    // crop the image
    
    CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], frame);
    UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
    
    _crppedImageView.layer.cornerRadius = _crppedImageView.frame.size.height /2;
    _crppedImageView.layer.masksToBounds = YES;
    _crppedImageView.layer.borderWidth = 0;
    self.crppedImageView.image = croppedImage;
    // save the image
    
    NSData *data = UIImagePNGRepresentation(croppedImage);
    [data writeToFile:path atomically:YES];
    
    // tell the user we're done
    
    [[[UIAlertView alloc] initWithTitle:nil message:@"Saved" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show];
     }
    
    
        #pragma mark - Gesture recognizers
    
          - (void)handlePan:(UIPanGestureRecognizer *)gesture
     {
    static CGPoint oldCenter;
    CGPoint tranlation = [gesture translationInView:gesture.view];
    
    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        oldCenter = self.circleCenter;
    }
    
    CGPoint newCenter = CGPointMake(oldCenter.x + tranlation.x, oldCenter.y + tranlation.y);
    
    [self updateCirclePathAtLocation:newCenter radius:self.circleRadius];
     }
    
      - (void)handlePinch:(UIPinchGestureRecognizer *)gesture
     {
    static CGFloat oldRadius;
    CGFloat scale = [gesture scale];
    
    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        oldRadius = self.circleRadius;
    }
    
    CGFloat newRadius = oldRadius * scale;
    
    [self updateCirclePathAtLocation:self.circleCenter radius:newRadius];
    }
    
    
     #pragma mark - UIGestureRecognizerDelegate
    
        - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
       {
          if ((gestureRecognizer == self.pan   && otherGestureRecognizer == self.pinch) ||
        (gestureRecognizer == self.pinch && otherGestureRecognizer == self.pan))
    {
        return YES;
    }
    
    return NO;
     }
    
       - (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
     }
    
       @end
    
    0 讨论(0)
  • 2020-12-04 08:44

    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.

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

    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.

    0 讨论(0)
提交回复
热议问题