335 lines
13 KiB
Mathematica
335 lines
13 KiB
Mathematica
|
|
// Copyright (c) 2024 Tencent. All rights reserved.
|
|||
|
|
// Author: eddardliu
|
|||
|
|
|
|||
|
|
#import "TUIMultimediaCropView.h"
|
|||
|
|
|
|||
|
|
#import <ReactiveObjC/ReactiveObjC.h>
|
|||
|
|
#import <Masonry/Masonry.h>
|
|||
|
|
#import <TUICore/TUIThemeManager.h>
|
|||
|
|
|
|||
|
|
#define TOUCH_MAX_DIS 40
|
|||
|
|
#define CORNER_LINE_WIDTH 4.0
|
|||
|
|
#define CORNER_LENGTH 25.0
|
|||
|
|
#define CROP_RECT_BORDER_LINE_WIDTH 2.0
|
|||
|
|
#define CROP_RECT_GRID_LINE_WIDTH 0.8
|
|||
|
|
|
|||
|
|
@interface TUIMultimediaCropView()<UIGestureRecognizerDelegate, UIGestureRecognizerDelegate>{
|
|||
|
|
UIColor * _borderColor;
|
|||
|
|
UIColor* _backgroundColor;
|
|||
|
|
|
|||
|
|
NSDate * _lastShowTransparentBackgroundTime;
|
|||
|
|
NSDate * _lastShowGridTime;
|
|||
|
|
BOOL _isShowTransparentBackground;
|
|||
|
|
BOOL _isShowGird;
|
|||
|
|
|
|||
|
|
CGRect _limitRect;
|
|||
|
|
CGRect _cropRect;
|
|||
|
|
|
|||
|
|
CGFloat _isMoveLeft;
|
|||
|
|
CGFloat _isMoveRight;
|
|||
|
|
CGFloat _isMoveTop;
|
|||
|
|
CGFloat _isMoveBottom;
|
|||
|
|
}
|
|||
|
|
@end
|
|||
|
|
|
|||
|
|
@implementation TUIMultimediaCropView
|
|||
|
|
|
|||
|
|
- (instancetype)initWithFrame:(CGRect)frame {
|
|||
|
|
self = [super initWithFrame:frame];
|
|||
|
|
if (self) {
|
|||
|
|
self.backgroundColor = [UIColor clearColor];
|
|||
|
|
|
|||
|
|
_borderColor = [UIColor whiteColor];
|
|||
|
|
_backgroundColor = [UIColor blackColor];
|
|||
|
|
_isShowTransparentBackground = NO;
|
|||
|
|
_isShowGird = NO;
|
|||
|
|
|
|||
|
|
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
|
|||
|
|
panGesture.maximumNumberOfTouches = 1;
|
|||
|
|
panGesture.delegate = self;
|
|||
|
|
[self addGestureRecognizer:panGesture];
|
|||
|
|
|
|||
|
|
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
|
|||
|
|
[self addGestureRecognizer:tapGesture];
|
|||
|
|
|
|||
|
|
UIPinchGestureRecognizer *pinchRec = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(onPinch:)];
|
|||
|
|
[self addGestureRecognizer:pinchRec];
|
|||
|
|
pinchRec.delegate = self;
|
|||
|
|
}
|
|||
|
|
return self;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
-(CGRect)getCropRect {
|
|||
|
|
return _cropRect;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
-(void)reset {
|
|||
|
|
[self adjustCropRect:_preViewFrame];
|
|||
|
|
[self setNeedsDisplay];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
-(void)rotation90 {
|
|||
|
|
int centerX = CGRectGetMidX(_cropRect);
|
|||
|
|
int centerY = CGRectGetMidY(_cropRect);
|
|||
|
|
int width = _cropRect.size.width;
|
|||
|
|
int height = _cropRect.size.height;
|
|||
|
|
|
|||
|
|
_cropRect.origin.y = centerY - width / 2.0f;
|
|||
|
|
_cropRect.size.height = width;
|
|||
|
|
|
|||
|
|
_cropRect.origin.x = centerX - height / 2.0f;
|
|||
|
|
_cropRect.size.width = height;
|
|||
|
|
|
|||
|
|
[self adjustCropRect:_cropRect];
|
|||
|
|
[self setNeedsDisplay];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)layoutSubviews {
|
|||
|
|
[super layoutSubviews];
|
|||
|
|
_limitRect = CGRectMake(10, 50, self.frame.size.width - 20, self.frame.size.height - 160);
|
|||
|
|
_cropRect = _limitRect;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)onTap:(UITapGestureRecognizer *)gesture {
|
|||
|
|
[self showTransparentBackground];
|
|||
|
|
[self showGrid];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)onPinch:(UIPinchGestureRecognizer *)gestureRecognizer {
|
|||
|
|
[self showTransparentBackground];
|
|||
|
|
[self showGrid];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)onPan:(UIPanGestureRecognizer *)gesture {
|
|||
|
|
_backgroundColor = [UIColor clearColor];
|
|||
|
|
[self showTransparentBackground];
|
|||
|
|
[self showGrid];
|
|||
|
|
switch (gesture.state) {
|
|||
|
|
case UIGestureRecognizerStateBegan: {
|
|||
|
|
CGPoint p = [gesture locationInView:self];
|
|||
|
|
_isMoveLeft = fabs(p.x - _cropRect.origin.x) < TOUCH_MAX_DIS;
|
|||
|
|
_isMoveRight = fabs(p.x - CGRectGetMaxX(_cropRect)) < TOUCH_MAX_DIS;
|
|||
|
|
_isMoveTop = fabs(p.y - _cropRect.origin.y) < TOUCH_MAX_DIS;
|
|||
|
|
_isMoveBottom = fabs(p.y - CGRectGetMaxY(_cropRect)) < TOUCH_MAX_DIS;
|
|||
|
|
if (_isMoveTop || _isMoveLeft || _isMoveRight || _isMoveBottom) {
|
|||
|
|
[_delegate onStartCrop];
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case UIGestureRecognizerStateChanged: {
|
|||
|
|
CGPoint p = [gesture locationInView:self];
|
|||
|
|
if (_isMoveLeft && p.x > _limitRect.origin.x && p.x < CGRectGetMaxX(_cropRect)) {
|
|||
|
|
_cropRect.size.width += (_cropRect.origin.x - p.x);
|
|||
|
|
_cropRect.origin.x = p.x;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_isMoveRight && p.x > _cropRect.origin.x && p.x < CGRectGetMaxX(_limitRect)) {
|
|||
|
|
_cropRect.size.width = p.x - _cropRect.origin.x;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_isMoveTop && p.y > _limitRect.origin.y && p.y < CGRectGetMaxY(_cropRect)) {
|
|||
|
|
_cropRect.size.height += (_cropRect.origin.y - p.y);
|
|||
|
|
_cropRect.origin.y = p.y;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_isMoveBottom && p.y > _cropRect.origin.y && p.y < CGRectGetMaxY(_limitRect)) {
|
|||
|
|
_cropRect.size.height = p.y - _cropRect.origin.y;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case UIGestureRecognizerStateEnded: {
|
|||
|
|
if (_isMoveTop || _isMoveLeft || _isMoveRight || _isMoveBottom) {
|
|||
|
|
[self adjustCropRect:_cropRect];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
default:
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[gesture setTranslation:CGPointZero inView:self.superview];
|
|||
|
|
[self setNeedsDisplay];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
- (void) adjustCropRect:(CGRect) sourceCropRect {
|
|||
|
|
if (CGRectIsEmpty(sourceCropRect)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
float limitRectRation = _limitRect.size.width * 1.0f / _limitRect.size.height;
|
|||
|
|
float sourceRectRation = sourceCropRect.size.width * 1.0f / sourceCropRect.size.height;
|
|||
|
|
|
|||
|
|
if (sourceRectRation > limitRectRation) {
|
|||
|
|
_cropRect.origin.x = _limitRect.origin.x;
|
|||
|
|
_cropRect.size.width = _limitRect.size.width;
|
|||
|
|
_cropRect.size.height = _cropRect.size.width / sourceRectRation;
|
|||
|
|
_cropRect.origin.y = (_limitRect.size.height - _cropRect.size.height) / 2.0f + _limitRect.origin.y;
|
|||
|
|
} else {
|
|||
|
|
_cropRect.origin.y = _limitRect.origin.y;
|
|||
|
|
_cropRect.size.height = _limitRect.size.height;
|
|||
|
|
_cropRect.size.width = _cropRect.size.height * sourceRectRation;
|
|||
|
|
_cropRect.origin.x = (_limitRect.size.width - _cropRect.size.width) / 2.0f + _limitRect.origin.x;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CGFloat scale = _cropRect.size.width * 1.0f / sourceCropRect.size.width;
|
|||
|
|
CGFloat moveX = _cropRect.origin.x - sourceCropRect.origin.x;
|
|||
|
|
CGFloat moveY = _cropRect.origin.y - sourceCropRect.origin.y;
|
|||
|
|
|
|||
|
|
[_delegate onCropComplete:scale centerPoint:sourceCropRect.origin
|
|||
|
|
offset:CGPointMake(moveX, moveY)];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)drawRect:(CGRect)rect {
|
|||
|
|
[super drawRect:rect];
|
|||
|
|
|
|||
|
|
if (_cropRect.size.width == 0 || _cropRect.size.height == 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
|||
|
|
if (_isShowTransparentBackground) {
|
|||
|
|
[[UIColor clearColor] setFill];
|
|||
|
|
CGContextFillRect(context, rect);
|
|||
|
|
CGContextFillPath(context);
|
|||
|
|
} else {
|
|||
|
|
[[UIColor blackColor] setFill];
|
|||
|
|
CGContextFillRect(context, rect);
|
|||
|
|
CGContextAddRect(context, _cropRect);
|
|||
|
|
CGContextClip(context);
|
|||
|
|
CGContextSetBlendMode(context, kCGBlendModeClear);
|
|||
|
|
CGContextAddRect(context, _cropRect);
|
|||
|
|
CGContextFillPath(context);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CGContextSetBlendMode(context, kCGBlendModeNormal);
|
|||
|
|
[self drawCropRect:context];
|
|||
|
|
[self drawCorner:context];
|
|||
|
|
if (_isShowGird) {
|
|||
|
|
[self drawGrid:context];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void) drawCropRect:(CGContextRef) context {
|
|||
|
|
CGContextSetStrokeColorWithColor(context, _borderColor.CGColor);
|
|||
|
|
CGContextSetLineWidth(context, CROP_RECT_BORDER_LINE_WIDTH );
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
|||
|
|
|
|||
|
|
CGContextStrokePath(context);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void) drawCorner:(CGContextRef) context {
|
|||
|
|
float corner_width = CORNER_LINE_WIDTH;
|
|||
|
|
if (!_isShowTransparentBackground) {
|
|||
|
|
corner_width = 1.5f * corner_width;
|
|||
|
|
}
|
|||
|
|
CGContextSetLineWidth(context, corner_width);
|
|||
|
|
float halfLineWidth = corner_width / 2.0f;
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) - halfLineWidth, CGRectGetMinY(_cropRect));
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) - halfLineWidth);
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect) + halfLineWidth, CGRectGetMinY(_cropRect));
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) - halfLineWidth);
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMaxY(_cropRect) + halfLineWidth);
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect) + halfLineWidth, CGRectGetMaxY(_cropRect));
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMaxY(_cropRect) + halfLineWidth);
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) - halfLineWidth, CGRectGetMaxY(_cropRect));
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
|||
|
|
|
|||
|
|
CGContextStrokePath(context);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void) drawGrid:(CGContextRef) context {
|
|||
|
|
CGContextSetLineWidth(context, CROP_RECT_GRID_LINE_WIDTH);
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width / 3, CGRectGetMinY(_cropRect));
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width / 3, CGRectGetMaxY(_cropRect));
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width * 2 / 3, CGRectGetMinY(_cropRect));
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width * 2 / 3, CGRectGetMaxY(_cropRect));
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height / 3);
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height / 3);
|
|||
|
|
|
|||
|
|
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height * 2 / 3);
|
|||
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height * 2 / 3);
|
|||
|
|
|
|||
|
|
CGContextStrokePath(context);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
|||
|
|
return YES;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void) showTransparentBackground {
|
|||
|
|
_lastShowTransparentBackgroundTime = [NSDate date];
|
|||
|
|
if (!_isShowTransparentBackground) {
|
|||
|
|
_isShowTransparentBackground = YES;
|
|||
|
|
[self setNeedsDisplay];
|
|||
|
|
[self delayCancelShowTransparentBackground];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void) delayCancelShowTransparentBackground {
|
|||
|
|
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC));
|
|||
|
|
|
|||
|
|
@weakify(self)
|
|||
|
|
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
|||
|
|
@strongify(self)
|
|||
|
|
if ([[NSDate date] timeIntervalSinceDate:self->_lastShowTransparentBackgroundTime] < 2.0f) {
|
|||
|
|
[self delayCancelShowTransparentBackground];
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
self->_isShowTransparentBackground = NO;
|
|||
|
|
[self setNeedsDisplay];
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void) showGrid {
|
|||
|
|
_lastShowGridTime = [NSDate date];
|
|||
|
|
if (!_isShowGird) {
|
|||
|
|
_isShowGird = YES;
|
|||
|
|
[self setNeedsDisplay];
|
|||
|
|
[self delayCancelShowGrid];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void) delayCancelShowGrid {
|
|||
|
|
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC));
|
|||
|
|
|
|||
|
|
@weakify(self)
|
|||
|
|
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
|||
|
|
@strongify(self)
|
|||
|
|
if ([[NSDate date] timeIntervalSinceDate:self->_lastShowGridTime] < 4.0f) {
|
|||
|
|
[self delayCancelShowGrid];
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
self->_isShowGird = NO;
|
|||
|
|
[self setNeedsDisplay];
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@end
|