IOS crop tool

Time:2020-1-16

download

Demo and tool download link spcliptool

Instructions

[[SPClipTool shareClipTool] sp_clipOriginImage:pickerImage complete:^(UIImage * _Nonnull image) {
    //Get the image subsequent operation after clipping
}];

demand

Picture clipping, the effect is as shown in the figure below, support picture dragging, zooming, and free size change of clipping box.

IOS crop tool

thinking

Two uiimageviews, one as the background, plus the mask effect, the other through the mask control display area, and ensure that the two uiimageviews are completely overlapped when panning and zooming. Finally, I use a uiview to interact and draw three grid lines (I don’t know what it’s called in professional terms, a reference for screenshot, 2 / 3 ≈ 0.667 is close to the golden ratio of 0.618).

Be careful

  • Coordinate system conversion.
  • Flexible use of mask.
  • When dealing with gestures and drawing three grid lines, it is necessary to pay special attention to calculate the width and length of lines.

  • In order to enhance the user experience, when trimming the interactive design of the edge of the box, pay attention to increasing the user’s controllable range additionally.

Realization

  • Initialize two uiimageviews, one for background image view and one for clip image view. Drag gesture is added to clip image view.
- (void)setupImageView {
    // backgroudImageView 
    UIImageView *backgroudImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    backgroudImageView.contentMode = UIViewContentModeScaleAspectFit;
    backgroudImageView.image = self.originImage;
    [self.view addSubview:backgroudImageView];
    self.backgroudImageView = backgroudImageView;
    backgroudImageView.layer.mask = [[CALayer alloc] init];
    backgroudImageView.layer.mask.frame = backgroudImageView.bounds;
    backgroudImageView.layer.mask.backgroundColor = [UIColor colorWithWhite:1 alpha:0.5].CGColor;
    
    // clipImageView
    UIImageView *clipImageView = [[UIImageView alloc] initWithFrame:backgroudImageView.frame];
    clipImageView.userInteractionEnabled = YES;
    clipImageView.image = backgroudImageView.image;
    clipImageView.contentMode = backgroudImageView.contentMode;
    [self.view addSubview:clipImageView];
    self.clipImageView = clipImageView;
    clipImageView.layer.mask = [[CALayer alloc] init];
    clipImageView.layer.mask.backgroundColor = [UIColor whiteColor].CGColor;
    clipImageView.layer.mask.borderColor = [UIColor whiteColor].CGColor;
    clipImageView.layer.mask.borderWidth = 1;
    [clipImageView.layer.mask removeAllAnimations];
    
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(imagePan:)];
    [clipImageView addGestureRecognizer:panGesture];
}
  • Initialize spclipview for crop interaction
- (void)setupClipView {
    SPClipView *clipView = [[SPClipView alloc] init];
    clipView.backgroundColor = [UIColor clearColor];
    //Open the following two lines of comments to see the size of the real clipview.
//    clipView.layer.borderColor = [UIColor whiteColor].CGColor;
//    clipView.layer.borderWidth = 1;
    [self.view addSubview:clipView];
    self.clipView = clipView;
    //Get the real frame
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        clipView.frame = CGRectMake(0, 0, self.view.width / 1.5, self.view.height / 1.5);
          clipView.center = self.view.center;
        self.backgroudImageView.frame = self.view.bounds;
        self.clipImageView.frame = self.backgroudImageView.frame;
        [self dealMask];
    });

    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(clipPan:)];
    [clipView addGestureRecognizer:panGesture];
    
    UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchGestureAction:)];
    [self.view addGestureRecognizer:pinchGesture];
}
  • Gesture processing
#pragma mark- UIPanGestureRecognizer
- (void)clipPan:(UIPanGestureRecognizer *)panGesture {
    CGPoint point = [panGesture translationInView:self.clipView];
    self.clipView.origin = [self.clipView convertPoint:point toView:self.view];
    [self expandClipView:panGesture];
    [self dealGuideLine:panGesture];
    [self dealMask];
    [panGesture setTranslation:CGPointMake(0, 0) inView:self.view];
}

- (void)imagePan:(UIPanGestureRecognizer *)panGesture {
    CGPoint point = [panGesture translationInView:self.clipImageView];
    self.clipImageView.origin = [self.clipImageView convertPoint:point toView:self.view];
    self.backgroudImageView.center = self.clipImageView.center;
    [self dealGuideLine:panGesture];
    [self dealMask];
    [panGesture setTranslation:CGPointMake(0, 0) inView:self.view];
}

#pragma mark- UIPinchGestureRecognizer
- (void)pinchGestureAction:(UIPinchGestureRecognizer *)pinchGesture {
    switch (pinchGesture.state) {
        case UIGestureRecognizerStateBegan: {
            if (lastScale <= minScale) {
                lastScale = minScale;
            }else if (lastScale >= maxScale) {
                lastScale = maxScale;
            }
            self.clipImageViewCenter = self.clipImageView.center;
            self.clipView.showGuideLine = YES;
        }
        case UIGestureRecognizerStateChanged: {
            CGFloat currentScale = lastScale + pinchGesture.scale - 1;
            if (currentScale > minScale && currentScale < maxScale) {
                [self dealViewScale:currentScale];
            }
        }
            break;
        case UIGestureRecognizerStateEnded:
            lastScale += (pinchGesture.scale - 1);
            self.clipView.showGuideLine = NO;
            [self.clipView setNeedsDisplay];
        default:
            break;
    }
}

#pragma mark- Action
- (void)dealViewScale:(CGFloat)currentScale {
    self.clipImageView.width = currentScale * self.view.width;
    self.clipImageView.height = currentScale * self.view.height;
    self.clipImageView.center = self.clipImageViewCenter;
    self.backgroudImageView.frame = self.clipImageView.frame;
    self.backgroudImageView.layer.mask.frame = self.backgroudImageView.bounds;
    [self.backgroudImageView.layer.mask removeAllAnimations];
    [self dealMask];
}

- (void)expandClipView:(UIPanGestureRecognizer *)panGesture {
    CGPoint point = [panGesture translationInView:self.clipImageView];
    CGFloat margin = 60;
    CGFloat minValue = margin;
    if (panGesture.numberOfTouches) {
        CGPoint location = [panGesture locationOfTouch:0 inView:panGesture.view];
        if (location.x < margin) {
            self.clipView.width = MAX(self.clipView.width -= point.x, minValue);
        }
        if ((self.clipView.width - location.x) < margin) {
            self.clipView.frame = CGRectMake(self.clipView.x - point.x, self.clipView.y, self.clipView.width + point.x, self.clipView.height);
        }
        if (location.y < margin) {
            self.clipView.height = MAX(self.clipView.height -= point.y, minValue);
        }
        if ((self.clipView.height - location.y) < margin) {
            self.clipView.frame = CGRectMake(self.clipView.x , self.clipView.y - point.y, self.clipView.width, self.clipView.height + point.y);
        }
    }
}

- (void)dealGuideLine:(UIPanGestureRecognizer *)panGesture  {
    switch (panGesture.state) {
        case UIGestureRecognizerStateBegan:
            self.clipView.showGuideLine = YES;
            break;
        case UIGestureRecognizerStateEnded:
            self.clipView.showGuideLine = NO;
            break;
        default:
            break;
    }
}

- (void)dealMask {
    //Extra drag area for edge gesture experience
    CGFloat margin = 30;
    CGRect rect = [self.view convertRect:self.clipView.frame toView:self.clipImageView];
    self.clipImageView.layer.mask.frame = CGRectMake(rect.origin.x + margin, rect.origin.y + margin, rect.size.width - 2 * margin, rect.size.height - 2 * margin);
    [self.clipView setNeedsDisplay];
    [self.clipImageView.layer.mask removeAllAnimations];
}
  • KJNova Clipper
- (void)clipImage {
    
    CGSize size = self.view.bounds.size;
    UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
    [self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:NO];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    CGImageRef cgImage = [image CGImage];
    CGRect rect = [self.clipImageView convertRect:self.clipImageView.layer.mask.frame toView:self.view];
    
    //Border line width value
    CGFloat borderW = 1;
    CGImageRef cgClipImage = CGImageCreateWithImageInRect(cgImage, CGRectMake((rect.origin.x + borderW / 2) * image.scale, (rect.origin.y + borderW / 2) * image.scale, (rect.size.width - borderW) * image.scale, (rect.size.height - borderW) * image.scale));
    UIGraphicsEndImageContext();
    if (self.complete) {
        self.complete([UIImage imageWithCGImage:cgClipImage]);

    }
    [self dismissViewControllerAnimated:YES completion:nil];
}
  • Crop region drawing

    Here, I do not directly use the frame size of clipview for the rectangular box of the clipping area, but draw a rectangular box inside it. In order to make the user more flexible when adjusting the edge, otherwise, only when the finger is at the internal edge of the frame can the event of adjusting the size of the frame be triggered. As shown in the figure below, you can see the real size (outer frame) of clipview.

IOS crop tool

@implementation SPClipView

- (void)drawRect:(CGRect)rect {
    // Drawing code
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSetStrokeColorWithColor(currentContext, [UIColor whiteColor].CGColor);
    CGContextSetLineWidth(currentContext, 1);
    //Add extra drag area to enhance the edge gesture experience, which should be consistent with the margin in the - (void) dealmask; method above
    CGFloat margin = 30;
  
    //Draw rectangle
    CGContextAddRect(currentContext, CGRectMake(margin, margin, self.width - 2 * margin, self.height - 2 * margin));
    CGContextStrokePath(currentContext);
    
    //Draw the third line
    CGFloat maskW = self.width - 2 * margin;
    CGFloat maskH = self.height - 2 * margin;
    CGContextSetLineWidth(currentContext, 0.5);
    if (self.showGuideLine) {
        CGContextSetStrokeColorWithColor(currentContext, [UIColor whiteColor].CGColor);
    }else {
        CGContextSetStrokeColorWithColor(currentContext, [UIColor clearColor].CGColor);
    }
    CGContextMoveToPoint(currentContext, margin, maskH / 3 + margin);
    CGContextAddLineToPoint(currentContext, self.width - margin, maskH / 3 + margin);
    CGContextMoveToPoint(currentContext, margin, 2 / 3.0 * maskH + margin);
    CGContextAddLineToPoint(currentContext, self.width - margin, 2 / 3.0 * maskH + margin);
    
    CGContextMoveToPoint(currentContext, maskW / 3 + margin, margin);
    CGContextAddLineToPoint(currentContext, maskW  / 3+ margin, self.height - margin);
    CGContextMoveToPoint(currentContext, 2 / 3.0 * maskW + margin, margin);
    CGContextAddLineToPoint(currentContext, 2 / 3.0 * maskW + margin, self.height - margin);
    
    CGContextStrokePath(currentContext);
    
    //Draw four corners
    CGFloat cornerL = 15;
    CGFloat cornerLW = 2;
    //Actual length
    CGFloat cornerRL = cornerL + cornerLW;
    CGPoint originH = CGPointMake(margin - cornerLW, margin - cornerLW / 2);
    CGPoint originV = CGPointMake(margin - cornerLW / 2, margin - cornerLW);
    CGContextSetStrokeColorWithColor(currentContext, [UIColor whiteColor].CGColor);
    CGContextSetLineWidth(currentContext, cornerLW);
    
    // left up
    CGContextMoveToPoint(currentContext, originH.x, originH.y);
    CGContextAddLineToPoint(currentContext, originH.x + cornerRL, originH.y);
    CGContextMoveToPoint(currentContext, originV.x, originV.y);
    CGContextAddLineToPoint(currentContext, originV.x, originV.y + cornerRL);
    
    // left bottom
    CGContextMoveToPoint(currentContext, originH.x, originH.y + maskH + cornerLW);
    CGContextAddLineToPoint(currentContext, originH.x + cornerRL, originH.y + maskH + cornerLW);
    CGContextMoveToPoint(currentContext, originV.x, originV.y + maskH + 2 * cornerLW);
    CGContextAddLineToPoint(currentContext, originV.x, originV.y + maskH + 2 * cornerLW - cornerRL);
    
    // right up
    CGContextMoveToPoint(currentContext, originH.x + maskW + 2 * cornerLW, originH.y);
    CGContextAddLineToPoint(currentContext, originH.x + maskW + 2 * cornerLW - cornerRL, originH.y);
    CGContextMoveToPoint(currentContext, originV.x + maskW + cornerLW, originV.y);
    CGContextAddLineToPoint(currentContext, originV.x + maskW + cornerLW, originV.y + cornerRL);
    
    // right down
    CGContextMoveToPoint(currentContext, originH.x + maskW + 2 * cornerLW, originH.y + maskH + cornerLW);
    CGContextAddLineToPoint(currentContext, originH.x + maskW + 2 * cornerLW - cornerRL, originH.y + maskH + cornerLW);
    CGContextMoveToPoint(currentContext, originV.x + maskW + cornerLW, originV.y + maskH + 2 * cornerLW);
    CGContextAddLineToPoint(currentContext, originV.x + maskW + cornerLW, originV.y + maskH + 2 * cornerLW - cornerRL);

    CGContextStrokePath(currentContext);
}

Here, we must pay attention to the width of the line. The line has width, and the drawing path is in the center of the line.

IOS crop tool