Video editor fixes

This commit is contained in:
Ilya Laktyushin 2020-05-27 22:31:54 +03:00
parent 9c9fd6cc03
commit ef5756702c
33 changed files with 527 additions and 190 deletions

View File

@ -4,8 +4,6 @@
@interface PGPhotoEditorValues : NSObject <TGMediaEditAdjustments>
@property (nonatomic, readonly) CGFloat cropRotation;
+ (instancetype)editorValuesWithOriginalSize:(CGSize)originalSize cropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropLockedAspectRatio:(CGFloat)cropLockedAspectRatio cropMirrored:(bool)cropMirrored toolValues:(NSDictionary *)toolValues paintingData:(TGPaintingData *)paintingData sendAsGif:(bool)sendAsGif;
@end

View File

@ -25,6 +25,7 @@
@property (nonatomic, readonly) CGSize originalSize;
@property (nonatomic, readonly) CGRect cropRect;
@property (nonatomic, readonly) UIImageOrientation cropOrientation;
@property (nonatomic, readonly) CGFloat cropRotation;
@property (nonatomic, readonly) CGFloat cropLockedAspectRatio;
@property (nonatomic, readonly) bool cropMirrored;
@property (nonatomic, readonly) bool sendAsGif;

View File

@ -20,7 +20,7 @@
+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer;
+ (SSignal *)hashForAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments;
+ (SSignal *)renderUIImage:(UIImage *)image adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer;
+ (SSignal *)renderUIImage:(UIImage *)image duration:(NSTimeInterval)duration adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer;
+ (NSUInteger)estimatedSizeForPreset:(TGMediaVideoConversionPreset)preset duration:(NSTimeInterval)duration hasAudio:(bool)hasAudio;
+ (TGMediaVideoConversionPreset)bestAvailablePresetForDimensions:(CGSize)dimensions;

View File

@ -26,6 +26,8 @@
+ (instancetype)dataWithPaintingImagePath:(NSString *)imagePath;
- (instancetype)dataForAnimation;
+ (void)storePaintingData:(TGPaintingData *)data inContext:(TGMediaEditingContext *)context forItem:(id<TGMediaEditableItem>)item forVideo:(bool)video;
+ (void)facilitatePaintingData:(TGPaintingData *)data;

View File

@ -14,6 +14,7 @@ CGFloat TGRadiansToDegrees(CGFloat radians);
UIImage *TGPhotoEditorCrop(UIImage *image, UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize);
UIImage *TGPhotoEditorVideoCrop(UIImage *image, UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize, bool useImageSize);
UIImage *TGPhotoEditorVideoExtCrop(UIImage *inputImage, UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize, bool useImageSize, bool skipImageTransform);
UIImage *TGPhotoEditorFitImage(UIImage *image, CGSize maxSize);
CGSize TGRotatedContentSize(CGSize contentSize, CGFloat rotation);

View File

@ -18,6 +18,13 @@
@end
@protocol TGPhotoPaintStickersScreen <NSObject>
- (void)restore;
- (void)invalidate;
@end
@protocol TGPhotoPaintStickersContext <NSObject>
- (int64_t)documentIdForDocument:(id)document;
@ -25,6 +32,6 @@
- (UIView<TGPhotoPaintStickerRenderView> *)stickerViewForDocument:(id)document;
@property (nonatomic, copy) void(^presentStickersController)(void(^)(id, bool, UIView *, CGRect));
@property (nonatomic, copy) id<TGPhotoPaintStickersScreen>(^presentStickersController)(void(^)(id, bool, UIView *, CGRect));
@end

View File

@ -37,6 +37,7 @@ typedef enum
+ (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize
cropRect:(CGRect)cropRect
cropOrientation:(UIImageOrientation)cropOrientation
cropRotation:(CGFloat)cropRotation
cropLockedAspectRatio:(CGFloat)cropLockedAspectRatio
cropMirrored:(bool)cropMirrored
trimStartValue:(NSTimeInterval)trimStartValue

View File

@ -0,0 +1,15 @@
#import "GPUImageFilter.h"
@interface GPUImageCropFilter : GPUImageFilter
{
GLfloat cropTextureCoordinates[8];
}
// The crop region is the rectangle within the image to crop. It is normalized to a coordinate space from 0.0 to 1.0, with 0.0, 0.0 being the upper left corner of the image
@property(readwrite, nonatomic) CGRect cropRegion;
@property(readwrite, nonatomic) GPUImageRotationMode rotationMode;
// Initialization and teardown
- (id)initWithCropRegion:(CGRect)newCropRegion;
@end

View File

@ -0,0 +1,248 @@
#import "GPUImageCropFilter.h"
NSString *const kGPUImageCropFragmentShaderString = SHADER_STRING
(
varying highp vec2 texCoord;
uniform sampler2D sourceImage;
void main()
{
gl_FragColor = texture2D(sourceImage, texCoord);
}
);
@interface GPUImageCropFilter ()
- (void)calculateCropTextureCoordinates;
@end
@interface GPUImageCropFilter()
{
CGSize originallySuppliedInputSize;
}
@end
@implementation GPUImageCropFilter
@synthesize cropRegion = _cropRegion;
#pragma mark -
#pragma mark Initialization and teardown
- (id)initWithCropRegion:(CGRect)newCropRegion;
{
if (!(self = [super initWithFragmentShaderFromString:kGPUImageCropFragmentShaderString]))
{
return nil;
}
self.cropRegion = newCropRegion;
return self;
}
- (id)init;
{
if (!(self = [self initWithCropRegion:CGRectMake(0.0, 0.0, 1.0, 1.0)]))
{
return nil;
}
return self;
}
#pragma mark -
#pragma mark Rendering
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex;
{
if (self.preventRendering)
{
return;
}
CGSize rotatedSize = [self rotatedSize:newSize forIndex:textureIndex];
originallySuppliedInputSize = rotatedSize;
CGSize scaledSize;
scaledSize.width = rotatedSize.width * _cropRegion.size.width;
scaledSize.height = rotatedSize.height * _cropRegion.size.height;
if (CGSizeEqualToSize(scaledSize, CGSizeZero))
{
inputTextureSize = scaledSize;
}
else if (!CGSizeEqualToSize(inputTextureSize, scaledSize))
{
inputTextureSize = scaledSize;
}
}
#pragma mark -
#pragma mark GPUImageInput
- (void)calculateCropTextureCoordinates;
{
CGFloat minX = _cropRegion.origin.x;
CGFloat minY = _cropRegion.origin.y;
CGFloat maxX = CGRectGetMaxX(_cropRegion);
CGFloat maxY = CGRectGetMaxY(_cropRegion);
switch(inputRotation)
{
case kGPUImageNoRotation: // Works
{
cropTextureCoordinates[0] = minX; // 0,0
cropTextureCoordinates[1] = minY;
cropTextureCoordinates[2] = maxX; // 1,0
cropTextureCoordinates[3] = minY;
cropTextureCoordinates[4] = minX; // 0,1
cropTextureCoordinates[5] = maxY;
cropTextureCoordinates[6] = maxX; // 1,1
cropTextureCoordinates[7] = maxY;
}; break;
case kGPUImageRotateLeft: // Fixed
{
cropTextureCoordinates[0] = maxY; // 1,0
cropTextureCoordinates[1] = 1.0 - maxX;
cropTextureCoordinates[2] = maxY; // 1,1
cropTextureCoordinates[3] = 1.0 - minX;
cropTextureCoordinates[4] = minY; // 0,0
cropTextureCoordinates[5] = 1.0 - maxX;
cropTextureCoordinates[6] = minY; // 0,1
cropTextureCoordinates[7] = 1.0 - minX;
}; break;
case kGPUImageRotateRight: // Fixed
{
cropTextureCoordinates[0] = minY; // 0,1
cropTextureCoordinates[1] = 1.0 - minX;
cropTextureCoordinates[2] = minY; // 0,0
cropTextureCoordinates[3] = 1.0 - maxX;
cropTextureCoordinates[4] = maxY; // 1,1
cropTextureCoordinates[5] = 1.0 - minX;
cropTextureCoordinates[6] = maxY; // 1,0
cropTextureCoordinates[7] = 1.0 - maxX;
}; break;
case kGPUImageFlipVertical: // Works for me
{
cropTextureCoordinates[0] = minX; // 0,1
cropTextureCoordinates[1] = maxY;
cropTextureCoordinates[2] = maxX; // 1,1
cropTextureCoordinates[3] = maxY;
cropTextureCoordinates[4] = minX; // 0,0
cropTextureCoordinates[5] = minY;
cropTextureCoordinates[6] = maxX; // 1,0
cropTextureCoordinates[7] = minY;
}; break;
case kGPUImageFlipHorizonal: // Works for me
{
cropTextureCoordinates[0] = maxX; // 1,0
cropTextureCoordinates[1] = minY;
cropTextureCoordinates[2] = minX; // 0,0
cropTextureCoordinates[3] = minY;
cropTextureCoordinates[4] = maxX; // 1,1
cropTextureCoordinates[5] = maxY;
cropTextureCoordinates[6] = minX; // 0,1
cropTextureCoordinates[7] = maxY;
}; break;
case kGPUImageRotate180: // Fixed
{
cropTextureCoordinates[0] = maxX; // 1,1
cropTextureCoordinates[1] = maxY;
cropTextureCoordinates[2] = minX; // 0,1
cropTextureCoordinates[3] = maxY;
cropTextureCoordinates[4] = maxX; // 1,0
cropTextureCoordinates[5] = minY;
cropTextureCoordinates[6] = minX; // 0,0
cropTextureCoordinates[7] = minY;
}; break;
case kGPUImageRotateRightFlipVertical: // Fixed
{
cropTextureCoordinates[0] = minY; // 0,0
cropTextureCoordinates[1] = 1.0 - maxX;
cropTextureCoordinates[2] = minY; // 0,1
cropTextureCoordinates[3] = 1.0 - minX;
cropTextureCoordinates[4] = maxY; // 1,0
cropTextureCoordinates[5] = 1.0 - maxX;
cropTextureCoordinates[6] = maxY; // 1,1
cropTextureCoordinates[7] = 1.0 - minX;
}; break;
case kGPUImageRotateRightFlipHorizontal: // Fixed
{
cropTextureCoordinates[0] = maxY; // 1,1
cropTextureCoordinates[1] = 1.0 - minX;
cropTextureCoordinates[2] = maxY; // 1,0
cropTextureCoordinates[3] = 1.0 - maxX;
cropTextureCoordinates[4] = minY; // 0,1
cropTextureCoordinates[5] = 1.0 - minX;
cropTextureCoordinates[6] = minY; // 0,0
cropTextureCoordinates[7] = 1.0 - maxX;
}; break;
}
}
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
{
static const GLfloat cropSquareVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
[self renderToTextureWithVertices:cropSquareVertices textureCoordinates:cropTextureCoordinates];
[self informTargetsAboutNewFrameAtTime:frameTime];
}
#pragma mark -
#pragma mark Accessors
- (void)setCropRegion:(CGRect)newValue;
{
NSParameterAssert(newValue.origin.x >= 0 && newValue.origin.x <= 1 &&
newValue.origin.y >= 0 && newValue.origin.y <= 1 &&
newValue.size.width >= 0 && newValue.size.width <= 1 &&
newValue.size.height >= 0 && newValue.size.height <= 1);
_cropRegion = newValue;
[self calculateCropTextureCoordinates];
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex;
{
[super setInputRotation:newInputRotation atIndex:textureIndex];
[self calculateCropTextureCoordinates];
}
@end

View File

@ -18,8 +18,6 @@ NSString *const kGPUImageVertexShaderString = SHADER_STRING
}
);
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
(
varying highp vec2 texCoord;
@ -32,21 +30,6 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
}
);
#else
NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
(
varying vec2 texCoord;
uniform sampler2D sourceImage;
void main()
{
gl_FragColor = texture2D(sourceImage, texCoord);
}
);
#endif
@implementation GPUImageFilter

View File

@ -40,7 +40,7 @@
- (void)cleanup;
- (void)setImage:(UIImage *)image forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored fullSize:(bool)fullSize;
- (void)setPlayerItem:(AVPlayerItem *)playerItem;
- (void)setPlayerItem:(AVPlayerItem *)playerItem forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored;
- (void)setCIImage:(CIImage *)ciImage;
- (void)processAnimated:(bool)animated completion:(void (^)(void))completion;

View File

@ -11,6 +11,7 @@
#import "PGPhotoEditorPicture.h"
#import "GPUImageTextureInput.h"
#import "GPUImageCropFilter.h"
#import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGVideoEditAdjustments.h>
@ -43,6 +44,9 @@
id<TGMediaEditAdjustments> _initialAdjustments;
GPUImageOutput *_currentInput;
GPUImageCropFilter *_cropFilter;
GPUImageRotationMode _rotationMode;
NSArray *_currentProcessChain;
GPUImageOutput <GPUImageInput> *_finalFilter;
@ -159,7 +163,7 @@
_fullSize = fullSize;
}
- (void)setPlayerItem:(AVPlayerItem *)playerItem {
- (void)setPlayerItem:(AVPlayerItem *)playerItem forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored {
[_toolComposer invalidate];
_currentProcessChain = nil;
@ -167,6 +171,36 @@
PGVideoMovie *movie = [[PGVideoMovie alloc] initWithPlayerItem:playerItem];
_currentInput = movie;
bool hasCropping = !CGPointEqualToPoint(cropRect.origin, CGPointZero) || (!CGSizeEqualToSize(cropRect.size, CGSizeZero) && !CGSizeEqualToSize(cropRect.size, _originalSize));
_rotationMode = kGPUImageNoRotation;
if (cropOrientation != UIImageOrientationUp || cropMirrored || hasCropping) {
CGRect normalizedCropRect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
if (hasCropping) {
normalizedCropRect = CGRectMake(cropRect.origin.x / _originalSize.width, cropRect.origin.y / _originalSize.height, cropRect.size.width / _originalSize.width, cropRect.size.height / _originalSize.height);
}
_cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:normalizedCropRect];
if (cropOrientation != UIImageOrientationUp || cropMirrored) {
switch (cropOrientation) {
case UIImageOrientationLeft:
_rotationMode = kGPUImageRotateLeft;
break;
case UIImageOrientationRight:
_rotationMode = cropMirrored ? kGPUImageRotateRightFlipHorizontal : kGPUImageRotateRight;
break;
case UIImageOrientationDown:
_rotationMode = kGPUImageRotate180;
break;
case UIImageOrientationUp:
if (cropMirrored)
_rotationMode = kGPUImageFlipHorizonal;
break;
default:
break;
}
}
}
_fullSize = true;
}
@ -341,7 +375,12 @@
_currentProcessChain = processChain;
GPUImageOutput <GPUImageInput> *lastFilter = ((PGPhotoProcessPass *)_currentProcessChain.firstObject).filter;
[_currentInput addTarget:lastFilter];
if (_cropFilter != nil) {
[_currentInput addTarget:_cropFilter];
[_cropFilter addTarget:lastFilter];
} else {
[_currentInput addTarget:lastFilter];
}
NSInteger chainLength = _currentProcessChain.count;
if (chainLength > 1)
@ -460,7 +499,7 @@
{
TGVideoEditAdjustments *initialAdjustments = (TGVideoEditAdjustments *)_initialAdjustments;
return [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:self.originalSize cropRect:self.cropRect cropOrientation:self.cropOrientation cropLockedAspectRatio:self.cropLockedAspectRatio cropMirrored:self.cropMirrored trimStartValue:initialAdjustments.trimStartValue trimEndValue:initialAdjustments.trimEndValue toolValues:toolValues paintingData:paintingData sendAsGif:self.sendAsGif preset:self.preset];
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 preset:self.preset];
}
}

View File

@ -8,6 +8,7 @@
@synthesize originalSize = _originalSize;
@synthesize cropRect = _cropRect;
@synthesize cropOrientation = _cropOrientation;
@synthesize cropRotation = _cropRotation;
@synthesize cropLockedAspectRatio = _cropLockedAspectRatio;
@synthesize cropMirrored = _cropMirrored;
@synthesize paintingData = _paintingData;

View File

@ -850,19 +850,14 @@
NSData *data = UIImageJPEGRepresentation(image, 0.8);
[data writeToFile:filePath atomically:true];
dict[@"url"] = [NSURL fileURLWithPath:filePath];
if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied)
{
CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height);
UIImage *paintingImage = adjustments.paintingData.stillImage;
if (paintingImage == nil) {
paintingImage = adjustments.paintingData.image;
}
if (adjustments.toolsApplied) {
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
}
UIImage *thumbnailImage = TGPhotoEditorVideoCrop(image, paintingImage, adjustments.cropOrientation, 0, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(384, 384)), adjustments.originalSize, true, true);
UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(384, 384)), adjustments.originalSize, true, true, true);
if (thumbnailImage != nil) {
dict[@"previewImage"] = thumbnailImage;
}

View File

@ -647,7 +647,9 @@
[_paintingImageCache setImage:image forKey:itemId attributes:NULL];
NSData *imageData = UIImagePNGRepresentation(image);
[[NSFileManager defaultManager] removeItemAtURL:imageUrl error:nil];
bool imageSuccess = [imageData writeToURL:imageUrl options:NSDataWritingAtomic error:nil];
[[NSFileManager defaultManager] removeItemAtURL:dataUrl error:nil];
bool dataSuccess = [data writeToURL:dataUrl options:NSDataWritingAtomic error:nil];
if (imageSuccess && imageOutUrl != NULL)
@ -664,6 +666,7 @@
[_stillPaintingImageCache setImage:stillImage forKey:itemId attributes:NULL];
NSData *stillImageData = UIImagePNGRepresentation(stillImage);
[[NSFileManager defaultManager] removeItemAtURL:stillImageUrl error:nil];
[stillImageData writeToURL:stillImageUrl options:NSDataWritingAtomic error:nil];
if (video)

View File

@ -1108,7 +1108,7 @@
strongSelf->_photoEditor = [[PGPhotoEditor alloc] initWithOriginalSize:strongSelf->_videoDimensions adjustments:adjustments forVideo:true enableStickers:true];
strongSelf->_photoEditor.previewOutput = strongSelf->_videoView;
[strongSelf->_photoEditor setPlayerItem:playerItem];
[strongSelf->_photoEditor setPlayerItem:playerItem forCropRect:CGRectZero cropRotation:0.0 cropOrientation:UIImageOrientationUp cropMirrored:false];
[strongSelf->_photoEditor processAnimated:false completion:nil];
[strongSelf _seekToPosition:adjustments.trimStartValue manual:false];
@ -1383,7 +1383,7 @@
[self updatePlayerRange:trimEndValue];
}
TGVideoEditAdjustments *updatedAdjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:_videoDimensions cropRect:cropRect cropOrientation:adjustments.cropOrientation cropLockedAspectRatio:adjustments.cropLockedAspectRatio cropMirrored:adjustments.cropMirrored trimStartValue:trimStartValue trimEndValue:trimEndValue toolValues:adjustments.toolValues paintingData:adjustments.paintingData sendAsGif:sendAsGif preset:adjustments.preset];
TGVideoEditAdjustments *updatedAdjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:_videoDimensions cropRect:cropRect cropOrientation:adjustments.cropOrientation cropRotation:adjustments.cropRotation cropLockedAspectRatio:adjustments.cropLockedAspectRatio cropMirrored:adjustments.cropMirrored trimStartValue:trimStartValue trimEndValue:trimEndValue toolValues:adjustments.toolValues paintingData:adjustments.paintingData sendAsGif:sendAsGif preset:adjustments.preset];
[self.item.editingContext setAdjustments:updatedAdjustments forItem:self.item.editableMediaItem];
if (sendAsGif)
@ -1475,7 +1475,7 @@
UIImageOrientation cropOrientation = (adjustments != nil) ? adjustments.cropOrientation : UIImageOrientationUp;
CGFloat cropLockedAspectRatio = (adjustments != nil) ? adjustments.cropLockedAspectRatio : 0.0f;
TGVideoEditAdjustments *updatedAdjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:_videoDimensions cropRect:cropRect cropOrientation:cropOrientation cropLockedAspectRatio:cropLockedAspectRatio cropMirrored:adjustments.cropMirrored trimStartValue:_scrubberView.trimStartValue trimEndValue:_scrubberView.trimEndValue toolValues:adjustments.toolValues paintingData:adjustments.paintingData sendAsGif:adjustments.sendAsGif preset:adjustments.preset];
TGVideoEditAdjustments *updatedAdjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:_videoDimensions cropRect:cropRect cropOrientation:cropOrientation cropRotation:adjustments.cropRotation cropLockedAspectRatio:cropLockedAspectRatio cropMirrored:adjustments.cropMirrored trimStartValue:_scrubberView.trimStartValue trimEndValue:_scrubberView.trimEndValue toolValues:adjustments.toolValues paintingData:adjustments.paintingData sendAsGif:adjustments.sendAsGif preset:adjustments.preset];
[self.item.editingContext setAdjustments:updatedAdjustments forItem:self.item.editableMediaItem];
}

View File

@ -154,7 +154,7 @@
}
}
if (![self setupAssetReaderWriterForAVAsset:avAsset image:nil outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error])
if (![self setupAssetReaderWriterForAVAsset:avAsset image:nil duration:0.0 outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error])
{
[subscriber putError:error];
return;
@ -212,7 +212,7 @@
}];
}
+ (SSignal *)renderUIImage:(UIImage *)image adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer
+ (SSignal *)renderUIImage:(UIImage *)image duration:(NSTimeInterval)duration adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer
{
SQueue *queue = [[SQueue alloc] init];
@ -248,7 +248,7 @@
}
}
if (![self setupAssetReaderWriterForAVAsset:avAsset image:image outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:true conversionContext:context error:&error])
if (![self setupAssetReaderWriterForAVAsset:avAsset image:image duration:duration outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:true conversionContext:context error:&error])
{
[subscriber putError:error];
return;
@ -350,18 +350,7 @@
UIImage *overlayImage = nil;
if (adjustments.paintingData.imagePath != nil)
overlayImage = [UIImage imageWithContentsOfFile:adjustments.paintingData.imagePath];
bool hasAnimation = false;
for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) {
if (entity.animated) {
hasAnimation = true;
break;
}
}
if (!hasAnimation) {
entityRenderer = nil;
}
AVMutableVideoComposition *videoComposition;
if (entityRenderer != nil || adjustments.toolsApplied) {
PGPhotoEditor *editor = nil;
@ -504,7 +493,7 @@
return output;
}
+ (bool)setupAssetReaderWriterForAVAsset:(AVAsset *)avAsset image:(UIImage *)image outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error
+ (bool)setupAssetReaderWriterForAVAsset:(AVAsset *)avAsset image:(UIImage *)image duration:(NSTimeInterval)duration outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error
{
if (image == nil) {
TGMediaSampleBufferProcessor *videoProcessor = nil;
@ -579,7 +568,7 @@
CGSize dimensions = CGSizeZero;
NSDictionary *outputSettings = nil;
CMTimeRange timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(0.0, NSEC_PER_SEC), CMTimeMakeWithSeconds(3.0, NSEC_PER_SEC));
CMTimeRange timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(0.0, NSEC_PER_SEC), CMTimeMakeWithSeconds(duration, NSEC_PER_SEC));
AVMutableComposition *composition = [AVMutableComposition composition];
AVAssetTrack *videoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];

View File

@ -46,6 +46,13 @@
return paintingData;
}
- (instancetype)dataForAnimation
{
TGPaintingData *paintingData = [[TGPaintingData alloc] init];
paintingData->_entities = _entities;
return paintingData;
}
+ (void)storePaintingData:(TGPaintingData *)data inContext:(TGMediaEditingContext *)context forItem:(id<TGMediaEditableItem>)item forVideo:(bool)video
{
[[TGPaintingData queue] dispatch:^

View File

@ -406,7 +406,7 @@
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
_player.muted = true;
[_photoEditor setPlayerItem:_playerItem];
[_photoEditor setPlayerItem:_playerItem forCropRect:_photoEditor.cropRect cropRotation:0.0 cropOrientation:_photoEditor.cropOrientation cropMirrored:_photoEditor.cropMirrored];
TGDispatchOnMainThread(^
{
@ -680,14 +680,17 @@
[photoEditor setImage:image forCropRect:photoEditor.cropRect cropRotation:photoEditor.cropRotation cropOrientation:photoEditor.cropOrientation cropMirrored:photoEditor.cropMirrored fullSize:true];
[photoEditor createResultImageWithCompletion:^(UIImage *result)
{
if (hasPainting)
{
result = TGPaintCombineCroppedImages(result, editorValues.paintingData.image, true, photoEditor.originalSize, photoEditor.cropRect, photoEditor.cropOrientation, photoEditor.cropRotation, photoEditor.cropMirrored);
[TGPaintingData facilitatePaintingData:editorValues.paintingData];
}
[subscriber putNext:result];
[subscriber putCompletion];
[_queue dispatch:^{
UIImage *final = result;
if (hasPainting)
{
final = TGPaintCombineCroppedImages(final, editorValues.paintingData.image, true, photoEditor.originalSize, photoEditor.cropRect, photoEditor.cropOrientation, photoEditor.cropRotation, photoEditor.cropMirrored);
[TGPaintingData facilitatePaintingData:editorValues.paintingData];
}
[subscriber putNext:final];
[subscriber putCompletion];
}];
}];
return nil;

View File

@ -217,7 +217,11 @@ UIImage *TGPhotoEditorCrop(UIImage *inputImage, UIImage *paintingImage, UIImageO
return TGPhotoEditorVideoCrop(inputImage, paintingImage, orientation, rotation, rect, mirrored, maxSize, originalSize, shouldResize, false);
}
UIImage *TGPhotoEditorVideoCrop(UIImage *inputImage, UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize, bool useImageSize)
UIImage *TGPhotoEditorVideoCrop(UIImage *inputImage, UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize, bool useImageSize) {
return TGPhotoEditorVideoExtCrop(inputImage, paintingImage, orientation, rotation, rect, mirrored, maxSize, originalSize, shouldResize, useImageSize, false);
}
UIImage *TGPhotoEditorVideoExtCrop(UIImage *inputImage, UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize, bool useImageSize, bool skipImageTransform)
{
if (iosMajorVersion() < 7)
return TGPhotoEditorLegacyCrop(inputImage, paintingImage, orientation, rotation, rect, mirrored, maxSize, shouldResize);
@ -244,16 +248,7 @@ UIImage *TGPhotoEditorVideoCrop(UIImage *inputImage, UIImage *paintingImage, UII
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillRect(context, CGRectMake(0, 0, outputImageSize.width, outputImageSize.height));
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGSize scales = CGSizeMake(fittedImageSize.width / rect.size.width, fittedImageSize.height / rect.size.height);
CGSize rotatedContentSize = TGRotatedContentSize(inputImage.size, rotation);
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, outputImageSize.width / 2, outputImageSize.height / 2);
transform = CGAffineTransformRotate(transform, TGRotationForOrientation(orientation));
transform = CGAffineTransformTranslate(transform, (rotatedContentSize.width / 2 - CGRectGetMidX(rect)) * scales.width, (rotatedContentSize.height / 2 - CGRectGetMidY(rect)) * scales.height);
transform = CGAffineTransformRotate(transform, rotation);
CGContextConcatCTM(context, transform);
UIImage *image = nil;
if (shouldResize)
{
@ -268,10 +263,25 @@ UIImage *TGPhotoEditorVideoCrop(UIImage *inputImage, UIImage *paintingImage, UII
image = inputImage;
}
if (skipImageTransform) {
[image drawInRect:CGRectMake(0.0, 0.0, outputImageSize.width, outputImageSize.height)];
}
CGSize scales = CGSizeMake(fittedImageSize.width / rect.size.width, fittedImageSize.height / rect.size.height);
CGSize rotatedContentSize = TGRotatedContentSize(inputImage.size, rotation);
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, outputImageSize.width / 2, outputImageSize.height / 2);
transform = CGAffineTransformRotate(transform, TGRotationForOrientation(orientation));
transform = CGAffineTransformTranslate(transform, (rotatedContentSize.width / 2 - CGRectGetMidX(rect)) * scales.width, (rotatedContentSize.height / 2 - CGRectGetMidY(rect)) * scales.height);
transform = CGAffineTransformRotate(transform, rotation);
CGContextConcatCTM(context, transform);
if (mirrored)
CGContextScaleCTM(context, -1.0f, 1.0f);
[image drawAtPoint:CGPointMake(-image.size.width / 2, -image.size.height / 2)];
if (!skipImageTransform) {
[image drawAtPoint:CGPointMake(-image.size.width / 2, -image.size.height / 2)];
}
if (paintingImage != nil)
{

View File

@ -37,8 +37,6 @@
#import "TGPhotoPaintActionsView.h"
#import "TGPhotoPaintSettingsView.h"
#import "TGPhotoStickersView.h"
#import "TGPhotoPaintSettingsWrapperView.h"
#import "TGPhotoBrushSettingsView.h"
#import "TGPhotoTextSettingsView.h"
@ -94,7 +92,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
TGPhotoPaintSettingsWrapperView *_settingsViewWrapper;
UIView<TGPhotoPaintPanelView> *_settingsView;
TGPhotoStickersView *_stickersView;
id<TGPhotoPaintStickersScreen> _stickersScreen;
bool _appeared;
@ -1005,84 +1003,28 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
- (void)presentStickersView
{
if (_stickersScreen != nil) {
[_stickersScreen restore];
return;
}
__weak TGPhotoPaintController *weakSelf = self;
_stickersContext.presentStickersController(^(id document, bool animated, UIView *view, CGRect rect) {
_stickersScreen = _stickersContext.presentStickersController(^(id document, bool animated, UIView *view, CGRect rect) {
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf != nil) {
// UIView *snapshot = [view snapshotViewAfterScreenUpdates:false];
[strongSelf createNewStickerWithDocument:document animated:animated transitionPoint:CGPointZero stickersView:nil snapshotView:nil];
[strongSelf createNewStickerWithDocument:document animated:animated transitionPoint:CGPointZero snapshotView:nil];
}
});
// if (_stickersView == nil)
// {
// TGPhotoStickersView *view = [[TGPhotoStickersView alloc] initWithContext:_context frame:self.view.bounds];
// view.parentViewController = self;
//
// __weak TGPhotoPaintController *weakSelf = self;
// __weak TGPhotoStickersView *weakStickersView = view;
// view.stickerSelected = ^(TGDocumentMediaAttachment *document, CGPoint transitionPoint, TGPhotoStickersView *stickersView, UIView *snapshotView)
// {
// __strong TGPhotoPaintController *strongSelf = weakSelf;
// if (strongSelf != nil)
// [strongSelf createNewStickerWithDocument:document transitionPoint:transitionPoint stickersView:stickersView snapshotView:snapshotView];
// };
// view.dismissed = ^
// {
// __strong TGPhotoStickersView *strongStickersView = weakStickersView;
// if (strongStickersView != nil)
// [strongStickersView removeFromSuperview];
// };
//
// _stickersView = view;
// }
//
// if ([_context currentSizeClass] == UIUserInterfaceSizeClassCompact)
// {
// _stickersView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// _stickersView.frame = self.view.bounds;
// _stickersView.safeAreaInset = self.controllerSafeAreaInset;
// [self.parentViewController.view addSubview:_stickersView];
// }
// else
// {
// _settingsView = _stickersView;
// [_stickersView sizeToFit];
//
// UIView *wrapper = [self settingsViewWrapper];
// wrapper.userInteractionEnabled = true;
// [wrapper addSubview:_stickersView];
//
// [self viewWillLayoutSubviews];
// }
//
// _stickersView.outerView = self.parentViewController.view;
// _stickersView.targetView = _contentWrapperView;
//
// [_stickersView present];
}
- (void)createNewStickerWithDocument:(id)document animated:(bool)animated transitionPoint:(CGPoint)transitionPoint stickersView:(TGPhotoStickersView *)stickersView snapshotView:(UIView *)snapshotView
- (void)createNewStickerWithDocument:(id)document animated:(bool)animated transitionPoint:(CGPoint)transitionPoint snapshotView:(UIView *)snapshotView
{
TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:document baseSize:[self _stickerBaseSizeForCurrentPainting] animated:animated];
[self _setStickerEntityPosition:entity];
TGPhotoStickerEntityView *stickerView = (TGPhotoStickerEntityView *)[_entitiesContainerView createEntityViewWithEntity:entity];
[self _commonEntityViewSetup:stickerView];
// stickerView.hidden = true;
CGFloat rotation = entity.angle - [self startRotation];
CGRect bounds = stickerView.realBounds;
CGPoint center = [stickerView.superview convertPoint:stickerView.center toView:self.parentViewController.view];
CGFloat scale = [[stickerView.superview.superview.layer valueForKeyPath:@"transform.scale.x"] floatValue];
CGRect targetFrame = CGRectMake(center.x - bounds.size.width * scale / 2.0f, center.y - bounds.size.height * scale / 2.0f, bounds.size.width * scale, bounds.size.height * scale);
[stickersView dismissWithSnapshotView:snapshotView startPoint:transitionPoint targetFrame:targetFrame targetRotation:rotation completion:^
{
stickerView.hidden = false;
[_entitySelectionView fadeIn];
}];
[self selectEntityView:stickerView];
_entitySelectionView.alpha = 0.0f;
@ -1802,6 +1744,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
{
_dismissing = true;
[_stickersScreen invalidate];
[_entitySelectionView removeFromSuperview];
_entitySelectionView = nil;
@ -2061,9 +2005,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if (_settingsView != nil)
[_settingsView setInterfaceOrientation:orientation];
_stickersView.safeAreaInset = safeAreaInset;
switch (orientation)
{
case UIInterfaceOrientationLandscapeLeft:
@ -2126,14 +2068,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if ([_context currentSizeClass] == UIUserInterfaceSizeClassRegular)
{
if ([_settingsView isKindOfClass:[TGPhotoStickersView class]])
{
_settingsView.frame = CGRectMake(_settingsViewWrapper.frame.size.width / 6 * 2 - 5 - _settingsView.frame.size.width / 2.0f, _settingsViewWrapper.frame.size.height - _settingsView.frame.size.height - TGPhotoEditorToolbarSize + 10.0f, _settingsView.frame.size.width, _settingsView.frame.size.height);
}
else
{
_settingsView.frame = CGRectMake(_settingsViewWrapper.frame.size.width / 2.0f - 10.0f, _settingsViewWrapper.frame.size.height - _settingsView.frame.size.height - TGPhotoEditorToolbarSize - 50.0f, _settingsView.frame.size.width, _settingsView.frame.size.height);
}
_settingsView.frame = CGRectMake(_settingsViewWrapper.frame.size.width / 2.0f - 10.0f, _settingsViewWrapper.frame.size.height - _settingsView.frame.size.height - TGPhotoEditorToolbarSize - 50.0f, _settingsView.frame.size.width, _settingsView.frame.size.height);
}
else
{

View File

@ -17,6 +17,7 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
@synthesize originalSize = _originalSize;
@synthesize cropRect = _cropRect;
@synthesize cropOrientation = _cropOrientation;
@synthesize cropRotation = _cropRotation;
@synthesize cropLockedAspectRatio = _cropLockedAspectRatio;
@synthesize cropMirrored = _cropMirrored;
@synthesize paintingData = _paintingData;
@ -26,6 +27,7 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
+ (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize
cropRect:(CGRect)cropRect
cropOrientation:(UIImageOrientation)cropOrientation
cropRotation:(CGFloat)cropRotation
cropLockedAspectRatio:(CGFloat)cropLockedAspectRatio
cropMirrored:(bool)cropMirrored
trimStartValue:(NSTimeInterval)trimStartValue
@ -39,6 +41,7 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
adjustments->_originalSize = originalSize;
adjustments->_cropRect = cropRect;
adjustments->_cropOrientation = cropOrientation;
adjustments->_cropRotation = cropRotation;
adjustments->_cropLockedAspectRatio = cropLockedAspectRatio;
adjustments->_cropMirrored = cropMirrored;
adjustments->_trimStartValue = trimStartValue;
@ -140,13 +143,12 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
}
adjustments->_cropRect = cropRect;
adjustments->_cropOrientation = values.cropOrientation;
adjustments->_cropRotation = values.cropRotation;
adjustments->_cropLockedAspectRatio = values.cropLockedAspectRatio;
adjustments->_cropMirrored = values.cropMirrored;
adjustments->_toolValues = values.toolValues;
adjustments->_paintingData = values.paintingData;
adjustments->_paintingData = [values.paintingData dataForAnimation];
adjustments->_sendAsGif = true;
adjustments->_preset = TGMediaVideoConversionPresetAnimation;
adjustments->_toolValues = values.toolValues;
return adjustments;
}
@ -157,11 +159,11 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
adjustments->_originalSize = _originalSize;
adjustments->_cropRect = _cropRect;
adjustments->_cropOrientation = _cropOrientation;
adjustments->_cropRotation = _cropRotation;
adjustments->_cropLockedAspectRatio = _cropLockedAspectRatio;
adjustments->_cropMirrored = _cropMirrored;
adjustments->_trimStartValue = _trimStartValue;
adjustments->_trimEndValue = _trimEndValue;
adjustments->_toolValues = _toolValues;
adjustments->_paintingData = _paintingData;
adjustments->_sendAsGif = _sendAsGif;
adjustments->_preset = preset;
@ -187,6 +189,7 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
dict[@"cropOrientation"] = @(self.cropOrientation);
dict[@"cropRotation"] = @(self.cropRotation);
if ([self cropAppliedForAvatar:false])
dict[@"cropRect"] = [NSValue valueWithCGRect:self.cropRect];
dict[@"cropMirrored"] = @(self.cropMirrored);
@ -279,6 +282,9 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
if (self.cropLockedAspectRatio > FLT_EPSILON)
return true;
if (ABS(self.cropRotation) > FLT_EPSILON)
return true;
if (self.cropOrientation != UIImageOrientationUp)
return true;
@ -334,6 +340,9 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
if (!_CGRectEqualToRectWithEpsilon(self.cropRect, adjustments.cropRect, [self _cropRectEpsilon]))
return false;
if (ABS(self.cropRotation - adjustments.cropRotation) > FLT_EPSILON)
return false;
if (self.cropOrientation != adjustments.cropOrientation)
return false;

View File

@ -980,7 +980,7 @@ typedef enum
if (trimStartValue > DBL_EPSILON || trimEndValue < _duration - DBL_EPSILON)
{
adjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:dimensions cropRect:CGRectMake(0.0f, 0.0f, dimensions.width, dimensions.height) cropOrientation:UIImageOrientationUp cropLockedAspectRatio:1.0 cropMirrored:false trimStartValue:trimStartValue trimEndValue:trimEndValue toolValues:nil paintingData:nil sendAsGif:false preset:TGMediaVideoConversionPresetVideoMessage];
adjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:dimensions cropRect:CGRectMake(0.0f, 0.0f, dimensions.width, dimensions.height) cropOrientation:UIImageOrientationUp cropRotation:0.0 cropLockedAspectRatio:1.0 cropMirrored:false trimStartValue:trimStartValue trimEndValue:trimEndValue toolValues:nil paintingData:nil sendAsGif:false preset:TGMediaVideoConversionPresetVideoMessage];
duration = trimEndValue - trimStartValue;
}

View File

@ -59,7 +59,7 @@ public enum LegacyAttachmentMenuMediaEditing {
case file
}
public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> Void, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController {
public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController {
let defaultVideoPreset = defaultVideoPresetForContext(context)
UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0")
@ -108,7 +108,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO
let paintStickersContext = LegacyPaintStickersContext(context: context)
paintStickersContext.presentStickersController = { completion in
presentStickers({ file, animated, view, rect in
return presentStickers({ file, animated, view, rect in
let coder = PostboxEncoder()
coder.encodeRootObject(file)
completion?(coder.makeData(), animated, view, rect)

View File

@ -19,12 +19,12 @@ public func guessMimeTypeByFileExtension(_ ext: String) -> String {
return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary"
}
public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, initialCaption: String, hasSchedule: Bool, presentWebSearch: (() -> Void)?, presentSelectionLimitExceeded: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> Void) {
public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, initialCaption: String, hasSchedule: Bool, presentWebSearch: (() -> Void)?, presentSelectionLimitExceeded: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?) {
let isSecretChat = peer.id.namespace == Namespaces.Peer.SecretChat
let paintStickersContext = LegacyPaintStickersContext(context: context)
paintStickersContext.presentStickersController = { completion in
presentStickers({ file, animated, view, rect in
return presentStickers({ file, animated, view, rect in
let coder = PostboxEncoder()
coder.encodeRootObject(file)
completion?(coder.makeData(), animated, view, rect)

View File

@ -45,6 +45,7 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity {
let file: TelegramMediaFile
let entity: TGPhotoPaintStickerEntity
let animated: Bool
let durationPromise = Promise<Double>()
var source: AnimatedStickerNodeSource?
var frameSource: AnimatedStickerFrameSource?
@ -81,6 +82,8 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity {
strongSelf.frameQueue = frameQueue
strongSelf.frameSource = frameSource
strongSelf.durationPromise.set(.single(Double(frameSource.frameCount) / Double(frameSource.frameRate)))
}
}
}))
@ -106,6 +109,10 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity {
self.disposable.dispose()
}
var duration: Signal<Double, NoError> {
return self.durationPromise.get()
}
private func render(width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType) -> CIImage? {
let calculatedBytesPerRow = (4 * Int(width) + 15) & (~15)
assert(bytesPerRow == calculatedBytesPerRow)
@ -160,6 +167,7 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity {
completion(image)
} else {
let _ = (self.imagePromise.get()
|> take(1)
|> deliverOn(self.queue)).start(next: { [weak self] image in
if let strongSelf = self {
strongSelf.cachedCIImage = CIImage(image: image)
@ -246,6 +254,33 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender
}
public func duration() -> Signal<Double, NoError> {
var durations: [Signal<Double, NoError>] = []
for entity in self.entities {
if let sticker = entity as? LegacyPaintStickerEntity, sticker.animated {
durations.append(sticker.duration)
}
}
func gcd(_ a: Int32, _ b: Int32) -> Int32 {
let remainder = a % b
if remainder != 0 {
return gcd(b, remainder)
} else {
return b
}
}
func lcm(_ x: Int32, _ y: Int32) -> Int32 {
return x / gcd(x, y) * y
}
return combineLatest(durations)
|> map { durations in
return min(6.0, Double(durations.reduce(1) { lcm(Int32($0), Int32($1)) }))
}
}
public func entities(for time: CMTime, size: CGSize, completion: (([CIImage]?) -> Void)!) {
let entities = self.entities
let maxSide = max(size.width, size.height)
@ -307,7 +342,7 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender
}
public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersContext {
public var presentStickersController: ((((Any?, Bool, UIView?, CGRect) -> Void)?) -> Void)!
public var presentStickersController: ((((Any?, Bool, UIView?, CGRect) -> Void)?) -> TGPhotoPaintStickersScreen?)!
private let context: AccountContext

View File

@ -106,7 +106,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
cropRect = CGRect(x: (size.width - shortestSide) / 2.0, y: (size.height - shortestSide) / 2.0, width: shortestSide, height: shortestSide)
}
adjustments = TGVideoEditAdjustments(originalSize: size, cropRect: cropRect, cropOrientation: .up, cropLockedAspectRatio: 1.0, cropMirrored: false, trimStartValue: 0.0, trimEndValue: 0.0, toolValues: nil, paintingData: nil, sendAsGif: false, preset: TGMediaVideoConversionPresetVideoMessage)
adjustments = TGVideoEditAdjustments(originalSize: size, cropRect: cropRect, cropOrientation: .up, cropRotation: 0.0, cropLockedAspectRatio: 1.0, cropMirrored: false, trimStartValue: 0.0, trimEndValue: 0.0, toolValues: nil, paintingData: nil, sendAsGif: false, preset: TGMediaVideoConversionPresetVideoMessage)
}
}
var finalDuration: Double = CMTimeGetSeconds(asset.duration)

View File

@ -6060,6 +6060,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return true
})
strongSelf.present(controller, in: .window(.root))
return controller
} else {
return nil
}
})
}
@ -6132,6 +6135,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return true
})
strongSelf.present(controller, in: .window(.root))
return controller
} else {
return nil
}
}, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
@ -6313,9 +6319,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}, presentTimerPicker: { [weak self] done in
if let strongSelf = self {
strongSelf.presentTimerPicker(style: .media, completion: { [weak self] time in
if let strongSelf = self {
done(time)
}
done(time)
})
}
}, presentStickers: { [weak self] completion in
@ -6325,6 +6329,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return true
})
strongSelf.present(controller, in: .window(.root))
return controller
} else {
return nil
}
})
controller.descriptionGenerator = legacyAssetPickerItemGenerator()

View File

@ -368,6 +368,7 @@ final class ChatMediaInputNodeInteraction {
var highlightedGifMode: ChatMediaInputGifMode = .recent
var previewedStickerPackItem: StickerPreviewPeekItem?
var appearanceTransition: CGFloat = 1.0
var displayStickerPlaceholder = true
init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) {
self.navigateToCollectionId = navigateToCollectionId

View File

@ -254,9 +254,12 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
let boundingSize = CGSize(width: sideSize, height: sideSize)
self.item = item
if self.currentState == nil || self.currentState!.0 !== item.account || self.currentState!.1 != item.stickerItem {
if !item.inputNodeInteraction.displayStickerPlaceholder {
self.removePlaceholder(animated: false)
}
if let dimensions = item.stickerItem.file.dimensions {
if item.stickerItem.file.isAnimatedSticker {
if self.animationNode == nil {

View File

@ -16,6 +16,7 @@ import PresentationDataUtils
import SearchBarNode
import UndoUI
import SegmentedControlNode
import LegacyComponents
private final class DrawingStickersScreenNode: ViewControllerTracingNode {
private let context: AccountContext
@ -274,7 +275,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}
})
self.inputNodeInteraction.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: true)
self.inputNodeInteraction.displayStickerPlaceholder = false
self.addSubnode(self.topPanel)
@ -288,8 +289,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
self.addSubnode(self.topSeparatorNode)
self.addSubnode(self.bottomSeparatorNode)
let trendingInteraction = TrendingPaneInteraction(installPack: { [weak self] info in
}, openPack: { [weak self] info in
let trendingInteraction = TrendingPaneInteraction(installPack: { info in
}, openPack: { info in
}, getItemIsPreviewed: { item in
return false
}, openSearch: {
@ -481,7 +482,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}
@objc private func cancelPressed() {
self.dismiss?()
self.animateOut()
}
private func setHighlightedItemCollectionId(_ collectionId: ItemCollectionId) {
@ -819,11 +820,16 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}
func animateIn() {
self.isUserInteractionEnabled = true
self.isHidden = false
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
func animateOut() {
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.isUserInteractionEnabled = false
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak self] _ in
self?.isHidden = true
})
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
@ -836,7 +842,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}
}
final class DrawingStickersScreen: ViewController {
final class DrawingStickersScreen: ViewController, TGPhotoPaintStickersScreen {
private let context: AccountContext
var selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
@ -896,6 +902,7 @@ final class DrawingStickersScreen: ViewController {
context: self.context,
selectSticker: { [weak self] file, sourceNode, sourceRect in
if let strongSelf = self, let selectSticker = strongSelf.selectSticker {
(strongSelf.displayNode as! DrawingStickersScreenNode).animateOut()
return selectSticker(file, sourceNode, sourceRect)
} else {
return false
@ -928,4 +935,12 @@ final class DrawingStickersScreen: ViewController {
self.controllerNode.containerLayoutUpdated(layout, navigationHeight: 0.0, transition: transition)
}
func restore() {
(self.displayNode as! DrawingStickersScreenNode).animateIn()
}
func invalidate() {
self.dismiss()
}
}

View File

@ -287,7 +287,13 @@ public func fetchVideoLibraryMediaResource(account: Account, resource: VideoLibr
}
}
let updatedSize = Atomic<Int>(value: 0)
let entityRenderer = adjustments.flatMap { LegacyPaintEntityRenderer(account: account, adjustments: $0) }
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
return LegacyPaintEntityRenderer(account: account, adjustments: adjustments)
} else {
return nil
}
}
let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in
var value = stat()
if stat(path, &value) == 0 {
@ -379,24 +385,47 @@ func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideo
}
}
let updatedSize = Atomic<Int>(value: 0)
let entityRenderer = adjustments.flatMap { LegacyPaintEntityRenderer(account: account, adjustments: $0) }
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
return LegacyPaintEntityRenderer(account: account, adjustments: adjustments)
} else {
return nil
}
}
let signal: SSignal
if filteredPath.contains(".jpg") {
if filteredPath.contains(".jpg"), let entityRenderer = entityRenderer {
if let data = try? Data(contentsOf: URL(fileURLWithPath: filteredPath), options: [.mappedRead]), let image = UIImage(data: data) {
signal = TGMediaVideoConverter.renderUIImage(image, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in
var value = stat()
if stat(path, &value) == 0 {
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
var range: Range<Int>?
let _ = updatedSize.modify { updatedSize in
range = updatedSize ..< Int(value.st_size)
return Int(value.st_size)
let durationSignal: SSignal = SSignal(generator: { subscriber in
let disposable = (entityRenderer.duration()).start(next: { duration in
subscriber?.putNext(duration)
subscriber?.putCompletion()
})
return SBlockDisposable(block: {
disposable.dispose()
})
})
signal = durationSignal.map(toSignal: { duration -> SSignal? in
if let duration = duration as? Double {
return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in
var value = stat()
if stat(path, &value) == 0 {
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
var range: Range<Int>?
let _ = updatedSize.modify { updatedSize in
range = updatedSize ..< Int(value.st_size)
return Int(value.st_size)
}
//print("size = \(Int(value.st_size)), range: \(range!)")
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
}
}
//print("size = \(Int(value.st_size)), range: \(range!)")
subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false))
}
}), entityRenderer: entityRenderer)!
} else {
return SSignal.single(nil)
}
}), entityRenderer: entityRenderer)!
})
} else {
signal = SSignal.single(nil)
}

View File

@ -11,7 +11,7 @@ import ShareController
import LegacyUI
import LegacyMediaPickerUI
func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> Void) {
func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
@ -65,7 +65,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
let paintStickersContext = LegacyPaintStickersContext(context: context)
paintStickersContext.presentStickersController = { completion in
presentStickers({ file, animated, view, rect in
return presentStickers({ file, animated, view, rect in
let coder = PostboxEncoder()
coder.encodeRootObject(file)
completion?(coder.makeData(), animated, view, rect)