Merge commit '5ecb819963619cdf0d4f9cadfff7380247303743'

This commit is contained in:
Ali 2023-09-16 10:49:58 +02:00
commit 4bf474bbe5
26 changed files with 847 additions and 138 deletions

View File

@ -9,13 +9,6 @@ import ComponentFlow
import LottieAnimationComponent
import ReactionSelectionNode
public func decodeDrawingEntities(data: Data) -> [DrawingEntity] {
if let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: data) {
return codableEntities.map { $0.entity }
}
return []
}
private func makeEntityView(context: AccountContext, entity: DrawingEntity) -> DrawingEntityView? {
if let entity = entity as? DrawingBubbleEntity {
return DrawingBubbleEntityView(context: context, entity: entity)

View File

@ -3170,6 +3170,7 @@ public final class DrawingToolsInteraction {
} else {
if self.isVideo {
entityView.seek(to: 0.0)
entityView.play()
}
entityView.animateInsertion()

View File

@ -34,6 +34,9 @@ typedef enum
- (CMTimeRange)trimTimeRange;
- (NSDictionary *)tintValue;
- (NSDictionary *)curvesValue;
- (bool)trimApplied;
- (bool)isCropAndRotationEqualWith:(id<TGMediaEditAdjustments>)adjustments;

View File

@ -19,8 +19,15 @@ void TGDispatchAfter(double delay, dispatch_queue_t queue, dispatch_block_t bloc
int deviceMemorySize();
int cpuCoreCount();
@interface UIColor (Int32)
- (int32_t)int32Value;
@end
#define UIColorRGB(rgb) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:1.0f])
#define UIColorARGB(rgb) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:(((rgb >> 24) & 0xff) / 255.0f)])
#define UIColorRGBA(rgb,a) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:a])
#define TGRestrictedToMainThread {if(![[NSThread currentThread] isMainThread]) TGLegacyLog(@"***** Warning: main thread-bound operation is running in background! *****");}

View File

@ -144,3 +144,27 @@ NSString *TGComponentsPathForResource(NSString *name, NSString *type) {
}
return [bundle pathForResource:name ofType:type];
}
@implementation UIColor (Int32)
- (int32_t)int32Value {
CGFloat red, green, blue, alpha;
if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) {
if ([self getWhite:&red alpha:&alpha]) {
green = red;
blue = red;
} else {
red = green = blue = alpha = 1.0;
}
}
int8_t r = (int8_t)(red * 255);
int8_t g = (int8_t)(green * 255);
int8_t b = (int8_t)(blue * 255);
int8_t a = (int8_t)(alpha * 255);
int32_t intValue = (a << 24) | (r << 16) | (g << 8) | b;
return intValue;
}
@end

View File

@ -31,6 +31,9 @@ typedef enum
@property (nonatomic, assign) PGCurvesType activeType;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
- (NSDictionary *)dictionary;
@end
@interface PGCurvesTool : PGPhotoTool

View File

@ -116,6 +116,41 @@ const NSUInteger PGCurveDataStep = 2;
return interpolatedPoints;
}
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (dictionary.count == 0) {
return nil;
}
PGCurvesValue *value = [[PGCurvesValue alloc] init];
if (dictionary[@"blacks"]) {
value.blacksLevel = [dictionary[@"blacks"] floatValue];
}
if (dictionary[@"shadows"]) {
value.shadowsLevel = [dictionary[@"shadows"] floatValue];
}
if (dictionary[@"midtones"]) {
value.midtonesLevel = [dictionary[@"midtones"] floatValue];
}
if (dictionary[@"highlights"]) {
value.highlightsLevel = [dictionary[@"highlights"] floatValue];
}
if (dictionary[@"whites"]) {
value.whitesLevel = [dictionary[@"whites"] floatValue];
}
return value;
}
- (NSDictionary *)dictionary {
return @{
@"blacks": @(self.blacksLevel),
@"shadows": @(self.shadowsLevel),
@"midtones": @(self.midtonesLevel),
@"highlights": @(self.highlightsLevel),
@"whites": @(self.whitesLevel)
};
}
@end
@implementation PGCurvesToolValue
@ -156,6 +191,36 @@ const NSUInteger PGCurveDataStep = 2;
return value;
}
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (dictionary.count == 0) {
return nil;
}
PGCurvesToolValue *value = [[PGCurvesToolValue alloc] init];
if (dictionary[@"luminance"]) {
value.luminanceCurve = [[PGCurvesValue alloc] initWithDictionary:dictionary[@"luminance"]];
}
if (dictionary[@"red"]) {
value.redCurve = [[PGCurvesValue alloc] initWithDictionary:dictionary[@"red"]];
}
if (dictionary[@"green"]) {
value.greenCurve = [[PGCurvesValue alloc] initWithDictionary:dictionary[@"green"]];
}
if (dictionary[@"blue"]) {
value.blueCurve = [[PGCurvesValue alloc] initWithDictionary:dictionary[@"blue"]];
}
return value;
}
- (NSDictionary *)dictionary {
return @{
@"luminance": self.luminanceCurve.dictionary,
@"red": self.redCurve.dictionary,
@"green": self.greenCurve.dictionary,
@"blue": self.blueCurve.dictionary
};
}
@end

View File

@ -49,6 +49,11 @@
[(PGPhotoSkinPass *)_pass setIntensity:value.floatValue / 100];
}
- (bool)isAvialableForVideo
{
return false;
}
- (bool)requiresFaces
{
return true;

View File

@ -2,13 +2,16 @@
@interface PGTintToolValue : NSObject <PGCustomToolValue>
@property (nonatomic, assign) UIColor *shadowsColor;
@property (nonatomic, assign) UIColor *highlightsColor;
@property (nonatomic, strong) UIColor *shadowsColor;
@property (nonatomic, strong) UIColor *highlightsColor;
@property (nonatomic, assign) CGFloat shadowsIntensity;
@property (nonatomic, assign) CGFloat highlightsIntensity;
@property (nonatomic, assign) bool editingHighlights;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
- (NSDictionary *)dictionary;
@end
@interface PGTintTool : PGPhotoTool

View File

@ -62,6 +62,36 @@
return value;
}
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (dictionary.count == 0) {
return nil;
}
PGTintToolValue *value = [[PGTintToolValue alloc] init];
if (dictionary[@"shadowsColor"]) {
value.shadowsColor = UIColorARGB([dictionary[@"shadowsColor"] intValue]);
}
if (dictionary[@"shadowsIntensity"]) {
value.shadowsIntensity = [dictionary[@"shadowsIntensity"] floatValue];
}
if (dictionary[@"highlightsColor"]) {
value.highlightsColor = UIColorARGB([dictionary[@"highlightsColor"] intValue]);
}
if (dictionary[@"highlightsIntensity"]) {
value.highlightsIntensity = [dictionary[@"highlightsIntensity"] floatValue];
}
return value;
}
- (NSDictionary *)dictionary {
return @{
@"shadowsColor": @(self.shadowsColor.int32Value),
@"shadowsIntensity": @(self.shadowsIntensity),
@"highlightsColor": @(self.highlightsColor.int32Value),
@"highlightsIntensity": @(self.highlightsIntensity)
};
}
@end

View File

@ -421,7 +421,8 @@
_videoDimensions = item.dimensions;
if (_entitiesView == nil) {
_entitiesView = [item.stickersContext drawingEntitiesViewWithSize:item.dimensions];
CGSize maxSize = CGSizeMake(1920.0, 1920.0);
_entitiesView = [item.stickersContext drawingEntitiesViewWithSize:TGFitSize(item.dimensions, maxSize)];
_entitiesView.hidden = true;
_entitiesView.userInteractionEnabled = false;
[_contentWrapperView addSubview:_entitiesView];
@ -755,7 +756,7 @@
if (self.bounds.size.width > self.bounds.size.height)
_containerView.frame = self.bounds;
else
_containerView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height - 44.0);
_containerView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
[self _layoutPlayerView];
@ -812,7 +813,6 @@
_paintingImageView.frame = _imageView.frame;
_videoView.frame = _imageView.frame;
CGSize originalSize = self.item.asset.originalSize;
CGSize rotatedCropSize = cropRect.size;
@ -831,8 +831,10 @@
_contentWrapperView.frame = CGRectMake(0.0f, 0.0f, _contentView.bounds.size.width, _contentView.bounds.size.height);
CGRect rect = [TGPhotoDrawingController fittedCropRect:cropRect originalSize:originalSize keepOriginalSize:true];
_entitiesView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height);
_entitiesView.transform = CGAffineTransformMakeRotation(0.0);
_entitiesView.bounds = CGRectMake(0, 0, rect.size.width, rect.size.height);
_entitiesView.center = CGPointMake(_contentWrapperView.bounds.size.width / 2.0, _contentWrapperView.bounds.size.height / 2.0);
CGFloat entitiesScale = _contentWrapperView.bounds.size.width / rect.size.width;
_entitiesView.transform = CGAffineTransformMakeScale(entitiesScale, entitiesScale);
CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoDrawingController maximumPaintingSize]);
CGSize rotatedSize = TGRotatedContentSize(fittedOriginalSize, 0.0);
@ -949,10 +951,12 @@
CGSize originalSize = _videoDimensions;
CGRect cropRect = CGRectMake(0, 0, _videoDimensions.width, _videoDimensions.height);
UIImageOrientation cropOrientation = UIImageOrientationUp;
bool cropMirrored = false;
if (adjustments != nil)
{
cropRect = adjustments.cropRect;
cropOrientation = adjustments.cropOrientation;
cropMirrored = adjustments.cropMirrored;
}
CGContextConcatCTM(UIGraphicsGetCurrentContext(), TGVideoCropTransformForOrientation(cropOrientation, _playerWrapperView.bounds.size, false));

View File

@ -786,7 +786,7 @@
+ (SSignal *)hashForAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments
{
if ([adjustments trimApplied] || [adjustments cropAppliedForAvatar:false] || adjustments.sendAsGif)
if ([adjustments trimApplied] || [adjustments cropAppliedForAvatar:false] || adjustments.sendAsGif || [adjustments toolsApplied] || [adjustments hasPainting])
return [SSignal single:nil];
NSURL *fileUrl = nil;

View File

@ -413,9 +413,9 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original";
if (saving)
{
CGFloat containerHeight = self.view.frame.size.height;
if (_forVideo && self.view.frame.size.width < self.view.frame.size.height) {
containerHeight -= 44.0;
}
// if (_forVideo && self.view.frame.size.width < self.view.frame.size.height) {
// containerHeight -= 44.0;
// }
CGSize fittedSize = TGScaleToSize(snapshotView.frame.size, self.view.frame.size);
targetFrame = CGRectMake((self.view.frame.size.width - fittedSize.width) / 2,
(containerHeight - fittedSize.height) / 2,

View File

@ -451,12 +451,7 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
- (CGRect)transitionOutSourceFrameForReferenceFrame:(CGRect)referenceFrame orientation:(UIInterfaceOrientation)orientation
{
bool hasOnScreenNavigation = false;
if (@available(iOS 11.0, *)) {
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || self.context.safeAreaInset.bottom > FLT_EPSILON;
}
CGRect containerFrame = [TGPhotoQualityController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoEditorQualityPanelSize hasOnScreenNavigation:hasOnScreenNavigation];
CGRect containerFrame = [TGPhotoQualityController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoEditorQualityPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation];
CGSize fittedSize = TGScaleToSize(referenceFrame.size, containerFrame.size);
CGRect sourceFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height);
@ -466,20 +461,7 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
- (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame
{
CGSize referenceSize = [self referenceViewSize];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
UIInterfaceOrientation orientation = self.interfaceOrientation;
#pragma clang diagnostic pop
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
orientation = UIInterfaceOrientationPortrait;
bool hasOnScreenNavigation = false;
if (@available(iOS 11.0, *)) {
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || self.context.safeAreaInset.bottom > FLT_EPSILON;
}
CGRect containerFrame = [TGPhotoQualityController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoEditorQualityPanelSize hasOnScreenNavigation:hasOnScreenNavigation];
CGRect containerFrame = [TGPhotoQualityController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoEditorQualityPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation];
CGSize fittedSize = TGScaleToSize(fromFrame.size, containerFrame.size);
CGRect toFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height);

View File

@ -9,6 +9,9 @@
#import "TGPhotoPaintStickerEntity.h"
#import "TGPhotoPaintTextEntity.h"
#import "PGTintTool.h"
#import "PGCurvesTool.h"
const NSTimeInterval TGVideoEditMinimumTrimmableDuration = 1.5;
const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
@ -91,6 +94,18 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
id value = dictionary[@"tools"][key];
if ([value isKindOfClass:[NSNumber class]]) {
tools[key] = value;
} else if ([value isKindOfClass:[NSDictionary class]]) {
if ([key isEqualToString:@"tint"]) {
PGTintToolValue *tintValue = [[PGTintToolValue alloc] initWithDictionary:value];
if (tintValue != nil) {
tools[key] = tintValue;
}
} else if ([key isEqualToString:@"curves"]) {
PGCurvesToolValue *curvesValues = [[PGCurvesToolValue alloc] initWithDictionary:value];
if (curvesValues != nil) {
tools[key] = curvesValues;
}
}
}
}
adjustments->_toolValues = tools;
@ -250,6 +265,10 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
id value = self.toolValues[key];
if ([value isKindOfClass:[NSNumber class]]) {
tools[key] = value;
} else if ([value isKindOfClass:[PGTintToolValue class]]) {
tools[key] = ((PGTintToolValue *)value).dictionary;
} else if ([value isKindOfClass:[PGCurvesToolValue class]]) {
tools[key] = ((PGCurvesToolValue *)value).dictionary;
}
}
dict[@"tools"] = tools;
@ -318,6 +337,70 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
return CMTimeRangeMake(CMTimeMakeWithSeconds(self.trimStartValue , NSEC_PER_SEC), CMTimeMakeWithSeconds((self.trimEndValue - self.trimStartValue), NSEC_PER_SEC));
}
- (NSDictionary *)tintValue {
PGTintToolValue *tintValue = self.toolValues[@"tint"];
if (tintValue != nil) {
return @{
@"shadowsColor": tintValue.shadowsColor,
@"shadowsIntensity": @(tintValue.shadowsIntensity),
@"highlightsColor": tintValue.highlightsColor,
@"highlightsIntensity": @(tintValue.highlightsIntensity)
};
} else {
return nil;
}
}
- (NSDictionary *)curvesValue {
PGCurvesToolValue *curvesValue = self.toolValues[@"curves"];
if (curvesValue != nil) {
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
PGCurvesValue *luminanceCurve = curvesValue.luminanceCurve;
if (luminanceCurve != nil) {
result[@"luminance"] = @{
@"blacks": @(luminanceCurve.blacksLevel),
@"shadows": @(luminanceCurve.shadowsLevel),
@"midtones": @(luminanceCurve.midtonesLevel),
@"highlights": @(luminanceCurve.highlightsLevel),
@"whites": @(luminanceCurve.whitesLevel)
};
}
PGCurvesValue *redCurve = curvesValue.redCurve;
if (redCurve != nil) {
result[@"red"] = @{
@"blacks": @(redCurve.blacksLevel),
@"shadows": @(redCurve.shadowsLevel),
@"midtones": @(redCurve.midtonesLevel),
@"highlights": @(redCurve.highlightsLevel),
@"whites": @(redCurve.whitesLevel)
};
}
PGCurvesValue *greenCurve = curvesValue.greenCurve;
if (greenCurve != nil) {
result[@"green"] = @{
@"blacks": @(greenCurve.blacksLevel),
@"shadows": @(greenCurve.shadowsLevel),
@"midtones": @(greenCurve.midtonesLevel),
@"highlights": @(greenCurve.highlightsLevel),
@"whites": @(greenCurve.whitesLevel)
};
}
PGCurvesValue *blueCurve = curvesValue.blueCurve;
if (blueCurve != nil) {
result[@"blue"] = @{
@"blacks": @(blueCurve.blacksLevel),
@"shadows": @(blueCurve.shadowsLevel),
@"midtones": @(blueCurve.midtonesLevel),
@"highlights": @(blueCurve.highlightsLevel),
@"whites": @(blueCurve.whitesLevel)
};
}
return result;
} else {
return nil;
}
}
- (bool)toolsApplied
{
if (self.toolValues.count > 0)

View File

@ -8,9 +8,9 @@ import AnimatedStickerNode
import TelegramAnimatedStickerNode
import YuvConversion
import StickerResources
import DrawingUI
import SolidRoundedButtonNode
import MediaEditor
import DrawingUI
protocol LegacyPaintEntity {
var position: CGPoint { get }

View File

@ -178,6 +178,11 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
self.scheduledMessageInput = nil
}
var hasTimer = self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser && !self.isScheduledMessages
if self.chatLocation.peerId?.isRepliesOrSavedMessages(accountPeerId: self.context.account.peerId) == true {
hasTimer = false
}
self.inputPanel.parentState = self.state
let inputPanelSize = self.inputPanel.update(
transition: Transition(transition),
@ -224,7 +229,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
self.toggleInputMode()
}
},
timeoutAction: self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser && !self.isScheduledMessages ? { [weak self] sourceView, gesture in
timeoutAction: hasTimer ? { [weak self] sourceView, gesture in
if let self {
self.presentTimeoutSetup(sourceView: sourceView, gesture: gesture)
}

View File

@ -1,6 +1,17 @@
import Foundation
import TelegramCore
public func decodeCodableDrawingEntities(data: Data) -> [CodableDrawingEntity] {
if let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: data) {
return codableEntities
}
return []
}
public func decodeDrawingEntities(data: Data) -> [DrawingEntity] {
return decodeCodableDrawingEntities(data: data).map { $0.entity }
}
public enum CodableDrawingEntity: Equatable {
public static func == (lhs: CodableDrawingEntity, rhs: CodableDrawingEntity) -> Bool {
return lhs.entity.isEqual(to: rhs.entity)

View File

@ -304,10 +304,11 @@ public final class MediaEditor {
peerId: context.account.peerId,
originalDimensions: subject.dimensions,
cropOffset: .zero,
cropSize: nil,
cropRect: nil,
cropScale: 1.0,
cropRotation: 0.0,
cropMirroring: false,
cropOrientation: nil,
gradientColors: nil,
videoTrimRange: nil,
videoIsMuted: false,
@ -325,7 +326,8 @@ public final class MediaEditor {
audioTrackTrimRange: nil,
audioTrackOffset: nil,
audioTrackVolume: nil,
audioTrackSamples: nil
audioTrackSamples: nil,
qualityPreset: nil
)
}
self.valuesPromise.set(.single(self.values))

View File

@ -121,9 +121,9 @@ final class MediaEditorComposer {
if let pixelBuffer {
processImage(inputImage: ciImage, time: time, completion: { compositedImage in
if var compositedImage {
let scale = self.outputDimensions.width / self.dimensions.width
let scale = self.outputDimensions.width / compositedImage.extent.width
compositedImage = compositedImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
self.ciContext?.render(compositedImage, to: pixelBuffer)
completion(pixelBuffer)
} else {
@ -160,9 +160,9 @@ final class MediaEditorComposer {
if let pixelBuffer, let context = self.ciContext {
makeEditorImageFrameComposition(context: context, inputImage: image, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, outputDimensions: self.outputDimensions, values: self.values, entities: self.entities, time: time, completion: { compositedImage in
if var compositedImage {
let scale = self.outputDimensions.width / self.dimensions.width
let scale = self.outputDimensions.width / compositedImage.extent.width
compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale))
self.ciContext?.render(compositedImage, to: pixelBuffer)
completion(pixelBuffer)
} else {
@ -218,24 +218,38 @@ public func makeEditorImageComposition(context: CIContext, postbox: Postbox, inp
private func makeEditorImageFrameComposition(context: CIContext, inputImage: CIImage, gradientImage: CIImage, drawingImage: CIImage?, dimensions: CGSize, outputDimensions: CGSize, values: MediaEditorValues, entities: [MediaEditorComposerEntity], time: CMTime, textScale: CGFloat = 1.0, completion: @escaping (CIImage?) -> Void) {
var resultImage = CIImage(color: .black).cropped(to: CGRect(origin: .zero, size: dimensions)).transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
resultImage = gradientImage.composited(over: resultImage)
if values.isStory {
resultImage = gradientImage.composited(over: resultImage)
}
var mediaImage = inputImage.samplingLinear().transformed(by: CGAffineTransform(translationX: -inputImage.extent.midX, y: -inputImage.extent.midY))
var initialScale: CGFloat
if mediaImage.extent.height > mediaImage.extent.width {
if mediaImage.extent.height > mediaImage.extent.width && values.isStory {
initialScale = max(dimensions.width / mediaImage.extent.width, dimensions.height / mediaImage.extent.height)
} else {
initialScale = dimensions.width / mediaImage.extent.width
}
var cropTransform = CGAffineTransform(translationX: values.cropOffset.x, y: values.cropOffset.y * -1.0)
cropTransform = cropTransform.rotated(by: -values.cropRotation)
cropTransform = cropTransform.scaledBy(x: initialScale * values.cropScale, y: initialScale * values.cropScale)
mediaImage = mediaImage.transformed(by: cropTransform)
resultImage = mediaImage.composited(over: resultImage)
if values.isStory {
var cropTransform: CGAffineTransform = CGAffineTransform(translationX: values.cropOffset.x, y: values.cropOffset.y * -1.0)
cropTransform = cropTransform.rotated(by: -values.cropRotation)
cropTransform = cropTransform.scaledBy(x: initialScale * values.cropScale, y: initialScale * values.cropScale)
mediaImage = mediaImage.transformed(by: cropTransform)
resultImage = mediaImage.composited(over: resultImage)
} else {
var horizontalScale = initialScale
if values.cropMirroring {
horizontalScale *= -1.0
}
mediaImage = mediaImage.transformed(by: CGAffineTransformMakeScale(horizontalScale, initialScale))
resultImage = mediaImage.composited(over: resultImage)
}
if let drawingImage {
if var drawingImage {
if values.isStory {
drawingImage = drawingImage.transformed(by: CGAffineTransformMakeScale(initialScale, initialScale))
}
resultImage = drawingImage.samplingLinear().composited(over: resultImage)
}
@ -254,7 +268,26 @@ private func makeEditorImageFrameComposition(context: CIContext, inputImage: CII
}
resultImage = resultImage.transformed(by: CGAffineTransform(translationX: dimensions.width / 2.0, y: dimensions.height / 2.0))
resultImage = resultImage.cropped(to: CGRect(origin: .zero, size: dimensions))
if values.isStory {
resultImage = resultImage.cropped(to: CGRect(origin: .zero, size: dimensions))
} else {
let originalDimensions = values.originalDimensions.cgSize
var cropRect = values.cropRect ?? .zero
if cropRect.isEmpty {
cropRect = CGRect(origin: .zero, size: originalDimensions)
}
let scale = dimensions.width / originalDimensions.width
let scaledCropRect = CGRect(origin: CGPoint(x: cropRect.minX * scale, y: dimensions.height - cropRect.maxY * scale), size: CGSize(width: cropRect.width * scale, height: cropRect.height * scale))
resultImage = resultImage.cropped(to: scaledCropRect)
resultImage = resultImage.transformed(by: CGAffineTransformMakeTranslation(-scaledCropRect.minX, -scaledCropRect.minY))
if let orientation = values.cropOrientation, orientation != .up {
let rotation = orientation.rotation
resultImage = resultImage.transformed(by: CGAffineTransformMakeTranslation(-resultImage.extent.width / 2.0, -resultImage.extent.height / 2.0))
resultImage = resultImage.transformed(by: CGAffineTransformMakeRotation(rotation))
resultImage = resultImage.transformed(by: CGAffineTransformMakeTranslation(resultImage.extent.width / 2.0, resultImage.extent.height / 2.0))
}
}
completion(resultImage)
}
}
@ -273,7 +306,6 @@ private func makeEditorImageFrameComposition(context: CIContext, inputImage: CII
if let scale = entity.baseScale {
baseScale = scale
} else if let _ = entity.baseDrawingSize {
// baseScale = textScale
} else if let baseSize = entity.baseSize {
baseScale = baseSize.width / image.extent.width
}

View File

@ -99,6 +99,130 @@ public struct MediaAudioTrackSamples: Equatable {
}
}
public enum MediaQualityPreset: Int32 {
case compressedDefault
case compressedVeryLow
case compressedLow
case compressedMedium
case compressedHigh
case compressedVeryHigh
case animation
case videoMessage
case profileLow
case profile
case profileHigh
case profileVeryHigh
case passthrough
var hasAudio: Bool {
switch self {
case .animation, .profileLow, .profile, .profileHigh, .profileVeryHigh:
return false
default:
return true
}
}
var maximumDimensions: CGFloat {
switch self {
case .compressedVeryLow:
return 480.0
case .compressedLow:
return 640.0
case .compressedMedium:
return 848.0
case .compressedHigh:
return 1280.0
case .compressedVeryHigh:
return 1920.0
case .videoMessage:
return 384.0
case .profileLow:
return 720.0
case .profile, .profileHigh, .profileVeryHigh:
return 800.0
default:
return 848.0
}
}
var videoBitrateKbps: Int {
switch self {
case .compressedVeryLow:
return 400
case .compressedLow:
return 700
case .compressedMedium:
return 1600
case .compressedHigh:
return 3000
case .compressedVeryHigh:
return 6600
case .videoMessage:
return 1000
case .profileLow:
return 1100
case .profile:
return 1500
case .profileHigh:
return 2000
case .profileVeryHigh:
return 2400
default:
return 900
}
}
var audioBitrateKbps: Int {
switch self {
case .compressedVeryLow, .compressedLow:
return 32
case .compressedMedium, .compressedHigh, .compressedVeryHigh, .videoMessage:
return 64
default:
return 0
}
}
var audioChannelsCount: Int {
switch self {
case .compressedVeryLow, .compressedLow:
return 1
default:
return 2
}
}
}
public enum MediaCropOrientation: Int32 {
case up
case down
case left
case right
var rotation: CGFloat {
switch self {
case .up:
return 0.0
case .down:
return .pi
case .left:
return .pi / 2.0
case .right:
return -.pi / 2.0
}
}
var isSideward: Bool {
switch self {
case .left, .right:
return true
default:
return false
}
}
}
public final class MediaEditorValues: Codable, Equatable {
public static func == (lhs: MediaEditorValues, rhs: MediaEditorValues) -> Bool {
if lhs.peerId != rhs.peerId {
@ -110,7 +234,7 @@ public final class MediaEditorValues: Codable, Equatable {
if lhs.cropOffset != rhs.cropOffset {
return false
}
if lhs.cropSize != rhs.cropSize {
if lhs.cropRect != rhs.cropRect {
return false
}
if lhs.cropScale != rhs.cropScale {
@ -122,6 +246,9 @@ public final class MediaEditorValues: Codable, Equatable {
if lhs.cropMirroring != rhs.cropMirroring {
return false
}
if lhs.cropOrientation != rhs.cropOrientation {
return false
}
if lhs.gradientColors != rhs.gradientColors {
return false
}
@ -207,45 +334,43 @@ public final class MediaEditorValues: Codable, Equatable {
private enum CodingKeys: String, CodingKey {
case peerId
case originalWidth
case originalHeight
case cropOffset
case cropSize
case cropRect
case cropScale
case cropRotation
case cropMirroring
case cropOrientation
case gradientColors
case videoTrimRange
case videoIsMuted
case videoIsFullHd
case videoIsMirrored
case additionalVideoPath
case additionalVideoPosition
case additionalVideoScale
case additionalVideoRotation
case additionalVideoPositionChanges
case drawing
case entities
case toolValues
case audioTrack
case audioTrackTrimRange
case audioTrackOffset
case audioTrackVolume
case qualityPreset
}
public let peerId: EnginePeer.Id
public let originalDimensions: PixelDimensions
public let cropOffset: CGPoint
public let cropSize: CGSize?
public let cropRect: CGRect?
public let cropScale: CGFloat
public let cropRotation: CGFloat
public let cropMirroring: Bool
public let cropOrientation: MediaCropOrientation?
public let gradientColors: [UIColor]?
@ -270,14 +395,21 @@ public final class MediaEditorValues: Codable, Equatable {
public let audioTrackVolume: CGFloat?
public let audioTrackSamples: MediaAudioTrackSamples?
init(
public let qualityPreset: MediaQualityPreset?
var isStory: Bool {
return self.qualityPreset == nil
}
public init(
peerId: EnginePeer.Id,
originalDimensions: PixelDimensions,
cropOffset: CGPoint,
cropSize: CGSize?,
cropRect: CGRect?,
cropScale: CGFloat,
cropRotation: CGFloat,
cropMirroring: Bool,
cropOrientation: MediaCropOrientation?,
gradientColors: [UIColor]?,
videoTrimRange: Range<Double>?,
videoIsMuted: Bool,
@ -295,15 +427,17 @@ public final class MediaEditorValues: Codable, Equatable {
audioTrackTrimRange: Range<Double>?,
audioTrackOffset: Double?,
audioTrackVolume: CGFloat?,
audioTrackSamples: MediaAudioTrackSamples?
audioTrackSamples: MediaAudioTrackSamples?,
qualityPreset: MediaQualityPreset?
) {
self.peerId = peerId
self.originalDimensions = originalDimensions
self.cropOffset = cropOffset
self.cropSize = cropSize
self.cropRect = cropRect
self.cropScale = cropScale
self.cropRotation = cropRotation
self.cropMirroring = cropMirroring
self.cropOrientation = cropOrientation
self.gradientColors = gradientColors
self.videoTrimRange = videoTrimRange
self.videoIsMuted = videoIsMuted
@ -322,6 +456,7 @@ public final class MediaEditorValues: Codable, Equatable {
self.audioTrackOffset = audioTrackOffset
self.audioTrackVolume = audioTrackVolume
self.audioTrackSamples = audioTrackSamples
self.qualityPreset = qualityPreset
}
public init(from decoder: Decoder) throws {
@ -334,10 +469,11 @@ public final class MediaEditorValues: Codable, Equatable {
self.originalDimensions = PixelDimensions(width: width, height: height)
self.cropOffset = try container.decode(CGPoint.self, forKey: .cropOffset)
self.cropSize = try container.decodeIfPresent(CGSize.self, forKey: .cropSize)
self.cropRect = try container.decodeIfPresent(CGRect.self, forKey: .cropRect)
self.cropScale = try container.decode(CGFloat.self, forKey: .cropScale)
self.cropRotation = try container.decode(CGFloat.self, forKey: .cropRotation)
self.cropMirroring = try container.decode(Bool.self, forKey: .cropMirroring)
self.cropOrientation = (try container.decodeIfPresent(Int32.self, forKey: .cropOrientation)).flatMap { MediaCropOrientation(rawValue: $0) }
if let gradientColors = try container.decodeIfPresent([DrawingColor].self, forKey: .gradientColors) {
self.gradientColors = gradientColors.map { $0.toUIColor() }
@ -378,6 +514,8 @@ public final class MediaEditorValues: Codable, Equatable {
self.audioTrackVolume = try container.decodeIfPresent(CGFloat.self, forKey: .audioTrackVolume)
self.audioTrackSamples = nil
self.qualityPreset = (try container.decodeIfPresent(Int32.self, forKey: .qualityPreset)).flatMap { MediaQualityPreset(rawValue: $0) }
}
public func encode(to encoder: Encoder) throws {
@ -389,10 +527,11 @@ public final class MediaEditorValues: Codable, Equatable {
try container.encode(self.originalDimensions.height, forKey: .originalHeight)
try container.encode(self.cropOffset, forKey: .cropOffset)
try container.encode(self.cropSize, forKey: .cropSize)
try container.encode(self.cropRect, forKey: .cropRect)
try container.encode(self.cropScale, forKey: .cropScale)
try container.encode(self.cropRotation, forKey: .cropRotation)
try container.encode(self.cropMirroring, forKey: .cropMirroring)
try container.encodeIfPresent(self.cropOrientation?.rawValue, forKey: .cropOrientation)
if let gradientColors = self.gradientColors {
try container.encode(gradientColors.map { DrawingColor(color: $0) }, forKey: .gradientColors)
@ -427,71 +566,73 @@ public final class MediaEditorValues: Codable, Equatable {
try container.encodeIfPresent(self.audioTrackTrimRange, forKey: .audioTrackTrimRange)
try container.encodeIfPresent(self.audioTrackOffset, forKey: .audioTrackOffset)
try container.encodeIfPresent(self.audioTrackVolume, forKey: .audioTrackVolume)
try container.encodeIfPresent(self.qualityPreset?.rawValue, forKey: .qualityPreset)
}
public func makeCopy() -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedVideoIsMirrored(_ videoIsMirrored: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedAdditionalVideo(path: String, positionChanges: [VideoPositionChange]) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: path, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: path, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: drawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: drawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrack(_ audioTrack: MediaAudioTrack?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrackTrimRange(_ audioTrackTrimRange: Range<Double>?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrackOffset(_ audioTrackOffset: Double?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrackVolume(_ audioTrackVolume: CGFloat?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrackSamples(_ audioTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, qualityPreset: self.qualityPreset)
}
public var resultDimensions: PixelDimensions {
@ -515,6 +656,9 @@ public final class MediaEditorValues: Codable, Equatable {
if self.cropMirroring {
return true
}
if (self.cropOrientation ?? .up) != .up {
return true
}
if self.videoTrimRange != nil {
return true
}
@ -527,6 +671,9 @@ public final class MediaEditorValues: Codable, Equatable {
if !self.toolValues.isEmpty {
return true
}
if self.audioTrack != nil {
return true
}
return false
}
}
@ -1267,46 +1414,89 @@ private let hasHEVCHardwareEncoder: Bool = {
return result == noErr
}()
func targetSize(cropSize: CGSize, rotateSideward: Bool = false) -> CGSize {
let blockSize: CGFloat = 16.0
var adjustedCropSize = cropSize
if rotateSideward {
adjustedCropSize = CGSize(width: cropSize.height, height: cropSize.width)
}
let renderWidth = (adjustedCropSize.width / blockSize).rounded(.down) * blockSize
let renderHeight = (adjustedCropSize.height * renderWidth / adjustedCropSize.width).rounded(.down)
// if fmod(renderHeight, blockSize) != 0 {
// renderHeight = (adjustedCropSize.height / blockSize).rounded(.down) * blockSize
// }
return CGSize(width: renderWidth, height: renderHeight)
}
public func recommendedVideoExportConfiguration(values: MediaEditorValues, duration: Double, image: Bool = false, forceFullHd: Bool = false, frameRate: Float) -> MediaEditorVideoExport.Configuration {
let compressionProperties: [String: Any]
let codecType: AVVideoCodecType
var bitrate: Int = 3700
var videoBitrate: Int = 3700
var audioBitrate: Int = 64
var audioNumberOfChannels = 2
if image {
bitrate = 5000
videoBitrate = 5000
} else {
if duration < 10 {
bitrate = 5800
videoBitrate = 5800
} else if duration < 20 {
bitrate = 5500
videoBitrate = 5500
} else if duration < 30 {
bitrate = 5000
videoBitrate = 5000
}
}
if hasHEVCHardwareEncoder {
let width: Int
let height: Int
var useHEVC = hasHEVCHardwareEncoder
if let qualityPreset = values.qualityPreset {
let maxSize = CGSize(width: qualityPreset.maximumDimensions, height: qualityPreset.maximumDimensions)
var resultSize = values.originalDimensions.cgSize
if let cropRect = values.cropRect, !cropRect.isEmpty {
resultSize = targetSize(cropSize: cropRect.size.aspectFitted(maxSize), rotateSideward: values.cropOrientation?.isSideward ?? false)
} else {
resultSize = targetSize(cropSize: resultSize.aspectFitted(maxSize), rotateSideward: values.cropOrientation?.isSideward ?? false)
}
width = Int(resultSize.width)
height = Int(resultSize.height)
videoBitrate = qualityPreset.videoBitrateKbps
audioBitrate = qualityPreset.audioBitrateKbps
audioNumberOfChannels = qualityPreset.audioChannelsCount
useHEVC = false
} else {
if values.videoIsFullHd {
width = 1080
height = 1920
} else {
width = 720
height = 1280
}
}
if useHEVC {
codecType = AVVideoCodecType.hevc
compressionProperties = [
AVVideoAverageBitRateKey: bitrate * 1000,
AVVideoAverageBitRateKey: videoBitrate * 1000,
AVVideoProfileLevelKey: kVTProfileLevel_HEVC_Main_AutoLevel
]
} else {
codecType = AVVideoCodecType.h264
compressionProperties = [
AVVideoAverageBitRateKey: bitrate * 1000,
AVVideoAverageBitRateKey: videoBitrate * 1000,
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
]
}
let width: Int
let height: Int
if values.videoIsFullHd {
width = 1080
height = 1920
} else {
width = 720
height = 1280
}
let videoSettings: [String: Any] = [
AVVideoCodecKey: codecType,
@ -1318,8 +1508,8 @@ public func recommendedVideoExportConfiguration(values: MediaEditorValues, durat
let audioSettings: [String: Any] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 44100,
AVEncoderBitRateKey: 64000,
AVNumberOfChannelsKey: 2
AVEncoderBitRateKey: audioBitrate * 1000,
AVNumberOfChannelsKey: audioNumberOfChannels
]
return MediaEditorVideoExport.Configuration(

View File

@ -188,8 +188,8 @@ public final class MediaEditorVideoAVAssetWriter: MediaEditorVideoExportWriter {
public final class MediaEditorVideoExport {
public enum Subject {
case image(UIImage)
case video(AVAsset)
case image(image: UIImage)
case video(asset: AVAsset, isStory: Bool)
}
public struct Configuration {
@ -247,7 +247,12 @@ public final class MediaEditorVideoExport {
}
var composerDimensions: CGSize {
return CGSize(width: 1080.0, height: 1920.0)
if self.values.isStory {
return CGSize(width: 1080.0, height: 1920.0)
} else {
let maxSize = CGSize(width: 1920.0, height: 1920.0)
return targetSize(cropSize: self.values.originalDimensions.cgSize.aspectFitted(maxSize))
}
}
var dimensions: CGSize {
@ -351,12 +356,12 @@ public final class MediaEditorVideoExport {
}
private func setup() {
if case let .video(asset) = self.subject {
if case let .video(asset, isStory) = self.subject {
if let trimmedVideoDuration = self.configuration.timeRange?.duration {
self.durationValue = trimmedVideoDuration
} else {
asset.loadValuesAsynchronously(forKeys: ["tracks", "duration"]) {
if asset.duration.seconds > 60.0 {
if asset.duration.seconds > 60.0 && isStory {
self.durationValue = CMTime(seconds: 60.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
} else {
self.durationValue = asset.duration
@ -368,7 +373,7 @@ public final class MediaEditorVideoExport {
}
switch self.subject {
case let .video(asset):
case let .video(asset, _):
var additionalAsset: AVAsset?
if let additionalPath = self.configuration.values.additionalVideoPath {
additionalAsset = AVURLAsset(url: URL(fileURLWithPath: additionalPath))

View File

@ -4834,15 +4834,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
switch subject {
case let .video(path, _, _, _, _, _, _, _, _):
let asset = AVURLAsset(url: NSURL(fileURLWithPath: path) as URL)
exportSubject = .single(.video(asset))
exportSubject = .single(.video(asset: asset, isStory: true))
case let .image(image, _, _, _):
exportSubject = .single(.image(image))
exportSubject = .single(.image(image: image))
case let .asset(asset):
exportSubject = Signal { subscriber in
if asset.mediaType == .video {
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
if let avAsset {
subscriber.putNext(.video(avAsset))
subscriber.putNext(.video(asset: avAsset, isStory: true))
subscriber.putCompletion()
}
}
@ -4851,7 +4851,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
options.deliveryMode = .highQualityFormat
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
if let image {
subscriber.putNext(.image(image))
subscriber.putNext(.image(image: image))
subscriber.putCompletion()
}
}
@ -4861,10 +4861,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
case let .draft(draft, _):
if draft.isVideo {
let asset = AVURLAsset(url: NSURL(fileURLWithPath: draft.fullPath(engine: context.engine)) as URL)
exportSubject = .single(.video(asset))
exportSubject = .single(.video(asset: asset, isStory: true))
} else {
if let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) {
exportSubject = .single(.image(image))
exportSubject = .single(.image(image: image))
} else {
fatalError()
}
@ -4876,7 +4876,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return
}
var duration: Double = 0.0
if case let .video(video) = exportSubject {
if case let .video(video, _) = exportSubject {
duration = video.duration.seconds
}
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0)

View File

@ -460,7 +460,7 @@ final class VideoScrubberComponent: Component {
let updatedPosition: Double
if let (start, from, to, _) = self.positionAnimation {
var from = from
if let offset = component.audioData?.offset {
if component.audioOnly, let offset = component.audioData?.offset {
from -= offset
}
let duration = to - from
@ -471,13 +471,13 @@ final class VideoScrubberComponent: Component {
}
} else {
var position = component.position
if let offset = component.audioData?.offset {
if component.audioOnly, let offset = component.audioData?.offset {
position -= offset
}
let advance = component.isPlaying ? timestamp - component.generationTimestamp : 0.0
updatedPosition = max(component.startPosition, min(component.endPosition, position + advance))
}
let cursorHeight: CGFloat = component.audioData != nil ? 80.0 : 50.0
let cursorHeight: CGFloat = component.audioData != nil && !component.audioOnly ? 80.0 : 50.0
self.cursorView.frame = cursorFrame(size: scrubberSize, height: cursorHeight, position: updatedPosition, duration: trimDuration)
}
@ -489,6 +489,7 @@ final class VideoScrubberComponent: Component {
var trimDuration = component.duration
var isFirstTime = false
var animateAudioAppearance = false
if let previousComponent {
if previousComponent.audioData == nil, component.audioData != nil {
@ -499,6 +500,8 @@ final class VideoScrubberComponent: Component {
self.isAudioSelected = false
animateAudioAppearance = true
}
} else {
isFirstTime = true
}
let scrubberSpacing: CGFloat = 4.0
@ -587,6 +590,11 @@ final class VideoScrubberComponent: Component {
audioTransition.setFrame(view: self.audioScrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: audioScrubberHeight)))
self.audioScrollView.contentSize = CGSize(width: audioTotalWidth, height: audioScrubberHeight)
if isFirstTime, let offset = component.audioData?.offset, let duration = component.audioData?.duration, duration > 0.0 {
let contentOffset = offset * audioTotalWidth / duration
self.audioScrollView.contentOffset = CGPoint(x: contentOffset, y: 0.0)
}
audioTransition.setCornerRadius(layer: self.audioClippingView.layer, cornerRadius: self.isAudioSelected ? 0.0 : 9.0)
let audioContainerFrame = CGRect(origin: .zero, size: CGSize(width: audioTotalWidth, height: audioScrubberHeight))
@ -797,7 +805,7 @@ final class VideoScrubberComponent: Component {
self.positionAnimation = nil
self.displayLink?.isPaused = true
let cursorHeight: CGFloat = component.audioData != nil ? 80.0 : 50.0
let cursorHeight: CGFloat = component.audioData != nil && !component.audioOnly ? 80.0 : 50.0
var cursorPosition = component.position
if component.audioOnly, let audioOffset = component.audioData?.offset {
cursorPosition -= audioOffset

View File

@ -208,7 +208,7 @@ private final class FetchVideoLibraryMediaResourceContext {
private let throttlingContext = FetchVideoLibraryMediaResourceContext()
public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibraryMediaResource) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibraryMediaResource, alwaysUseModernPipeline: Bool = true) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
let signal = Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
subscriber.putNext(.reset)
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [resource.localIdentifier], options: nil)
@ -248,7 +248,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr
Logger.shared.log("FetchVideoResource", "Requesting video export")
let configuration = recommendedVideoExportConfiguration(values: mediaEditorValues, duration: 5.0, image: true, frameRate: 30.0)
let videoExport = MediaEditorVideoExport(postbox: postbox, subject: .image(image), configuration: configuration, outputPath: tempFile.path)
let videoExport = MediaEditorVideoExport(postbox: postbox, subject: .image(image: image), configuration: configuration, outputPath: tempFile.path)
videoExport.start()
let statusDisposable = videoExport.status.start(next: { status in
@ -299,6 +299,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr
let options = PHVideoRequestOptions()
options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
let dimensions = PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight))
requestId = PHImageManager.default().requestAVAsset(forVideo: asset, options: options, resultHandler: { avAsset, _, _ in
if alreadyReceivedAsset.swap(true) {
return
@ -307,6 +308,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr
return
}
var isStory = false
var adjustments: TGVideoEditAdjustments?
var mediaEditorValues: MediaEditorValues?
switch resource.conversion {
@ -324,13 +326,24 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr
adjustments = nil
}
case let .compress(adjustmentsValue):
let defaultPreset = TGMediaVideoConversionPreset(rawValue: UInt32(UserDefaults.standard.integer(forKey: "TG_preferredVideoPreset_v0")))
let qualityPreset = MediaQualityPreset(preset: defaultPreset)
if let adjustmentsValue = adjustmentsValue {
if adjustmentsValue.isStory {
isStory = true
if let values = try? JSONDecoder().decode(MediaEditorValues.self, from: adjustmentsValue.data.makeData()) {
mediaEditorValues = values
}
} else if let dict = NSKeyedUnarchiver.unarchiveObject(with: adjustmentsValue.data.makeData()) as? [AnyHashable : Any] {
adjustments = TGVideoEditAdjustments(dictionary: dict)
} else if let dict = NSKeyedUnarchiver.unarchiveObject(with: adjustmentsValue.data.makeData()) as? [AnyHashable : Any], let legacyAdjustments = TGVideoEditAdjustments(dictionary: dict) {
if alwaysUseModernPipeline {
mediaEditorValues = MediaEditorValues(legacyAdjustments: legacyAdjustments, defaultPreset: qualityPreset)
} else {
adjustments = legacyAdjustments
}
}
} else {
if alwaysUseModernPipeline {
mediaEditorValues = MediaEditorValues(dimensions: dimensions, qualityPreset: qualityPreset)
}
}
}
@ -339,7 +352,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr
if let mediaEditorValues {
let duration: Double = avAsset.duration.seconds
let configuration = recommendedVideoExportConfiguration(values: mediaEditorValues, duration: duration, frameRate: 30.0)
let videoExport = MediaEditorVideoExport(postbox: postbox, subject: .video(avAsset), configuration: configuration, outputPath: tempFile.path)
let videoExport = MediaEditorVideoExport(postbox: postbox, subject: .video(asset: avAsset, isStory: isStory), configuration: configuration, outputPath: tempFile.path)
videoExport.start()
let statusDisposable = videoExport.status.start(next: { status in
@ -464,7 +477,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr
return throttlingContext.wrap(priority: .default, signal: signal)
}
public func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideoMediaResource) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
public func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideoMediaResource, alwaysUseModernPipeline: Bool = true) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
let signal = Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
subscriber.putNext(.reset)
@ -473,16 +486,31 @@ public func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFi
filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)])
}
let defaultPreset = TGMediaVideoConversionPreset(rawValue: UInt32(UserDefaults.standard.integer(forKey: "TG_preferredVideoPreset_v0")))
let qualityPreset = MediaQualityPreset(preset: defaultPreset)
let isImage = filteredPath.contains(".jpg")
var isStory = false
let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath))
var adjustments: TGVideoEditAdjustments?
var mediaEditorValues: MediaEditorValues?
if let videoAdjustments = resource.adjustments {
if videoAdjustments.isStory {
isStory = true
if let values = try? JSONDecoder().decode(MediaEditorValues.self, from: videoAdjustments.data.makeData()) {
mediaEditorValues = values
}
} else if let dict = NSKeyedUnarchiver.unarchiveObject(with: videoAdjustments.data.makeData()) as? [AnyHashable : Any] {
adjustments = TGVideoEditAdjustments(dictionary: dict)
} else if let dict = NSKeyedUnarchiver.unarchiveObject(with: videoAdjustments.data.makeData()) as? [AnyHashable : Any], let legacyAdjustments = TGVideoEditAdjustments(dictionary: dict) {
if alwaysUseModernPipeline && !isImage {
mediaEditorValues = MediaEditorValues(legacyAdjustments: legacyAdjustments, defaultPreset: qualityPreset)
} else {
adjustments = legacyAdjustments
}
}
} else {
if alwaysUseModernPipeline && !isImage, let track = avAsset.tracks(withMediaType: .video).first {
let dimensions = track.naturalSize.applying(track.preferredTransform)
mediaEditorValues = MediaEditorValues(dimensions: PixelDimensions(dimensions), qualityPreset: qualityPreset)
}
}
let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
@ -491,10 +519,10 @@ public func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFi
let duration: Double = avAsset.duration.seconds
let configuration = recommendedVideoExportConfiguration(values: mediaEditorValues, duration: duration, frameRate: 30.0)
let subject: MediaEditorVideoExport.Subject
if filteredPath.contains(".jpg"), let data = try? Data(contentsOf: URL(fileURLWithPath: filteredPath), options: [.mappedRead]), let image = UIImage(data: data) {
subject = .image(image)
if isImage, let data = try? Data(contentsOf: URL(fileURLWithPath: filteredPath), options: [.mappedRead]), let image = UIImage(data: data) {
subject = .image(image: image)
} else {
subject = .video(avAsset)
subject = .video(asset: avAsset, isStory: isStory)
}
let videoExport = MediaEditorVideoExport(postbox: postbox, subject: subject, configuration: configuration, outputPath: tempFile.path)
@ -556,7 +584,7 @@ public func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFi
}
}
let signal: SSignal
if filteredPath.contains(".jpg"), let entityRenderer = entityRenderer {
if isImage, let entityRenderer = entityRenderer {
if let data = try? Data(contentsOf: URL(fileURLWithPath: filteredPath), options: [.mappedRead]), let image = UIImage(data: data) {
let durationSignal: SSignal = SSignal(generator: { subscriber in
let disposable = (entityRenderer.duration()).start(next: { duration in
@ -687,8 +715,8 @@ public func fetchVideoLibraryMediaResourceHash(resource: VideoLibraryMediaResour
adjustments = nil
case let .compress(adjustmentsValue):
if let adjustmentsValue = adjustmentsValue {
if let dict = NSKeyedUnarchiver.unarchiveObject(with: adjustmentsValue.data.makeData()) as? [AnyHashable : Any] {
adjustments = TGVideoEditAdjustments(dictionary: dict)
if let dict = NSKeyedUnarchiver.unarchiveObject(with: adjustmentsValue.data.makeData()) as? [AnyHashable : Any], let legacyAdjustments = TGVideoEditAdjustments(dictionary: dict) {
adjustments = legacyAdjustments
}
}
}
@ -765,3 +793,220 @@ public func fetchLocalFileGifMediaResource(resource: LocalFileGifMediaResource)
}
}
}
private extension MediaQualityPreset {
init(preset: TGMediaVideoConversionPreset) {
var qualityPreset: MediaQualityPreset
switch preset {
case TGMediaVideoConversionPresetCompressedDefault:
qualityPreset = .compressedDefault
case TGMediaVideoConversionPresetCompressedVeryLow:
qualityPreset = .compressedVeryLow
case TGMediaVideoConversionPresetCompressedLow:
qualityPreset = .compressedLow
case TGMediaVideoConversionPresetCompressedMedium:
qualityPreset = .compressedMedium
case TGMediaVideoConversionPresetCompressedHigh:
qualityPreset = .compressedHigh
case TGMediaVideoConversionPresetCompressedVeryHigh:
qualityPreset = .compressedVeryHigh
case TGMediaVideoConversionPresetProfileLow:
qualityPreset = .profileLow
case TGMediaVideoConversionPresetProfile:
qualityPreset = .profile
case TGMediaVideoConversionPresetProfileHigh:
qualityPreset = .profileHigh
case TGMediaVideoConversionPresetProfileVeryHigh:
qualityPreset = .profileVeryHigh
case TGMediaVideoConversionPresetAnimation:
qualityPreset = .animation
case TGMediaVideoConversionPresetVideoMessage:
qualityPreset = .videoMessage
default:
qualityPreset = .compressedMedium
}
self = qualityPreset
}
}
private extension UIImage.Orientation {
var cropOrientation: MediaCropOrientation {
switch self {
case .up:
return .up
case .down:
return .down
case .left:
return .left
case .right:
return .right
default:
return .up
}
}
}
private extension MediaEditorValues {
convenience init(dimensions: PixelDimensions, qualityPreset: MediaQualityPreset) {
self.init(
peerId: EnginePeer.Id(0),
originalDimensions: dimensions,
cropOffset: .zero,
cropRect: nil,
cropScale: 1.0,
cropRotation: 0.0,
cropMirroring: false,
cropOrientation: nil,
gradientColors: nil,
videoTrimRange: nil,
videoIsMuted: false,
videoIsFullHd: true,
videoIsMirrored: false,
additionalVideoPath: nil,
additionalVideoPosition: nil,
additionalVideoScale: nil,
additionalVideoRotation: nil,
additionalVideoPositionChanges: [],
drawing: nil,
entities: [],
toolValues: [:],
audioTrack: nil,
audioTrackTrimRange: nil,
audioTrackOffset: nil,
audioTrackVolume: nil,
audioTrackSamples: nil,
qualityPreset: qualityPreset
)
}
convenience init(legacyAdjustments: TGVideoEditAdjustments, defaultPreset: MediaQualityPreset) {
var videoTrimRange: Range<Double>?
if legacyAdjustments.trimStartValue > 0.0 || !legacyAdjustments.trimEndValue.isZero {
videoTrimRange = legacyAdjustments.trimStartValue ..< legacyAdjustments.trimEndValue
}
var entities: [CodableDrawingEntity] = []
var drawing: UIImage?
if let paintingData = legacyAdjustments.paintingData {
if let entitiesData = paintingData.entitiesData {
entities = decodeCodableDrawingEntities(data: entitiesData)
}
if let imagePath = paintingData.imagePath, let image = UIImage(contentsOfFile: imagePath) {
drawing = image
}
}
var toolValues: [EditorToolKey: Any] = [:]
if let tools = legacyAdjustments.toolValues {
for (key, value) in tools {
if let floatValue = (value as? NSNumber)?.floatValue {
if key == AnyHashable("enhance") {
toolValues[.enhance] = floatValue / 100.0
}
if key == AnyHashable("exposure") {
toolValues[.brightness] = floatValue / 100.0
}
if key == AnyHashable("contrast") {
toolValues[.contrast] = floatValue / 100.0
}
if key == AnyHashable("saturation") {
toolValues[.saturation] = floatValue / 100.0
}
if key == AnyHashable("warmth") {
toolValues[.warmth] = floatValue / 100.0
}
if key == AnyHashable("fade") {
toolValues[.fade] = floatValue / 100.0
}
if key == AnyHashable("vignette") {
toolValues[.vignette] = floatValue / 100.0
}
if key == AnyHashable("grain") {
toolValues[.grain] = floatValue / 100.0
}
if key == AnyHashable("highlights") {
toolValues[.highlights] = floatValue / 100.0
}
if key == AnyHashable("shadows") {
toolValues[.shadows] = floatValue / 100.0
}
}
}
}
if let value = legacyAdjustments.tintValue() {
let shadowsColor = value["shadowsColor"] as? UIColor
let shadowsIntensity = (value["shadowsIntensity"] as? NSNumber)?.floatValue
let highlightsColor = value["highlightsColor"] as? UIColor
let highlightsIntensity = (value["highlightsIntensity"] as? NSNumber)?.floatValue
if let shadowsColor, let shadowsIntensity, shadowsColor.alpha > 0.0 {
let shadowsTintValue = TintValue(color: shadowsColor, intensity: shadowsIntensity / 100.0)
toolValues[.shadowsTint] = shadowsTintValue
}
if let highlightsColor, let highlightsIntensity, highlightsColor.alpha > 0.0 {
let highlightsTintValue = TintValue(color: highlightsColor, intensity: highlightsIntensity / 100.0)
toolValues[.highlightsTint] = highlightsTintValue
}
}
if let value = legacyAdjustments.curvesValue() {
func readValue(_ key: String) -> CurvesValue.CurveValue? {
if let values = value[key] as? [AnyHashable: Any] {
if let blacks = values["blacks"] as? NSNumber, let shadows = values["shadows"] as? NSNumber, let midtones = values["midtones"] as? NSNumber, let highlights = values["highlights"] as? NSNumber, let whites = values["whites"] as? NSNumber {
return CurvesValue.CurveValue(
blacks: blacks.floatValue / 100.0,
shadows: shadows.floatValue / 100.0,
midtones: midtones.floatValue / 100.0,
highlights: highlights.floatValue / 100.0,
whites: whites.floatValue / 100.0
)
}
}
return nil
}
if let all = readValue("luminance"), let red = readValue("red"), let green = readValue("green"), let blue = readValue("blue") {
toolValues[.curves] = CurvesValue(
all: all,
red: red,
green: green,
blue: blue
)
}
}
var qualityPreset = MediaQualityPreset(preset: legacyAdjustments.preset)
if qualityPreset == .compressedDefault {
qualityPreset = defaultPreset
}
self.init(
peerId: EnginePeer.Id(0),
originalDimensions: PixelDimensions(legacyAdjustments.originalSize),
cropOffset: .zero,
cropRect: legacyAdjustments.cropRect,
cropScale: 1.0,
cropRotation: legacyAdjustments.cropRotation,
cropMirroring: legacyAdjustments.cropMirrored,
cropOrientation: legacyAdjustments.cropOrientation.cropOrientation,
gradientColors: nil,
videoTrimRange: videoTrimRange,
videoIsMuted: legacyAdjustments.sendAsGif,
videoIsFullHd: true,
videoIsMirrored: false,
additionalVideoPath: nil,
additionalVideoPosition: nil,
additionalVideoScale: nil,
additionalVideoRotation: nil,
additionalVideoPositionChanges: [],
drawing: drawing,
entities: entities,
toolValues: toolValues,
audioTrack: nil,
audioTrackTrimRange: nil,
audioTrackOffset: nil,
audioTrackVolume: nil,
audioTrackSamples: nil,
qualityPreset: qualityPreset
)
}
}

View File

@ -409,7 +409,15 @@ public final class TextFieldComponent: Component {
}
public func textViewDidBeginEditing(_ textView: UITextView) {
guard let component = self.component else {
return
}
self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)).withUserData(AnimationHint(kind: .textFocusChanged)))
if component.isOneLineWhenUnfocused {
Queue.mainQueue().justDispatch {
self.textView.selectedTextRange = self.textView.textRange(from: self.textView.endOfDocument, to: self.textView.endOfDocument)
}
}
}
public func textViewDidEndEditing(_ textView: UITextView) {