Files
featherVoice/TUIKit/TUIMultimediaPlugin/Edit/UI/CommonEditor/Views/TUIMultimediaCropView.m

335 lines
13 KiB
Mathematica
Raw Normal View History

2025-08-08 10:49:36 +08:00
// 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