Files
Swiftgram/submodules/LegacyComponents/Sources/PGPhotoEditor.m
Kylmakalle fd86110711 Version 11.3.1
Fixes

fix localeWithStrings globally (#30)

Fix badge on zoomed devices. closes #9

Hide channel bottom panel closes #27

Another attempt to fix badge on some Zoomed devices

Force System Share sheet tg://sg/debug

fixes for device badge

New Crowdin updates (#34)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

Fix input panel hidden on selection (#31)

* added if check for selectionState != nil

* same order of subnodes

Revert "Fix input panel hidden on selection (#31)"

This reverts commit e8a8bb1496.

Fix input panel for channels Closes #37

Quickly share links with system's share menu

force tabbar when editing

increase height for correct animation

New translations sglocalizable.strings (Ukrainian) (#38)

Hide Post Story button

Fix 10.15.1

Fix archive option for long-tap

Enable in-app Safari

Disable some unsupported purchases

disableDeleteChatSwipeOption + refactor restart alert

Hide bot in suggestions list

Fix merge v11.0

Fix exceptions for safari webview controller

New Crowdin updates (#47)

* New translations sglocalizable.strings (Romanian)

* New translations sglocalizable.strings (French)

* New translations sglocalizable.strings (Spanish)

* New translations sglocalizable.strings (Afrikaans)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Catalan)

* New translations sglocalizable.strings (Czech)

* New translations sglocalizable.strings (Danish)

* New translations sglocalizable.strings (German)

* New translations sglocalizable.strings (Greek)

* New translations sglocalizable.strings (Finnish)

* New translations sglocalizable.strings (Hebrew)

* New translations sglocalizable.strings (Hungarian)

* New translations sglocalizable.strings (Italian)

* New translations sglocalizable.strings (Japanese)

* New translations sglocalizable.strings (Korean)

* New translations sglocalizable.strings (Dutch)

* New translations sglocalizable.strings (Norwegian)

* New translations sglocalizable.strings (Polish)

* New translations sglocalizable.strings (Portuguese)

* New translations sglocalizable.strings (Serbian (Cyrillic))

* New translations sglocalizable.strings (Swedish)

* New translations sglocalizable.strings (Turkish)

* New translations sglocalizable.strings (Vietnamese)

* New translations sglocalizable.strings (Indonesian)

* New translations sglocalizable.strings (Hindi)

* New translations sglocalizable.strings (Uzbek)

New Crowdin updates (#49)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Arabic)

New translations sglocalizable.strings (Russian) (#51)

Call confirmation

WIP Settings search

Settings Search

Localize placeholder

Update AccountUtils.swift

mark mutual contact

Align back context action to left

New Crowdin updates (#54)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Ukrainian)

Independent Playground app for simulator

New translations sglocalizable.strings (Ukrainian) (#55)

Playground UIKit base and controllers

Inject SwiftUI view with overflow to AsyncDisplayKit

Launch Playgound project on simulator

Create .swiftformat

Move Playground to example

Update .swiftformat

Init SwiftUIViewController

wip

New translations sglocalizable.strings (Chinese Traditional) (#57)

Xcode 16 fixes

Fix

New translations sglocalizable.strings (Italian) (#59)

New translations sglocalizable.strings (Chinese Simplified) (#63)

Force disable CallKit integration due to missing NSE Entitlement

Fix merge

Fix whole chat translator

Sweetpad config

Bump version

11.3.1 fixes

Mutual contact placement fix

Disable Video PIP swipe

Update versions.json

Fix PIP crash
2024-12-20 09:38:13 +02:00

643 lines
21 KiB
Objective-C

#import "PGPhotoEditor.h"
#import <SSignalKit/SSignalKit.h>
#import <LegacyComponents/TGMemoryImageCache.h>
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import "TGPhotoEditorPreviewView.h"
#import "PGPhotoEditorView.h"
#import "PGPhotoEditorPicture.h"
#import "GPUImageTextureInput.h"
#import "GPUImageCropFilter.h"
#import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGVideoEditAdjustments.h>
#import <LegacyComponents/TGPaintingData.h>
#import "PGVideoMovie.h"
#import "PGPhotoToolComposer.h"
#import "PGEnhanceTool.h"
#import "PGSkinTool.h"
#import "PGExposureTool.h"
#import "PGContrastTool.h"
#import "PGWarmthTool.h"
#import "PGSaturationTool.h"
#import "PGHighlightsTool.h"
#import "PGShadowsTool.h"
#import "PGVignetteTool.h"
#import "PGGrainTool.h"
#import "PGBlurTool.h"
#import "PGSharpenTool.h"
#import "PGFadeTool.h"
#import "PGTintTool.h"
#import "PGCurvesTool.h"
#import "PGPhotoHistogramGenerator.h"
@interface PGPhotoEditor ()
{
PGPhotoToolComposer *_toolComposer;
id<TGMediaEditAdjustments> _initialAdjustments;
GPUImageOutput *_currentInput;
GPUImageCropFilter *_cropFilter;
GPUImageRotationMode _rotationMode;
NSArray *_currentProcessChain;
GPUImageOutput <GPUImageInput> *_finalFilter;
PGPhotoHistogram *_currentHistogram;
PGPhotoHistogramGenerator *_histogramGenerator;
UIImageOrientation _imageCropOrientation;
CGRect _imageCropRect;
CGFloat _imageCropRotation;
bool _imageCropMirrored;
SPipe *_histogramPipe;
SQueue *_queue;
SQueue *_videoQueue;
bool _playing;
bool _processing;
bool _needsReprocessing;
bool _fullSize;
}
@end
@implementation PGPhotoEditor
- (instancetype)initWithOriginalSize:(CGSize)originalSize adjustments:(id<TGMediaEditAdjustments>)adjustments forVideo:(bool)forVideo enableStickers:(bool)enableStickers
{
self = [super init];
if (self != nil)
{
_queue = [[SQueue alloc] init];
_videoQueue = [[SQueue alloc] init];
_forVideo = forVideo;
_enableStickers = enableStickers;
_originalSize = originalSize;
_cropRect = CGRectMake(0.0f, 0.0f, _originalSize.width, _originalSize.height);
_paintingData = adjustments.paintingData;
_tools = [self toolsInit];
_toolComposer = [[PGPhotoToolComposer alloc] init];
[_toolComposer addPhotoTools:_tools];
[_toolComposer compose];
_histogramPipe = [[SPipe alloc] init];
if (!forVideo) {
__weak PGPhotoEditor *weakSelf = self;
_histogramGenerator = [[PGPhotoHistogramGenerator alloc] init];
_histogramGenerator.histogramReady = ^(PGPhotoHistogram *histogram)
{
__strong PGPhotoEditor *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_currentHistogram = histogram;
strongSelf->_histogramPipe.sink(histogram);
};
}
[self importAdjustments:adjustments];
}
return self;
}
- (void)dealloc
{
if ([_currentInput isKindOfClass:[PGVideoMovie class]]) {
[(PGVideoMovie *)_currentInput cancelProcessing];
}
TGDispatchAfter(1.5f, dispatch_get_main_queue(), ^
{
[[GPUImageContext sharedFramebufferCache] purgeAllUnassignedFramebuffers];
});
}
- (void)cleanup
{
[[GPUImageContext sharedFramebufferCache] purgeAllUnassignedFramebuffers];
}
- (NSArray *)toolsInit
{
NSMutableArray *tools = [NSMutableArray array];
for (Class toolClass in [PGPhotoEditor availableTools])
{
PGPhotoTool *toolInstance = [[toolClass alloc] init];
if (!_forVideo || toolInstance.isAvialableForVideo) {
[tools addObject:toolInstance];
}
}
return tools;
}
- (void)setImage:(UIImage *)image forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored fullSize:(bool)fullSize
{
[_toolComposer invalidate];
_currentProcessChain = nil;
_imageCropRect = cropRect;
_imageCropRotation = cropRotation;
_imageCropOrientation = cropOrientation;
_imageCropMirrored = cropMirrored;
[_currentInput removeAllTargets];
_currentInput = [[PGPhotoEditorPicture alloc] initWithImage:image];
_histogramGenerator.imageSize = image.size;
_fullSize = fullSize;
}
- (CGFloat)_cropRectEpsilon
{
return MAX(_originalSize.width, _originalSize.height) * 0.005f;
}
- (CGRect)normalizedCropRect:(CGRect)cropRect
{
CGRect normalizedCropRect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
normalizedCropRect = CGRectMake(MAX(0.0, MIN(1.0, cropRect.origin.x / _originalSize.width)), MAX(0.0, MIN(1.0, cropRect.origin.y / _originalSize.height)), MAX(0.0, MIN(1.0, cropRect.size.width / _originalSize.width)), MAX(0.0, MIN(1.0, cropRect.size.height / _originalSize.height)));
return normalizedCropRect;
}
- (void)setCropRect:(CGRect)cropRect
{
_cropRect = cropRect;
_cropFilter.cropRegion = [self normalizedCropRect:cropRect];
}
- (void)setCropOrientation:(UIImageOrientation)cropOrientation
{
_cropOrientation = cropOrientation;
}
- (void)setPlayerItem:(AVPlayerItem *)playerItem forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored {
[_toolComposer invalidate];
_currentProcessChain = nil;
[_currentInput removeAllTargets];
PGVideoMovie *movie = [[PGVideoMovie alloc] initWithPlayerItem:playerItem];
_currentInput = movie;
CGRect defaultCropRect = CGRectMake(0, 0, _originalSize.width, _originalSize.height);
bool hasCropping = !_CGRectEqualToRectWithEpsilon(cropRect, CGRectZero, [self _cropRectEpsilon]) && !_CGRectEqualToRectWithEpsilon(cropRect, defaultCropRect, [self _cropRectEpsilon]);
_rotationMode = kGPUImageNoRotation;
if (cropOrientation != UIImageOrientationUp || cropMirrored || hasCropping) {
if (_cropFilter == nil)
_cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:[self normalizedCropRect:cropRect]];
else
_cropFilter.cropRegion = [self normalizedCropRect:cropRect];
if (cropOrientation != UIImageOrientationUp || cropMirrored) {
switch (cropOrientation) {
case UIImageOrientationLeft:
_rotationMode = kGPUImageRotateLeft;
break;
case UIImageOrientationRight:
_rotationMode = cropMirrored ? kGPUImageRotateRightFlipHorizontal : kGPUImageRotateRight;
break;
case UIImageOrientationDown:
_rotationMode = cropMirrored ? kGPUImageRotate180FlipHorizontal : kGPUImageRotate180;
break;
case UIImageOrientationUp:
if (cropMirrored)
_rotationMode = kGPUImageFlipHorizonal;
break;
default:
break;
}
}
}
_fullSize = true;
}
- (void)setCIImage:(CIImage *)ciImage {
[_toolComposer invalidate];
_currentProcessChain = nil;
[_currentInput removeAllTargets];
if ([_currentInput isKindOfClass:[GPUImageTextureInput class]]) {
[(GPUImageTextureInput *)_currentInput setCIImage:ciImage];
} else {
GPUImageTextureInput *input = [[GPUImageTextureInput alloc] initWithCIImage:ciImage];
_currentInput = input;
}
_fullSize = true;
}
#pragma mark - Properties
- (CGSize)rotatedCropSize
{
if (_cropOrientation == UIImageOrientationLeft || _cropOrientation == UIImageOrientationRight)
return CGSizeMake(_cropRect.size.height, _cropRect.size.width);
return _cropRect.size;
}
- (bool)hasDefaultCropping
{
if (!_CGRectEqualToRectWithEpsilon(self.cropRect, CGRectMake(0, 0, _originalSize.width, _originalSize.height), 1.0f) || self.cropOrientation != UIImageOrientationUp || ABS(self.cropRotation) > FLT_EPSILON || self.cropMirrored)
{
return false;
}
return true;
}
#pragma mark - Processing
- (bool)readyForProcessing
{
return (_currentInput != nil);
}
- (void)processAnimated:(bool)animated completion:(void (^)(void))completion
{
[self processAnimated:animated capture:false synchronous:false completion:completion];
}
- (void)processAnimated:(bool)animated capture:(bool)capture synchronous:(bool)synchronous completion:(void (^)(void))completion
{
if (self.previewOutput == nil && !self.standalone)
return;
if (![_currentInput isKindOfClass:[PGPhotoEditorPicture class]]) {
[_queue dispatch:^
{
[self updateProcessChain];
GPUImageOutput *currentInput = _currentInput;
if ([currentInput isKindOfClass:[PGVideoMovie class]]) {
if (capture) {
if ([currentInput isKindOfClass:[PGVideoMovie class]])
[(PGVideoMovie *)currentInput process];
[_finalFilter useNextFrameForImageCapture];
if (completion != nil)
completion();
} else {
if (!_playing) {
_playing = true;
[_videoQueue dispatch:^{
if ([currentInput isKindOfClass:[PGVideoMovie class]]) {
[(PGVideoMovie *)currentInput startProcessing];
}
}];
}
}
} else if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) {
if (capture)
[_finalFilter useNextFrameForImageCapture];
[(GPUImageTextureInput *)currentInput processTextureWithFrameTime:kCMTimeZero synchronous:synchronous completion:^{
if (completion != nil)
completion();
}];
[_finalFilter commitImageCapture];
}
} synchronous:synchronous];
return;
}
if (iosMajorVersion() < 7)
animated = false;
if (_processing && completion == nil)
{
_needsReprocessing = true;
return;
}
_processing = true;
[_queue dispatch:^
{
[self updateProcessChain];
if (!self.forVideo && capture)
[_finalFilter useNextFrameForImageCapture];
TGPhotoEditorPreviewView *previewOutput = self.previewOutput;
if ([_currentInput isKindOfClass:[PGPhotoEditorPicture class]]) {
PGPhotoEditorPicture *picture = (PGPhotoEditorPicture *)_currentInput;
if (animated)
{
TGDispatchOnMainThread(^
{
[previewOutput prepareTransitionFadeView];
});
}
[picture processSynchronous:true completion:^
{
if (completion != nil)
completion();
_processing = false;
if (animated)
{
TGDispatchOnMainThread(^
{
[previewOutput performTransitionFade];
});
}
if (_needsReprocessing && !synchronous)
{
_needsReprocessing = false;
[self processAnimated:false completion:nil];
}
}];
} else {
}
} synchronous:synchronous];
}
- (void)reprocess {
if ([_currentInput isKindOfClass:[PGVideoMovie class]]) {
[(PGVideoMovie *)_currentInput reprocessCurrent];
}
}
- (void)updateProcessChain {
[self updateProcessChain:false];
}
- (void)updateProcessChain:(bool)force {
[GPUImageFramebuffer setMark:self.forVideo];
NSMutableArray *processChain = [NSMutableArray array];
for (PGPhotoTool *tool in _toolComposer.advancedTools)
{
if (!tool.shouldBeSkipped && tool.pass != nil)
[processChain addObject:tool.pass];
}
_toolComposer.imageSize = _cropRect.size;
[processChain addObject:_toolComposer];
TGPhotoEditorPreviewView *previewOutput = self.previewOutput;
if (![_currentProcessChain isEqualToArray:processChain] || force)
{
[_currentInput removeAllTargets];
[_cropFilter removeAllTargets];
for (PGPhotoProcessPass *pass in _currentProcessChain)
[pass.filter removeAllTargets];
_currentProcessChain = processChain;
GPUImageOutput <GPUImageInput> *lastFilter = ((PGPhotoProcessPass *)_currentProcessChain.firstObject).filter;
if (_cropFilter != nil && !self.cropOnLast) {
[_currentInput addTarget:_cropFilter];
[_cropFilter addTarget:lastFilter];
} else {
[_currentInput addTarget:lastFilter];
}
NSInteger chainLength = _currentProcessChain.count;
if (chainLength > 1)
{
for (NSInteger i = 1; i < chainLength; i++)
{
PGPhotoProcessPass *pass = ((PGPhotoProcessPass *)_currentProcessChain[i]);
GPUImageOutput <GPUImageInput> *filter = pass.filter;
[lastFilter addTarget:filter];
lastFilter = filter;
}
}
_finalFilter = lastFilter;
if (self.cropOnLast) {
if (_cropFilter == nil)
_cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:[self normalizedCropRect:_cropRect]];
for (PGPhotoEditorView *view in _additionalOutputs) {
[_finalFilter addTarget:view];
}
[_finalFilter addTarget:_cropFilter];
if (previewOutput != nil) {
[_cropFilter addTarget:previewOutput.imageView];
}
} else {
if (previewOutput != nil) {
[_finalFilter addTarget:previewOutput.imageView];
}
for (PGPhotoEditorView *view in _additionalOutputs) {
[_finalFilter addTarget:view];
}
}
if (_histogramGenerator != nil && !self.standalone) {
[_finalFilter addTarget:_histogramGenerator];
}
}
}
- (void)setAdditionalOutputs:(NSArray *)additionalOutputs {
_additionalOutputs = additionalOutputs;
if (_finalFilter == nil)
return;
[_cropFilter removeAllTargets];
[_finalFilter removeAllTargets];
if (self.cropOnLast) {
for (PGPhotoEditorView *view in _additionalOutputs) {
[_finalFilter addTarget:view];
}
[_finalFilter addTarget:_cropFilter];
if (self.previewOutput != nil) {
[_cropFilter addTarget:self.previewOutput.imageView];
}
} else {
for (PGPhotoEditorView *view in _additionalOutputs) {
[_finalFilter addTarget:view];
}
if (self.previewOutput != nil) {
[_finalFilter addTarget:self.previewOutput.imageView];
}
}
if (_histogramGenerator != nil && !self.standalone) {
[_finalFilter addTarget:_histogramGenerator];
}
}
#pragma mark - Result
- (void)createResultImageWithCompletion:(void (^)(UIImage *image))completion
{
[self processAnimated:false capture:true synchronous:false completion:^
{
UIImage *image = [_finalFilter imageFromCurrentFramebufferWithOrientation:UIImageOrientationUp];
if (completion != nil)
completion(image);
}];
}
- (UIImage *)currentResultImage
{
__block UIImage *image = nil;
[self processAnimated:false capture:true synchronous:true completion:^
{
image = [_finalFilter imageFromCurrentFramebufferWithOrientation:UIImageOrientationUp];
}];
return image;
}
- (void)currentResultCIImage:(void (^)(CIImage *image, void(^unlock)(void)))completion
{
[self processAnimated:false capture:true synchronous:true completion:^
{
[_finalFilter newCIImageFromCurrentlyProcessedOutput:completion];
}];
}
#pragma mark - Editor Values
- (void)importAdjustments:(id<TGMediaEditAdjustments>)adjustments
{
_initialAdjustments = adjustments;
if (adjustments != nil)
self.cropRect = adjustments.cropRect;
self.cropOrientation = adjustments.cropOrientation;
self.cropLockedAspectRatio = adjustments.cropLockedAspectRatio;
self.cropMirrored = adjustments.cropMirrored;
self.paintingData = adjustments.paintingData;
if ([adjustments isKindOfClass:[PGPhotoEditorValues class]])
{
PGPhotoEditorValues *editorValues = (PGPhotoEditorValues *)adjustments;
self.cropRotation = editorValues.cropRotation;
}
else if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]])
{
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
self.trimStartValue = videoAdjustments.trimStartValue;
self.trimEndValue = videoAdjustments.trimEndValue;
self.sendAsGif = videoAdjustments.sendAsGif;
self.sendAsTelescope = videoAdjustments.sendAsTelescope;
self.preset = videoAdjustments.preset;
}
for (PGPhotoTool *tool in self.tools)
{
id value = adjustments.toolValues[tool.identifier];
if (value != nil && [value isKindOfClass:[tool valueClass]])
tool.value = [value copy];
}
}
- (id<TGMediaEditAdjustments>)exportAdjustments
{
return [self exportAdjustmentsWithPaintingData:_paintingData];
}
- (id<TGMediaEditAdjustments>)exportAdjustmentsWithPaintingData:(TGPaintingData *)paintingData
{
NSMutableDictionary *toolValues = [[NSMutableDictionary alloc] init];
for (PGPhotoTool *tool in self.tools)
{
if (!tool.shouldBeSkipped && (!_forVideo || tool.isAvialableForVideo))
{
if (!([tool.value isKindOfClass:[NSNumber class]] && ABS([tool.value floatValue] - (float)tool.defaultValue) < FLT_EPSILON))
toolValues[tool.identifier] = [tool.value copy];
}
}
if (!_forVideo)
{
return [PGPhotoEditorValues editorValuesWithOriginalSize:self.originalSize cropRect:self.cropRect cropRotation:self.cropRotation cropOrientation:self.cropOrientation cropLockedAspectRatio:self.cropLockedAspectRatio cropMirrored:self.cropMirrored toolValues:toolValues paintingData:paintingData sendAsGif:self.sendAsGif sendAsTelescope:self.sendAsTelescope];
}
else
{
TGVideoEditAdjustments *initialAdjustments = (TGVideoEditAdjustments *)_initialAdjustments;
return [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:self.originalSize cropRect:self.cropRect cropOrientation:self.cropOrientation cropRotation:self.cropRotation cropLockedAspectRatio:self.cropLockedAspectRatio cropMirrored:self.cropMirrored trimStartValue:initialAdjustments.trimStartValue trimEndValue:initialAdjustments.trimEndValue toolValues:toolValues paintingData:paintingData sendAsGif:self.sendAsGif sendAsTelescope:self.sendAsTelescope preset:self.preset];
}
}
- (void)setDisableAll:(bool)disableAll {
_disableAll = disableAll;
for (PGPhotoTool *tool in self.tools)
{
tool.disabled = disableAll;
}
}
- (SSignal *)histogramSignal
{
return [[SSignal single:_currentHistogram] then:_histogramPipe.signalProducer()];
}
+ (NSArray *)availableTools
{
static NSArray *tools;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
tools = @[ [PGSkinTool class],
[PGEnhanceTool class],
[PGExposureTool class],
[PGContrastTool class],
[PGSaturationTool class],
[PGWarmthTool class],
[PGFadeTool class],
[PGTintTool class],
[PGHighlightsTool class],
[PGShadowsTool class],
[PGVignetteTool class],
[PGGrainTool class],
[PGBlurTool class],
[PGSharpenTool class],
[PGCurvesTool class] ];
});
return tools;
}
+ (UIImage *)resultImageForImage:(UIImage *)image adjustments:(id<TGMediaEditAdjustments>)adjustments {
PGPhotoEditor *editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:false enableStickers:true];
editor.standalone = true;
[editor setImage:image forCropRect:adjustments.cropRect cropRotation:0.0 cropOrientation:adjustments.cropOrientation cropMirrored:adjustments.cropMirrored fullSize:false];
return [editor currentResultImage];
}
@end