mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
546 lines
18 KiB
Objective-C
546 lines
18 KiB
Objective-C
#import "TGPhotoEditorCurvesToolView.h"
|
|
|
|
#import "LegacyComponentsInternal.h"
|
|
#import "TGFont.h"
|
|
#import "TGImageUtils.h"
|
|
|
|
typedef enum
|
|
{
|
|
TGCurvesSegmentNone,
|
|
TGCurvesSegmentBlacks,
|
|
TGCurvesSegmentShadows,
|
|
TGCurvesSegmentMidtones,
|
|
TGCurvesSegmentHighlights,
|
|
TGCurvesSegmentWhites
|
|
} TGCurvesSegment;
|
|
|
|
@interface TGPhotoEditorCurvesToolView () <UIGestureRecognizerDelegate>
|
|
{
|
|
TGCurvesSegment _activeSegment;
|
|
|
|
UILabel *_blacksLevelLabel;
|
|
UILabel *_shadowsLevelLabel;
|
|
UILabel *_midtonesLevelLabel;
|
|
UILabel *_highlightsLevelLabel;
|
|
UILabel *_whitesLevelLabel;
|
|
|
|
UIView *_selectionView;
|
|
|
|
UILongPressGestureRecognizer *_pressGestureRecognizer;
|
|
UIPanGestureRecognizer *_panGestureRecognizer;
|
|
UITapGestureRecognizer *_doubleTapGestureRecognizer;
|
|
|
|
NSArray *_interpolatedCurveValues;
|
|
CAShapeLayer *_curveLayer;
|
|
bool _appeared;
|
|
}
|
|
|
|
@property (nonatomic, weak) PGCurvesTool *curvesTool;
|
|
|
|
@end
|
|
|
|
@implementation TGPhotoEditorCurvesToolView
|
|
|
|
@synthesize valueChanged = _valueChanged;
|
|
@synthesize value = _value;
|
|
@synthesize interactionBegan = _interactionBegan;
|
|
@synthesize interactionEnded = _interactionEnded;
|
|
@synthesize actualAreaSize;
|
|
@synthesize isLandscape;
|
|
@synthesize toolbarLandscapeSize;
|
|
|
|
- (instancetype)initWithEditorItem:(id<PGPhotoEditorItem>)__unused editorItem
|
|
{
|
|
self = [self initWithFrame:CGRectZero];
|
|
if (self != nil)
|
|
{
|
|
self.backgroundColor = [UIColor clearColor];
|
|
self.contentMode = UIViewContentModeRedraw;
|
|
|
|
_activeSegment = TGCurvesSegmentNone;
|
|
|
|
_selectionView = [[UIView alloc] initWithFrame:CGRectZero];
|
|
_selectionView.alpha = 0.0f;
|
|
_selectionView.backgroundColor = [UIColor whiteColor];
|
|
_selectionView.userInteractionEnabled = false;
|
|
//[self addSubview:_selectionView];
|
|
|
|
_blacksLevelLabel = [self _levelLabel];
|
|
_blacksLevelLabel.text = @"0.00";
|
|
[self addSubview:_blacksLevelLabel];
|
|
|
|
_shadowsLevelLabel = [self _levelLabel];
|
|
_shadowsLevelLabel.text = @"0.00";
|
|
[self addSubview:_shadowsLevelLabel];
|
|
|
|
_midtonesLevelLabel = [self _levelLabel];
|
|
_midtonesLevelLabel.text = @"0.00";
|
|
[self addSubview:_midtonesLevelLabel];
|
|
|
|
_highlightsLevelLabel = [self _levelLabel];
|
|
_highlightsLevelLabel.text = @"0.00";
|
|
[self addSubview:_highlightsLevelLabel];
|
|
|
|
_whitesLevelLabel = [self _levelLabel];
|
|
_whitesLevelLabel.text = @"0.00";
|
|
[self addSubview:_whitesLevelLabel];
|
|
|
|
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
|
|
_panGestureRecognizer.delegate = self;
|
|
[self addGestureRecognizer:_panGestureRecognizer];
|
|
|
|
_pressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePress:)];
|
|
_pressGestureRecognizer.delegate = self;
|
|
_pressGestureRecognizer.minimumPressDuration = 0.1f;
|
|
[self addGestureRecognizer:_pressGestureRecognizer];
|
|
|
|
_doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
|
|
_doubleTapGestureRecognizer.numberOfTapsRequired = 2;
|
|
[self addGestureRecognizer:_doubleTapGestureRecognizer];
|
|
|
|
if ([editorItem isKindOfClass:[PGCurvesTool class]])
|
|
{
|
|
PGCurvesTool *curvesTool = (PGCurvesTool *)editorItem;
|
|
self.curvesTool = curvesTool;
|
|
[self setValue:editorItem.value];
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (UILabel *)_levelLabel
|
|
{
|
|
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
|
|
label.backgroundColor = [UIColor clearColor];
|
|
label.font = [TGFont systemFontOfSize:13];
|
|
label.textAlignment = NSTextAlignmentCenter;
|
|
label.textColor = [UIColor colorWithWhite:1.0f alpha:0.75f];
|
|
return label;
|
|
}
|
|
|
|
- (void)setValue:(id)value
|
|
{
|
|
if (![value isKindOfClass:[PGCurvesToolValue class]])
|
|
return;
|
|
|
|
_value = value;
|
|
|
|
[self updateCurve];
|
|
[self updateValueLabels];
|
|
}
|
|
|
|
- (void)updateCurve
|
|
{
|
|
if (CGRectEqualToRect([self _actualArea], CGRectZero))
|
|
return;
|
|
|
|
PGCurvesToolValue *curvesToolValue = (PGCurvesToolValue *)_value;
|
|
|
|
PGCurvesValue *curvesValue = nil;
|
|
switch (curvesToolValue.activeType)
|
|
{
|
|
case PGCurvesTypeLuminance:
|
|
curvesValue = curvesToolValue.luminanceCurve;
|
|
break;
|
|
|
|
case PGCurvesTypeRed:
|
|
curvesValue = curvesToolValue.redCurve;
|
|
break;
|
|
|
|
case PGCurvesTypeGreen:
|
|
curvesValue = curvesToolValue.greenCurve;
|
|
break;
|
|
|
|
case PGCurvesTypeBlue:
|
|
curvesValue = curvesToolValue.blueCurve;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
NSArray *points = [curvesValue interpolateCurve];
|
|
[self renderCurveWithPoints:points color:[TGPhotoEditorCurvesToolView colorForCurveType:curvesToolValue.activeType] lineWidth:2];
|
|
}
|
|
|
|
- (void)updateValueLabels
|
|
{
|
|
PGCurvesToolValue *curvesToolValue = (PGCurvesToolValue *)_value;
|
|
|
|
PGCurvesValue *curvesValue = nil;
|
|
switch (curvesToolValue.activeType)
|
|
{
|
|
case PGCurvesTypeLuminance:
|
|
curvesValue = curvesToolValue.luminanceCurve;
|
|
break;
|
|
|
|
case PGCurvesTypeRed:
|
|
curvesValue = curvesToolValue.redCurve;
|
|
break;
|
|
|
|
case PGCurvesTypeGreen:
|
|
curvesValue = curvesToolValue.greenCurve;
|
|
break;
|
|
|
|
case PGCurvesTypeBlue:
|
|
curvesValue = curvesToolValue.blueCurve;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_blacksLevelLabel.text = [NSString stringWithFormat:@"%0.2f", curvesValue.blacksLevel / 100.0f];
|
|
_shadowsLevelLabel.text = [NSString stringWithFormat:@"%0.2f", curvesValue.shadowsLevel / 100.0f];
|
|
_midtonesLevelLabel.text = [NSString stringWithFormat:@"%0.2f", curvesValue.midtonesLevel / 100.0f];
|
|
_highlightsLevelLabel.text = [NSString stringWithFormat:@"%0.2f", curvesValue.highlightsLevel / 100.0f];
|
|
_whitesLevelLabel.text = [NSString stringWithFormat:@"%0.2f", curvesValue.whitesLevel / 100.0f];
|
|
}
|
|
|
|
+ (UIColor *)colorForCurveType:(PGCurvesType)curveType
|
|
{
|
|
switch (curveType)
|
|
{
|
|
case PGCurvesTypeLuminance:
|
|
return [UIColor whiteColor];
|
|
|
|
case PGCurvesTypeRed:
|
|
return UIColorRGB(0xed3d4c);
|
|
|
|
case PGCurvesTypeGreen:
|
|
return UIColorRGB(0x10ee9d);
|
|
|
|
case PGCurvesTypeBlue:
|
|
return UIColorRGB(0x3377fb);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (bool)buttonPressed:(bool)__unused cancelButton
|
|
{
|
|
return true;
|
|
}
|
|
|
|
- (bool)isTracking
|
|
{
|
|
return false;
|
|
}
|
|
|
|
- (void)handlePress:(UILongPressGestureRecognizer *)gestureRecognizer
|
|
{
|
|
switch (gestureRecognizer.state)
|
|
{
|
|
case UIGestureRecognizerStateBegan:
|
|
[self selectSegmentWithPoint:[gestureRecognizer locationInView:gestureRecognizer.view]];
|
|
break;
|
|
|
|
case UIGestureRecognizerStateEnded:
|
|
case UIGestureRecognizerStateCancelled:
|
|
case UIGestureRecognizerStateFailed:
|
|
[self unselectSegments];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer
|
|
{
|
|
switch (gestureRecognizer.state)
|
|
{
|
|
case UIGestureRecognizerStateBegan:
|
|
[self selectSegmentWithPoint:[gestureRecognizer locationInView:gestureRecognizer.view]];
|
|
break;
|
|
|
|
case UIGestureRecognizerStateChanged:
|
|
{
|
|
PGCurvesToolValue *newValue = [(PGCurvesToolValue *)_value copy];
|
|
|
|
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
|
|
CGFloat delta = MIN(2, -1 * translation.y / 8.0f);
|
|
|
|
PGCurvesValue *curveValue = nil;
|
|
switch (newValue.activeType)
|
|
{
|
|
case PGCurvesTypeLuminance:
|
|
curveValue = newValue.luminanceCurve;
|
|
break;
|
|
|
|
case PGCurvesTypeRed:
|
|
curveValue = newValue.redCurve;
|
|
break;
|
|
|
|
case PGCurvesTypeGreen:
|
|
curveValue = newValue.greenCurve;
|
|
break;
|
|
|
|
case PGCurvesTypeBlue:
|
|
curveValue = newValue.blueCurve;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (_activeSegment)
|
|
{
|
|
case TGCurvesSegmentBlacks:
|
|
curveValue.blacksLevel = MAX(0, MIN(100, curveValue.blacksLevel + delta));
|
|
break;
|
|
|
|
case TGCurvesSegmentShadows:
|
|
curveValue.shadowsLevel = MAX(0, MIN(100, curveValue.shadowsLevel + delta));
|
|
break;
|
|
|
|
case TGCurvesSegmentMidtones:
|
|
curveValue.midtonesLevel = MAX(0, MIN(100, curveValue.midtonesLevel + delta));
|
|
break;
|
|
|
|
case TGCurvesSegmentHighlights:
|
|
curveValue.highlightsLevel = MAX(0, MIN(100, curveValue.highlightsLevel + delta));
|
|
break;
|
|
|
|
case TGCurvesSegmentWhites:
|
|
curveValue.whitesLevel = MAX(0, MIN(100, curveValue.whitesLevel + delta));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_value = newValue;
|
|
|
|
[self updateCurve];
|
|
[self updateValueLabels];
|
|
|
|
if (self.valueChanged != nil)
|
|
self.valueChanged(newValue, false);
|
|
|
|
[gestureRecognizer setTranslation:CGPointZero inView:gestureRecognizer.view];
|
|
}
|
|
break;
|
|
|
|
case UIGestureRecognizerStateEnded:
|
|
case UIGestureRecognizerStateCancelled:
|
|
case UIGestureRecognizerStateFailed:
|
|
{
|
|
[self unselectSegments];
|
|
|
|
if (self.interactionEnded != nil)
|
|
self.interactionEnded();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)handleDoubleTap:(UITapGestureRecognizer *)__unused gestureRecognizer
|
|
{
|
|
PGCurvesToolValue *value = [_value copy];
|
|
if (value == nil)
|
|
return;
|
|
|
|
switch (value.activeType) {
|
|
case PGCurvesTypeLuminance:
|
|
value.luminanceCurve = [PGCurvesValue defaultValue];
|
|
break;
|
|
|
|
case PGCurvesTypeRed:
|
|
value.redCurve = [PGCurvesValue defaultValue];
|
|
break;
|
|
|
|
case PGCurvesTypeGreen:
|
|
value.greenCurve = [PGCurvesValue defaultValue];
|
|
break;
|
|
|
|
case PGCurvesTypeBlue:
|
|
value.blueCurve = [PGCurvesValue defaultValue];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_value = value;
|
|
|
|
[self updateCurve];
|
|
[self updateValueLabels];
|
|
|
|
self.valueChanged(value, false);
|
|
}
|
|
|
|
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
|
{
|
|
if (gestureRecognizer == _pressGestureRecognizer || gestureRecognizer == _panGestureRecognizer)
|
|
{
|
|
CGPoint location = [gestureRecognizer locationInView:self];
|
|
CGRect actualArea = [self _actualArea];
|
|
|
|
return CGRectContainsPoint(actualArea, location);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
|
{
|
|
if (gestureRecognizer == _pressGestureRecognizer || otherGestureRecognizer == _pressGestureRecognizer)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
- (void)selectSegmentWithPoint:(CGPoint)point
|
|
{
|
|
if (_activeSegment != TGCurvesSegmentNone)
|
|
return;
|
|
|
|
CGRect actualArea = [self _actualArea];
|
|
CGFloat segmentWidth = actualArea.size.width / 5.0f;
|
|
|
|
point = CGPointMake(point.x - actualArea.origin.x, point.y - actualArea.origin.y);
|
|
|
|
_activeSegment = (TGCurvesSegment)(floor(point.x / segmentWidth) + 1);
|
|
|
|
[UIView animateWithDuration:0.2f animations:^
|
|
{
|
|
_selectionView.alpha = 0.11f;
|
|
}];
|
|
|
|
_selectionView.frame = CGRectMake(actualArea.origin.x + (_activeSegment - 1) * segmentWidth, actualArea.origin.y, segmentWidth, actualArea.size.height);
|
|
}
|
|
|
|
- (void)unselectSegments
|
|
{
|
|
if (_activeSegment == TGCurvesSegmentNone)
|
|
return;
|
|
|
|
_activeSegment = TGCurvesSegmentNone;
|
|
|
|
[UIView animateWithDuration:0.3f animations:^
|
|
{
|
|
_selectionView.alpha = 0.0f;
|
|
}];
|
|
}
|
|
|
|
- (void)drawRect:(CGRect)__unused rect
|
|
{
|
|
CGRect actualArea = [self _actualArea];
|
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
|
|
CGFloat segmentWidth = actualArea.size.width / 5.0f;
|
|
|
|
CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:1.0f alpha:0.6f].CGColor);
|
|
CGContextSetLineWidth(context, 1 - TGRetinaPixel);
|
|
|
|
for (NSUInteger i = 0; i < 4; i++)
|
|
{
|
|
CGContextMoveToPoint(context, actualArea.origin.x + segmentWidth + i * segmentWidth, actualArea.origin.y);
|
|
CGContextAddLineToPoint(context, actualArea.origin.x + segmentWidth + i * segmentWidth, actualArea.origin.y + actualArea.size.height);
|
|
CGContextStrokePath(context);
|
|
}
|
|
|
|
CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:1.0f alpha:0.6f].CGColor);
|
|
CGContextSetLineWidth(context, 1.5f);
|
|
|
|
CGFloat lengths[] = { 7, 4 };
|
|
CGContextSetLineDash(context, 0.0, lengths, 2);
|
|
|
|
CGContextMoveToPoint(context, actualArea.origin.x, actualArea.origin.y + actualArea.size.height);
|
|
CGContextAddLineToPoint(context, actualArea.origin.x + actualArea.size.width, actualArea.origin.y );
|
|
CGContextStrokePath(context);
|
|
|
|
CGContextSetLineDash(context, 0.0, NULL, 0);
|
|
}
|
|
|
|
- (CGRect)_actualArea
|
|
{
|
|
return CGRectMake((self.frame.size.width - self.actualAreaSize.width) / 2, (self.frame.size.height - self.actualAreaSize.height) / 2, self.actualAreaSize.width, self.actualAreaSize.height);
|
|
}
|
|
|
|
- (CGPoint)_viewPointWithPoint:(CGPoint)point actualArea:(CGRect)actualArea
|
|
{
|
|
return CGPointMake(point.x * actualArea.size.width, (1.0 - point.y) * actualArea.size.height);
|
|
}
|
|
|
|
- (void)renderCurveWithPoints:(NSArray *)points color:(UIColor *)color lineWidth:(CGFloat)lineWidth
|
|
{
|
|
UIBezierPath *path = [UIBezierPath bezierPath];
|
|
CGRect actualArea = [self _actualArea];
|
|
|
|
[points enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, __unused BOOL *stop)
|
|
{
|
|
CGPoint point = [self _viewPointWithPoint:value.CGPointValue actualArea:actualArea];
|
|
|
|
if (index == 0)
|
|
[path moveToPoint:point];
|
|
else
|
|
[path addLineToPoint:point];
|
|
}];
|
|
|
|
if (_curveLayer == nil)
|
|
{
|
|
_curveLayer = [[CAShapeLayer alloc] init];
|
|
_curveLayer.fillColor = [UIColor clearColor].CGColor;
|
|
_curveLayer.lineWidth = lineWidth;
|
|
[_curveLayer setLineCap:kCALineCapRound];
|
|
[self.layer addSublayer:_curveLayer];
|
|
}
|
|
|
|
[UIView performWithoutAnimation:^
|
|
{
|
|
_curveLayer.strokeColor = color.CGColor;
|
|
_curveLayer.path = [path CGPath];
|
|
_curveLayer.frame = CGRectMake(actualArea.origin.x, actualArea.origin.y, actualArea.size.width, actualArea.size.height);
|
|
}];
|
|
}
|
|
|
|
- (void)transitionIn
|
|
{
|
|
_appeared = true;
|
|
self.alpha = 0.0f;
|
|
|
|
[UIView animateWithDuration:0.2f animations:^
|
|
{
|
|
self.alpha = 1.0f;
|
|
}];
|
|
}
|
|
|
|
- (void)layoutSubviews
|
|
{
|
|
CGRect actualArea = [self _actualArea];
|
|
CGFloat segmentWidth = actualArea.size.width / 5.0f;
|
|
|
|
CGFloat bottomOffset = 4.0f;
|
|
|
|
[_blacksLevelLabel sizeToFit];
|
|
_blacksLevelLabel.frame = CGRectMake(actualArea.origin.x, actualArea.origin.y + actualArea.size.height - ceil(_blacksLevelLabel.frame.size.height) - bottomOffset, segmentWidth, ceil(_blacksLevelLabel.frame.size.height));
|
|
|
|
[_shadowsLevelLabel sizeToFit];
|
|
_shadowsLevelLabel.frame = CGRectMake(actualArea.origin.x + segmentWidth, actualArea.origin.y + actualArea.size.height - ceil(_shadowsLevelLabel.frame.size.height) - bottomOffset, segmentWidth, ceil(_shadowsLevelLabel.frame.size.height));
|
|
|
|
[_midtonesLevelLabel sizeToFit];
|
|
_midtonesLevelLabel.frame = CGRectMake(actualArea.origin.x + segmentWidth * 2, actualArea.origin.y + actualArea.size.height - ceil(_midtonesLevelLabel.frame.size.height) - bottomOffset, segmentWidth, ceil(_midtonesLevelLabel.frame.size.height));
|
|
|
|
[_highlightsLevelLabel sizeToFit];
|
|
_highlightsLevelLabel.frame = CGRectMake(actualArea.origin.x + segmentWidth * 3, actualArea.origin.y + actualArea.size.height - ceil(_highlightsLevelLabel.frame.size.height) - bottomOffset, segmentWidth, ceil(_highlightsLevelLabel.frame.size.height));
|
|
|
|
[_whitesLevelLabel sizeToFit];
|
|
_whitesLevelLabel.frame = CGRectMake(actualArea.origin.x + segmentWidth * 4, actualArea.origin.y + actualArea.size.height - ceil(_whitesLevelLabel.frame.size.height) - bottomOffset, segmentWidth, ceil(_whitesLevelLabel.frame.size.height));
|
|
|
|
_curveLayer.frame = CGRectMake(actualArea.origin.x, actualArea.origin.y, actualArea.size.width, actualArea.size.height);
|
|
|
|
[self updateCurve];
|
|
|
|
[self updateValueLabels];
|
|
if (!_appeared)
|
|
[self transitionIn];
|
|
}
|
|
|
|
@end
|