mirror of
https://github.com/danog/Telegram.git
synced 2024-12-12 17:37:33 +01:00
345 lines
10 KiB
Objective-C
345 lines
10 KiB
Objective-C
#import "PGCurvesTool.h"
|
|
#import "TGPhotoEditorCurvesToolView.h"
|
|
#import "TGPhotoEditorCurvesHistogramView.h"
|
|
|
|
const NSUInteger PGCurveGranularity = 100;
|
|
const NSUInteger PGCurveDataStep = 2;
|
|
|
|
@interface PGCurvesValue ()
|
|
{
|
|
NSArray *_cachedDataPoints;
|
|
}
|
|
@end
|
|
|
|
@implementation PGCurvesValue
|
|
|
|
- (instancetype)copyWithZone:(NSZone *)__unused zone
|
|
{
|
|
PGCurvesValue *value = [[PGCurvesValue alloc] init];
|
|
value.blacksLevel = self.blacksLevel;
|
|
value.shadowsLevel = self.shadowsLevel;
|
|
value.midtonesLevel = self.midtonesLevel;
|
|
value.highlightsLevel = self.highlightsLevel;
|
|
value.whitesLevel = self.whitesLevel;
|
|
|
|
return value;
|
|
}
|
|
|
|
+ (instancetype)defaultValue
|
|
{
|
|
PGCurvesValue *value = [[PGCurvesValue alloc] init];
|
|
value.blacksLevel = 0;
|
|
value.shadowsLevel = 25;
|
|
value.midtonesLevel = 50;
|
|
value.highlightsLevel = 75;
|
|
value.whitesLevel = 100;
|
|
|
|
return value;
|
|
}
|
|
|
|
- (NSArray *)dataPoints
|
|
{
|
|
if (_cachedDataPoints == nil)
|
|
[self interpolateCurve];
|
|
|
|
return _cachedDataPoints;
|
|
}
|
|
|
|
- (bool)isDefault
|
|
{
|
|
if (fabs(self.blacksLevel - 0) < FLT_EPSILON
|
|
&& fabs(self.shadowsLevel - 25) < FLT_EPSILON
|
|
&& fabs(self.midtonesLevel - 50) < FLT_EPSILON
|
|
&& fabs(self.highlightsLevel - 75) < FLT_EPSILON
|
|
&& fabs(self.whitesLevel - 100) < FLT_EPSILON)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
- (NSArray *)interpolateCurve
|
|
{
|
|
NSMutableArray *points = [[NSMutableArray alloc] init];
|
|
[points addObject:[NSValue valueWithCGPoint:CGPointMake(-0.001, self.blacksLevel / 100.0)]];
|
|
[points addObject:[NSValue valueWithCGPoint:CGPointMake(0.0, self.blacksLevel / 100.0)]];
|
|
[points addObject:[NSValue valueWithCGPoint:CGPointMake(0.25, self.shadowsLevel / 100.0)]];
|
|
[points addObject:[NSValue valueWithCGPoint:CGPointMake(0.5, self.midtonesLevel / 100.0)]];
|
|
[points addObject:[NSValue valueWithCGPoint:CGPointMake(0.75, self.highlightsLevel / 100.0)]];
|
|
[points addObject:[NSValue valueWithCGPoint:CGPointMake(1, self.whitesLevel / 100.0)]];
|
|
[points addObject:[NSValue valueWithCGPoint:CGPointMake(1.001, self.whitesLevel / 100.0)]];
|
|
|
|
NSMutableArray *dataPoints = [[NSMutableArray alloc] init];
|
|
|
|
NSMutableArray *interpolatedPoints = [[NSMutableArray alloc] init];
|
|
[interpolatedPoints addObject:points.firstObject];
|
|
|
|
for (NSUInteger index = 1; index < points.count - 2; index++)
|
|
{
|
|
CGPoint point0 = [points[index - 1] CGPointValue];
|
|
CGPoint point1 = [points[index] CGPointValue];
|
|
CGPoint point2 = [points[index + 1] CGPointValue];
|
|
CGPoint point3 = [points[index + 2] CGPointValue];
|
|
|
|
for (NSUInteger i = 1; i < PGCurveGranularity; i++)
|
|
{
|
|
CGFloat t = (CGFloat)i * (1.0f / (CGFloat)PGCurveGranularity);
|
|
CGFloat tt = t * t;
|
|
CGFloat ttt = tt * t;
|
|
|
|
CGPoint pi =
|
|
{
|
|
0.5 * (2 * point1.x + (point2.x - point0.x) * t + (2 * point0.x - 5 * point1.x + 4 * point2.x - point3.x) * tt + (3 * point1.x - point0.x - 3 * point2.x + point3.x) * ttt),
|
|
0.5 * (2 * point1.y + (point2.y - point0.y) * t + (2 * point0.y - 5 * point1.y + 4 * point2.y - point3.y) * tt + (3 * point1.y - point0.y - 3 * point2.y + point3.y) * ttt)
|
|
};
|
|
|
|
pi.y = MAX(0, MIN(1, pi.y));
|
|
|
|
if (pi.x > point0.x)
|
|
[interpolatedPoints addObject:[NSValue valueWithCGPoint:pi]];
|
|
|
|
if ((i - 1) % PGCurveDataStep == 0)
|
|
[dataPoints addObject:@(pi.y)];
|
|
}
|
|
|
|
[interpolatedPoints addObject:[NSValue valueWithCGPoint:point2]];
|
|
}
|
|
|
|
[interpolatedPoints addObject:points.lastObject];
|
|
|
|
_cachedDataPoints = dataPoints;
|
|
|
|
return interpolatedPoints;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation PGCurvesToolValue
|
|
|
|
- (instancetype)copyWithZone:(NSZone *)__unused zone
|
|
{
|
|
PGCurvesToolValue *value = [[PGCurvesToolValue alloc] init];
|
|
value.luminanceCurve = [self.luminanceCurve copy];
|
|
value.redCurve = [self.redCurve copy];
|
|
value.greenCurve = [self.greenCurve copy];
|
|
value.blueCurve = [self.blueCurve copy];
|
|
value.activeType = self.activeType;
|
|
|
|
return value;
|
|
}
|
|
|
|
+ (instancetype)defaultValue
|
|
{
|
|
PGCurvesToolValue *value = [[PGCurvesToolValue alloc] init];
|
|
value.luminanceCurve = [PGCurvesValue defaultValue];
|
|
value.redCurve = [PGCurvesValue defaultValue];
|
|
value.greenCurve = [PGCurvesValue defaultValue];
|
|
value.blueCurve = [PGCurvesValue defaultValue];
|
|
value.activeType = PGCurvesTypeLuminance;
|
|
|
|
return value;
|
|
}
|
|
|
|
- (id<PGCustomToolValue>)cleanValue
|
|
{
|
|
PGCurvesToolValue *value = [[PGCurvesToolValue alloc] init];
|
|
value.luminanceCurve = [self.luminanceCurve copy];
|
|
value.redCurve = [self.redCurve copy];
|
|
value.greenCurve = [self.greenCurve copy];
|
|
value.blueCurve = [self.blueCurve copy];
|
|
value.activeType = PGCurvesTypeLuminance;
|
|
|
|
return value;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@interface PGCurvesTool ()
|
|
{
|
|
PGPhotoProcessPassParameter *_rgbCurveParameter;
|
|
PGPhotoProcessPassParameter *_redCurveParameter;
|
|
PGPhotoProcessPassParameter *_greenCurveParameter;
|
|
PGPhotoProcessPassParameter *_blueCurveParameter;
|
|
|
|
PGPhotoProcessPassParameter *_skipToneParameter;
|
|
}
|
|
@end
|
|
|
|
@implementation PGCurvesTool
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self != nil)
|
|
{
|
|
_identifier = @"curves";
|
|
_type = PGPhotoToolTypeShader;
|
|
_order = 1;
|
|
|
|
_minimumValue = 0;
|
|
_maximumValue = 100;
|
|
_defaultValue = 0;
|
|
|
|
self.value = [PGCurvesToolValue defaultValue];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSString *)title
|
|
{
|
|
return TGLocalized(@"PhotoEditor.CurvesTool");
|
|
}
|
|
|
|
- (UIImage *)image
|
|
{
|
|
return [UIImage imageNamed:@"PhotoEditorCurvesTool"];
|
|
}
|
|
|
|
- (UIView <TGPhotoEditorToolView> *)itemAreaViewWithChangeBlock:(void (^)(id))changeBlock
|
|
{
|
|
__weak PGCurvesTool *weakSelf = self;
|
|
|
|
UIView <TGPhotoEditorToolView> *view = [[TGPhotoEditorCurvesToolView alloc] initWithEditorItem:self];
|
|
view.valueChanged = ^(id newValue, __unused bool animated)
|
|
{
|
|
__strong PGPhotoTool *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
if (newValue != nil)
|
|
{
|
|
if ([strongSelf.tempValue isEqual:newValue])
|
|
return;
|
|
|
|
strongSelf.tempValue = newValue;
|
|
}
|
|
|
|
if (changeBlock != nil)
|
|
changeBlock(newValue);
|
|
};
|
|
return view;
|
|
}
|
|
|
|
- (UIView <TGPhotoEditorToolView> *)itemControlViewWithChangeBlock:(void (^)(id, bool))__unused changeBlock
|
|
{
|
|
__weak PGCurvesTool *weakSelf = self;
|
|
|
|
UIView <TGPhotoEditorToolView> *view = [[TGPhotoEditorCurvesHistogramView alloc] initWithEditorItem:self];
|
|
view.valueChanged = ^(id newValue, __unused bool animated)
|
|
{
|
|
__strong PGPhotoTool *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
if (newValue != nil)
|
|
{
|
|
if ([strongSelf.tempValue isEqual:newValue])
|
|
return;
|
|
|
|
strongSelf.tempValue = newValue;
|
|
}
|
|
|
|
if (changeBlock != nil)
|
|
changeBlock(newValue, false);
|
|
};
|
|
|
|
return view;
|
|
}
|
|
|
|
- (Class)valueClass
|
|
{
|
|
return [PGCurvesToolValue class];
|
|
}
|
|
|
|
- (bool)shouldBeSkipped
|
|
{
|
|
PGCurvesToolValue *value = (PGCurvesToolValue *)self.displayValue;
|
|
return [value.luminanceCurve isDefault] && [value.redCurve isDefault] && [value.greenCurve isDefault] && [value.blueCurve isDefault];
|
|
}
|
|
|
|
- (NSArray *)parameters
|
|
{
|
|
if (!_parameters)
|
|
{
|
|
NSInteger count = PGCurveGranularity * PGCurveDataStep;
|
|
_rgbCurveParameter = [PGPhotoProcessPassParameter parameterWithName:@"rgbCurveValues" type:@"lowp float" count:count];
|
|
_redCurveParameter = [PGPhotoProcessPassParameter parameterWithName:@"redCurveValues" type:@"lowp float" count:count];
|
|
_greenCurveParameter = [PGPhotoProcessPassParameter parameterWithName:@"greenCurveValues" type:@"lowp float" count:count];
|
|
_blueCurveParameter = [PGPhotoProcessPassParameter parameterWithName:@"blueCurveValues" type:@"lowp float" count:count];
|
|
_skipToneParameter = [PGPhotoProcessPassParameter parameterWithName:@"skipTone" type:@"lowp float"];
|
|
|
|
_parameters = @[ _rgbCurveParameter, _redCurveParameter, _greenCurveParameter, _blueCurveParameter, _skipToneParameter ];
|
|
}
|
|
|
|
return _parameters;
|
|
}
|
|
|
|
- (void)updateParameters
|
|
{
|
|
PGCurvesToolValue *value = (PGCurvesToolValue *)self.displayValue;
|
|
|
|
[_rgbCurveParameter setFloatArray:[value.luminanceCurve dataPoints]];
|
|
[_redCurveParameter setFloatArray:[value.redCurve dataPoints]];
|
|
[_greenCurveParameter setFloatArray:[value.greenCurve dataPoints]];
|
|
[_blueCurveParameter setFloatArray:[value.blueCurve dataPoints]];
|
|
|
|
[_skipToneParameter setFloatValue:self.shouldBeSkipped ? 1.0 : 0.0];
|
|
}
|
|
|
|
- (NSString *)ancillaryShaderString
|
|
{
|
|
return PGShaderString
|
|
(
|
|
lowp vec3 applyLuminanceCurve(lowp vec3 pixel) {
|
|
int index = int(clamp(pixel.z / (1.0 / 200.0), 0.0, 199.0));
|
|
highp float value = rgbCurveValues[index];
|
|
|
|
highp float grayscale = (smoothstep(0.0, 0.1, pixel.z) * (1.0 - smoothstep(0.8, 1.0, pixel.z)));
|
|
highp float saturation = mix(0.0, pixel.y, grayscale);
|
|
pixel.y = saturation;
|
|
pixel.z = value;
|
|
return pixel;
|
|
}
|
|
|
|
lowp vec3 applyRGBCurve(lowp vec3 pixel) {
|
|
int index = int(clamp(pixel.r / (1.0 / 200.0), 0.0, 199.0));
|
|
highp float value = redCurveValues[index];
|
|
pixel.r = value;
|
|
|
|
index = int(clamp(pixel.g / (1.0 / 200.0), 0.0, 199.0));
|
|
value = greenCurveValues[index];
|
|
pixel.g = clamp(value, 0.0, 1.0);
|
|
|
|
index = int(clamp(pixel.b / (1.0 / 200.0), 0.0, 199.0));
|
|
value = blueCurveValues[index];
|
|
pixel.b = clamp(value, 0.0, 1.0);
|
|
|
|
return pixel;
|
|
}
|
|
);
|
|
}
|
|
|
|
- (NSString *)stringValue
|
|
{
|
|
if (![self shouldBeSkipped])
|
|
{
|
|
return @"◆";
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (NSString *)shaderString
|
|
{
|
|
return PGShaderString
|
|
(
|
|
if (skipTone < toolEpsilon) {
|
|
result = vec4(applyRGBCurve(hslToRgb(applyLuminanceCurve(rgbToHsl(result.rgb)))), result.a);
|
|
}
|
|
);
|
|
}
|
|
|
|
@end
|