From ef782bbda32cc79dc967befbbfdf39675d729624 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Jul 2020 00:57:12 +0300 Subject: [PATCH 1/5] Video avatar fixes --- submodules/GalleryUI/Sources/GalleryPagerNode.swift | 4 ++++ .../Sources/PeerAvatarImageGalleryItem.swift | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/submodules/GalleryUI/Sources/GalleryPagerNode.swift b/submodules/GalleryUI/Sources/GalleryPagerNode.swift index b51506c3e2..be4e797c59 100644 --- a/submodules/GalleryUI/Sources/GalleryPagerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryPagerNode.swift @@ -343,6 +343,10 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest } self.transaction(GalleryPagerTransaction(deleteItems: deleteItems, insertItems: insertItems, updateItems: updateItems, focusOnItem: centralItemIndex, synchronous: synchronous)) + + if self.updateOnReplacement { + self.items = items + } } public func transaction(_ transaction: GalleryPagerTransaction) { diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index 7872f0fdfb..cbb2b1134e 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -43,8 +43,6 @@ private struct PeerAvatarImageGalleryThumbnailItem: GalleryThumbnailItem { } class PeerAvatarImageGalleryItem: GalleryItem { - - var id: AnyHashable { return self.entry.id } From bc78f7f7fed458455be179e6ab7f1f77fdee2d58 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Jul 2020 10:02:09 +0300 Subject: [PATCH 2/5] Video avatar fixes --- .../LegacyComponents/TGPhotoEditorUtils.h | 4 +- .../Sources/TGAttachmentCarouselItemView.m | 3 +- .../Sources/TGCameraController.m | 33 ++++++- .../Sources/TGMediaAssetsController.m | 2 +- .../Sources/TGMediaAssetsPickerController.m | 3 +- .../Sources/TGPhotoEditorUtils.m | 98 ++++++++++++++++++- .../Sources/PhotoResources.swift | 12 +-- .../TelegramUI/Sources/ChatController.swift | 2 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 10 +- .../Sources/LegacyWebSearchGallery.swift | 2 +- 10 files changed, 142 insertions(+), 27 deletions(-) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h index f5837b4dd0..948ccab2ff 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h @@ -14,9 +14,11 @@ 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 *TGPhotoEditorVideoExtCrop(UIImage *inputImage, UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize, bool useImageSize, bool skipImageTransform, bool fillPainting); UIImage *TGPhotoEditorFitImage(UIImage *image, CGSize maxSize); CGSize TGRotatedContentSize(CGSize contentSize, CGFloat rotation); + +UIImage *TGPhotoEditorPaintingCrop(UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize, bool useImageSize, bool skipImageTransform); UIImageOrientation TGNextCWOrientationForOrientation(UIImageOrientation orientation); UIImageOrientation TGNextCCWOrientationForOrientation(UIImageOrientation orientation); diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m index 37da1a71a1..3aa9bcda19 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m @@ -928,7 +928,8 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; if (paintingImage == nil) { paintingImage = adjustments.paintingData.image; } - UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(resultImage, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(800, 800)), adjustments.originalSize, true, true, true); + UIImage *croppedPaintingImage = TGPhotoEditorPaintingCrop(paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, resultImage.size, adjustments.originalSize, true, true, false); + UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(resultImage, croppedPaintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(800, 800)), adjustments.originalSize, true, true, true, true); if (thumbnailImage != nil) { previewImage = thumbnailImage; } diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index 638153c61f..8482896730 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -1751,9 +1751,34 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus TGDispatchOnMainThread(^ { - if (strongSelf.finishedWithPhoto != nil) - strongSelf.finishedWithPhoto(nil, resultImage, nil, nil, nil, nil); - + if (editorValues.paintingData.hasAnimation) { + TGVideoEditAdjustments *adjustments = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)editorValues preset:TGMediaVideoConversionPresetProfileVeryHigh]; + + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"gifvideo_%x.jpg", (int)arc4random()]]; + NSData *data = UIImageJPEGRepresentation(resultImage, 0.8); + [data writeToFile:filePath atomically:true]; + + UIImage *previewImage = resultImage; + if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied) + { + UIImage *paintingImage = adjustments.paintingData.stillImage; + if (paintingImage == nil) { + paintingImage = adjustments.paintingData.image; + } + UIImage *croppedPaintingImage = TGPhotoEditorPaintingCrop(paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, resultImage.size, adjustments.originalSize, true, true, false); + UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(resultImage, croppedPaintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(resultImage.size, CGSizeMake(800, 800)), adjustments.originalSize, true, true, true, true); + if (thumbnailImage != nil) { + previewImage = thumbnailImage; + } + } + + if (strongSelf.finishedWithVideo != nil) + strongSelf.finishedWithVideo(nil, [NSURL fileURLWithPath:filePath], previewImage, 0, CGSizeZero, adjustments, nil, nil, nil, nil); + } else { + if (strongSelf.finishedWithPhoto != nil) + strongSelf.finishedWithPhoto(nil, resultImage, nil, nil, nil, nil); + } + if (strongSelf.shouldStoreCapturedAssets && [input isKindOfClass:[UIImage class]]) { [strongSelf _savePhotoToCameraRollWithOriginalImage:image editedImage:[editorValues toolsApplied] ? resultImage : nil]; @@ -2608,7 +2633,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus if (paintingImage == nil) { paintingImage = adjustments.paintingData.image; } - UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(image.size, CGSizeMake(512, 512)), adjustments.originalSize, true, true, true); + UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(image.size, CGSizeMake(512, 512)), adjustments.originalSize, true, true, true, false); if (thumbnailImage != nil) { dict[@"previewImage"] = thumbnailImage; } diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 7c295a7652..956d31fb69 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -920,7 +920,7 @@ if (paintingImage == nil) { paintingImage = adjustments.paintingData.image; } - UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(512, 512)), adjustments.originalSize, true, true, true); + UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(512, 512)), adjustments.originalSize, true, true, true, false); if (thumbnailImage != nil) { dict[@"previewImage"] = thumbnailImage; } diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m index 28e2407007..61c68aec85 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m @@ -442,7 +442,8 @@ if (paintingImage == nil) { paintingImage = adjustments.paintingData.image; } - UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(resultImage, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(800, 800)), adjustments.originalSize, true, true, true); + UIImage *croppedPaintingImage = TGPhotoEditorPaintingCrop(paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, resultImage.size, adjustments.originalSize, true, true, false); + UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(resultImage, croppedPaintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(800, 800)), adjustments.originalSize, true, true, true, true); if (thumbnailImage != nil) { previewImage = thumbnailImage; } diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m b/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m index 91dd667420..c2a2882fe3 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m @@ -218,10 +218,10 @@ UIImage *TGPhotoEditorCrop(UIImage *inputImage, UIImage *paintingImage, UIImageO } 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); + return TGPhotoEditorVideoExtCrop(inputImage, paintingImage, orientation, rotation, rect, mirrored, maxSize, originalSize, shouldResize, useImageSize, false, 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) +UIImage *TGPhotoEditorVideoExtCrop(UIImage *inputImage, UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize, bool useImageSize, bool skipImageTransform, bool fillPainting) { if (iosMajorVersion() < 7) return TGPhotoEditorLegacyCrop(inputImage, paintingImage, orientation, rotation, rect, mirrored, maxSize, shouldResize); @@ -251,6 +251,9 @@ UIImage *TGPhotoEditorVideoExtCrop(UIImage *inputImage, UIImage *paintingImage, UIGraphicsBeginImageContextWithOptions(CGSizeMake(outputImageSize.width, outputImageSize.height), true, 1.0f); CGContextRef context = UIGraphicsGetCurrentContext(); + + CGContextSaveGState(context); + CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor); CGContextFillRect(context, CGRectMake(0, 0, outputImageSize.width, outputImageSize.height)); CGContextSetInterpolationQuality(context, kCGInterpolationHigh); @@ -297,6 +300,97 @@ UIImage *TGPhotoEditorVideoExtCrop(UIImage *inputImage, UIImage *paintingImage, [image drawAtPoint:CGPointMake(-image.size.width / 2, -image.size.height / 2)]; } + if (paintingImage != nil) + { + if (fillPainting) { + CGContextRestoreGState(context); + [paintingImage drawInRect:CGRectMake(0.0, 0.0, outputImageSize.width, outputImageSize.height)]; + } else { + if (mirrored) + CGContextScaleCTM(context, -1.0f, 1.0f); + + [paintingImage drawInRect:CGRectMake(-imageSize.width / 2, -imageSize.height / 2, imageSize.width, imageSize.height)]; + } + } + + UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return croppedImage; +} + +UIImage *TGPhotoEditorPaintingCrop(UIImage *paintingImage, UIImageOrientation orientation, CGFloat rotation, CGRect rect, bool mirrored, CGSize maxSize, CGSize originalSize, bool shouldResize, bool useImageSize, bool skipImageTransform) +{ + CGSize fittedOriginalSize = originalSize; + if (useImageSize) + { + CGFloat ratio = paintingImage.size.width / originalSize.width; + if (skipImageTransform) { + + } + rect.origin.x = rect.origin.x * ratio; + rect.origin.y = rect.origin.y * ratio; + rect.size.width = rect.size.width * ratio; + rect.size.height = rect.size.height * ratio; + + fittedOriginalSize = CGSizeMake(originalSize.width * ratio, originalSize.height * ratio); + } + + CGSize fittedImageSize = shouldResize ? TGFitSize(rect.size, maxSize) : rect.size; + + CGSize outputImageSize = fittedImageSize; + outputImageSize.width = CGFloor(outputImageSize.width); + outputImageSize.height = CGFloor(outputImageSize.height); + if (TGOrientationIsSideward(orientation, NULL)) + outputImageSize = CGSizeMake(outputImageSize.height, outputImageSize.width); + + UIGraphicsBeginImageContextWithOptions(CGSizeMake(outputImageSize.width, outputImageSize.height), false, 1.0f); + CGContextRef context = UIGraphicsGetCurrentContext(); + + UIImage *image = nil; + CGSize imageSize = paintingImage.size; + if (shouldResize) + { + CGSize referenceSize = useImageSize ? paintingImage.size : originalSize; + CGSize resizedSize = CGSizeMake(referenceSize.width * fittedImageSize.width / rect.size.width, referenceSize.height * fittedImageSize.height / rect.size.height); + + UIGraphicsBeginImageContextWithOptions(resizedSize, false, 1.0f); + [image drawInRect:CGRectMake(0, 0, resizedSize.width, resizedSize.height) blendMode:kCGBlendModeCopy alpha:1.0f]; + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + if (skipImageTransform) { + imageSize = CGSizeMake(image.size.width * fittedOriginalSize.width / rect.size.width, image.size.height * fittedOriginalSize.height / rect.size.height); + } else { + imageSize = image.size; + } + } + else + { + image = paintingImage; + imageSize = image.size; + } + + 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(paintingImage.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); + + if (!skipImageTransform) { + [image drawAtPoint:CGPointMake(-image.size.width / 2, -image.size.height / 2)]; + } + if (paintingImage != nil) { if (mirrored) diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index bde7d61772..53316eba0e 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -2218,16 +2218,14 @@ public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepr blurredThumbnailImage = UIImage(cgImage: thumbnailImage) } else { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) - - let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 100.0, height: 100.0)) + let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 90.0, height: 90.0)) let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: false) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { @@ -2236,7 +2234,7 @@ public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepr if thumbnailContextFittingSize.width > thumbnailContextSize.width { let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0, clear: false) + let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) additionalBlurContext.withFlippedContext { c in c.interpolationQuality = .default if let image = thumbnailContext.generateImage()?.cgImage { @@ -2261,7 +2259,7 @@ public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepr c.setBlendMode(.copy) if let blurredThumbnailImage = blurredThumbnailImage, let cgImage = blurredThumbnailImage.cgImage { - c.interpolationQuality = .medium + c.interpolationQuality = .default drawImage(context: c, image: cgImage, orientation: imageOrientation, in: fittedRect) c.setBlendMode(.normal) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 364109e5e5..2f9da9a78d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -5070,7 +5070,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.preloadAvatarDisposable.set((peerInfoProfilePhotosWithCache(context: context, peerId: peerId) |> mapToSignal { result -> Signal in var signals: [Signal] = [.complete()] - for i in 0 ..< min(5, result.count) { + for i in 0 ..< min(1, result.count) { if let video = result[i].videoRepresentations.first { let duration: Double = (video.representation.startTimestamp ?? 0.0) + (i == 0 ? 4.0 : 2.0) signals.append(preloadVideoResource(postbox: context.account.postbox, resourceReference: video.reference, duration: duration)) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index e58d4507ff..567095af9c 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -1706,13 +1706,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode { func animateAvatarCollapse(transition: ContainedViewLayoutTransition) { if let currentItemNode = self.listContainerNode.currentItemNode, case .animated = transition { if let _ = self.avatarContainerNode.videoNode { -// if self.listContainerNode.currentIndex > 0 { -// transition.updateAlpha(node: currentItemNode, alpha: 0.0, completion: { _ in -// Queue.mainQueue().after(0.1, { -// currentItemNode.alpha = 1.0 -// }) -// }) -// } + } else if let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage { let avatarCopyView = UIImageView() avatarCopyView.image = unroundedImage @@ -2858,7 +2852,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 10.0 + (subtitleSize.height.isZero ? 11.0 : 0.0)), size: titleSize) let totalSubtitleWidth = subtitleSize.width + usernameSpacing + usernameSize.width - twoLineInfo = true // totalSubtitleWidth > width - textSideInset * 2.0 + twoLineInfo = true if usernameSize.width == 0.0 || twoLineInfo { subtitleFrame = CGRect(origin: CGPoint(x: floor((width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) usernameFrame = CGRect(origin: CGPoint(x: floor((width - usernameSize.width) / 2.0), y: subtitleFrame.maxY + 1.0), size: usernameSize) diff --git a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift b/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift index 99e7fab29a..3d5f8e1149 100644 --- a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift +++ b/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift @@ -459,7 +459,7 @@ public func legacyEnqueueWebSearchMessages(_ selectionState: TGMediaSelectionCon paintingImage = adjustments.paintingData?.image } - let thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(image.size, CGSize(width: 512.0, height: 512.0)), adjustments.originalSize, true, true, true) + let thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(image.size, CGSize(width: 512.0, height: 512.0)), adjustments.originalSize, true, true, true, false) if let thumbnailImage = thumbnailImage { dict["previewImage"] = thumbnailImage } From a911b403ca28a1f46180a113b0d2bbfc5c60798c Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Jul 2020 12:34:40 +0300 Subject: [PATCH 3/5] Video avatar fixes --- .../Sources/TGMediaVideoConverter.m | 2 +- .../Sources/ChatMessageActionItemNode.swift | 4 +- ...atMessageInteractiveInstantVideoNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 102 ++++++++++++++++-- 4 files changed, 97 insertions(+), 13 deletions(-) diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index a42bb2737a..f2387de000 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -1356,7 +1356,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, return 2000; case TGMediaVideoConversionPresetProfileVeryHigh: - return 2500; + return 2300; default: return 900; diff --git a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift index 00fae31474..e12c9fc369 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift @@ -147,7 +147,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } } - let imageSize = layoutConstants.instantVideo.dimensions + let imageSize = CGSize(width: 212.0, height: 212.0) let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) @@ -222,7 +222,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { if let image = image, let video = image.videoRepresentations.last, let id = image.id?.id { let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: image.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) - let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) + let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager let videoNode = UniversalVideoNode(postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 7b34b53c9e..87c3688d60 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -519,7 +519,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } var isBuffering: Bool? - if let message = self.item?.message, let media = self.media, let size = media.size, (isMediaStreamable(message: message, media: media) || size <= 256 * 1024) && (self.automaticDownload ?? false) { + if let message = self.item?.message, let media = self.media, isMediaStreamable(message: message, media: media) && (self.automaticDownload ?? false) { if let playerStatus = self.playerStatus, case .buffering = playerStatus.status { isBuffering = true } else { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 567095af9c..b60fabd78a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -198,8 +198,13 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { private var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? private var videoStartTimestamp: Double? - private let playbackStatusDisposable = MetaDisposable() + private let playbackStartDisposable = MetaDisposable() + private let statusDisposable = MetaDisposable() private let preloadDisposable = MetaDisposable() + private var statusNode: RadialStatusNode? + + private var fetchStatus: MediaResourceStatus? + private var playerStatus: MediaPlayerStatus? let isReady = Promise() private var didSetReady: Bool = false @@ -229,7 +234,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { self.preloadDisposable.set(nil) } else { if let videoNode = self.videoNode { - self.playbackStatusDisposable.set(nil) + self.playbackStartDisposable.set(nil) self.statusPromise.set(.single(nil)) self.videoNode = nil if self.delayCentralityLose { @@ -262,10 +267,76 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { } deinit { - self.playbackStatusDisposable.dispose() + self.statusDisposable.dispose() + self.playbackStartDisposable.dispose() self.preloadDisposable.dispose() } + private func updateStatus() { + guard let videoContent = self.videoContent, let fetchStatus = self.fetchStatus else { + return + } + + var isBuffering: Bool? + var isPlaying = false + if isMediaStreamable(resource: videoContent.fileReference.media.resource) { + if let playerStatus = self.playerStatus { + if case .buffering = playerStatus.status { + isBuffering = true + } else if case .playing = playerStatus.status { + isPlaying = true + } + } else { + isBuffering = false + } + } + + var progressRequired = false + if case .Local = fetchStatus { + } else if isBuffering ?? false { + progressRequired = true + } else if case .Fetching = fetchStatus, !isPlaying { + progressRequired = true + } else if case .Remote = fetchStatus, !isPlaying { + progressRequired = true + } + + if progressRequired { + if self.statusNode == nil { + let statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.3)) + statusNode.isUserInteractionEnabled = false + statusNode.frame = CGRect(origin: CGPoint(x: floor((self.frame.size.width - 50.0) / 2.0), y: floor((self.frame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0)) + self.statusNode = statusNode + self.addSubnode(statusNode) + } + } else { + if let statusNode = self.statusNode { + statusNode.transitionToState(.none, completion: { [weak statusNode] in + statusNode?.removeFromSupernode() + }) + self.statusNode = nil + } + } + + var state: RadialStatusNodeState + if progressRequired { + state = .progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false) + } else { + state = .none + } + + if let statusNode = self.statusNode { + if state == .none { + self.statusNode = nil + } + statusNode.transitionToState(state, completion: { [weak statusNode] in + if state == .none { + statusNode?.removeFromSupernode() + } + }) + } + } + func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) { if let videoNode = self.videoNode { if case .immediate = transition, fraction == 1.0 { @@ -287,7 +358,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { videoNode.isHidden = true if let _ = self.videoStartTimestamp { - self.playbackStatusDisposable.set((videoNode.status + self.playbackStartDisposable.set((videoNode.status |> map { status -> Bool in if let status = status, case .playing = status.status { return true @@ -307,7 +378,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { } })) } else { - self.playbackStatusDisposable.set(nil) + self.playbackStartDisposable.set(nil) videoNode.isHidden = false } videoNode.play() @@ -316,6 +387,17 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { let videoStartTimestamp = self.videoStartTimestamp self.statusPromise.set(videoNode.status |> map { ($0, videoStartTimestamp) }) + self.statusDisposable.set((combineLatest(self.mediaStatus, self.context.account.postbox.mediaBox.resourceStatus(videoContent.fileReference.media.resource)) + |> deliverOnMainQueue).start(next: { [weak self] mediaStatus, fetchStatus in + if let strongSelf = self { + if let mediaStatusAndStartTimestamp = mediaStatus { + strongSelf.playerStatus = mediaStatusAndStartTimestamp.0 + } + strongSelf.fetchStatus = fetchStatus + strongSelf.updateStatus() + } + })) + self.addSubnode(videoNode) self.isReady.set(videoNode.ready |> map { return true }) @@ -369,6 +451,8 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { self.statusPromise.set(.single(nil)) + self.statusDisposable.set(nil) + self.imageNode.imageUpdated = { [weak self] _ in guard let strongSelf = self else { return @@ -1186,7 +1270,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { private var isFirstAvatarLoading = true var item: PeerInfoAvatarListItem? - private let playbackStatusDisposable = MetaDisposable() + private let playbackStartDisposable = MetaDisposable() init(context: AccountContext) { self.context = context @@ -1202,7 +1286,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } deinit { - self.playbackStatusDisposable.dispose() + self.playbackStartDisposable.dispose() } @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { @@ -1289,7 +1373,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { if let startTimestamp = video.representation.startTimestamp { self.videoStartTimestamp = startTimestamp - self.playbackStatusDisposable.set((videoNode.status + self.playbackStartDisposable.set((videoNode.status |> map { status -> Bool in if let status = status, case .playing = status.status { return true @@ -1310,7 +1394,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { })) } else { self.videoStartTimestamp = nil - self.playbackStatusDisposable.set(nil) + self.playbackStartDisposable.set(nil) videoNode.isHidden = false } From 24907139c6c9c5dae097cb642d188092f13b5995 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Jul 2020 12:48:27 +0300 Subject: [PATCH 4/5] Video avatar fixes --- .../Sources/TGMediaVideoConverter.m | 2 +- .../Sources/TGPhotoEditorController.m | 25 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index f2387de000..32340c5c77 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -1356,7 +1356,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, return 2000; case TGMediaVideoConversionPresetProfileVeryHigh: - return 2300; + return 2400; default: return 900; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index 9585088885..ee9c1d8db9 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -622,7 +622,10 @@ - (NSTimeInterval)trimEndValue { if (_scrubberView != nil) { - return _scrubberView.trimEndValue; + if (_scrubberView.trimEndValue > 0.0) + return _scrubberView.trimEndValue; + else + return MIN(9.9, _scrubberView.duration); } else { return _photoEditor.trimEndValue; } @@ -1925,7 +1928,7 @@ { videoStartValue = _dotPosition; trimStartValue = self.trimStartValue; - trimEndValue = self.trimEndValue; + trimEndValue = MIN(self.trimStartValue + 9.9, self.trimEndValue); } [self stopVideoPlayback:true]; @@ -1989,14 +1992,18 @@ NSTimeInterval duration = trimEndValue - trimStartValue; TGMediaVideoConversionPreset preset; - if (duration <= 2.5) { - preset = TGMediaVideoConversionPresetProfileVeryHigh; - } else if (duration <= 5.0) { - preset = TGMediaVideoConversionPresetProfileHigh; - } else if (duration <= 8.0) { - preset = TGMediaVideoConversionPresetProfile; + if (duration > 0.0) { + if (duration <= 2.0) { + preset = TGMediaVideoConversionPresetProfileVeryHigh; + } else if (duration <= 5.0) { + preset = TGMediaVideoConversionPresetProfileHigh; + } else if (duration <= 8.0) { + preset = TGMediaVideoConversionPresetProfile; + } else { + preset = TGMediaVideoConversionPresetProfileLow; + } } else { - preset = TGMediaVideoConversionPresetProfileLow; + preset = TGMediaVideoConversionPresetProfile; } TGDispatchOnMainThread(^{ From 913588f205f0b9cf6abd7bf30a656c7650080d17 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Jul 2020 13:03:28 +0300 Subject: [PATCH 5/5] Video avatar fixes --- .../Sources/AvatarGalleryController.swift | 81 ++++--------------- 1 file changed, 14 insertions(+), 67 deletions(-) diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index a652a5c105..c740e75b2a 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -659,70 +659,6 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } } - private func openEntryEdit(_ rawEntry: AvatarGalleryEntry) { - let mediaReference: AnyMediaReference - if let video = rawEntry.videoRepresentations.last?.representation { - mediaReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) - } else { - let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: rawEntry.representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) - mediaReference = .standalone(media: media) - } - - var dismissStatus: (() -> Void)? - let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { - dismissStatus?() - })) - dismissStatus = { [weak self, weak statusController] in - self?.editDisposable.set(nil) - statusController?.dismiss() - } - self.present(statusController, in: .window(.root)) - - self.editDisposable.set((fetchMediaData(context: self.context, postbox: self.context.account.postbox, mediaReference: mediaReference) - |> deliverOnMainQueue).start(next: { [weak self] state, isImage in - guard let strongSelf = self else { - return - } - switch state { - case .progress: - break - case let .data(data): - dismissStatus?() - - let image: UIImage? - let video: URL? - if isImage { - if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { - image = UIImage(data: fileData) - } else { - image = nil - } - video = nil - } else { - image = nil - video = URL(fileURLWithPath: data.path) - } - - let avatarPhotoEditCompletion = strongSelf.avatarPhotoEditCompletion - let avatarVideoEditCompletion = strongSelf.avatarVideoEditCompletion - - presentLegacyAvatarEditor(theme: strongSelf.presentationData.theme, image: image, video: video, present: { [weak self] c, a in - if let strongSelf = self { - strongSelf.present(c, in: .window(.root), with: a, blockInteraction: true) - } - }, imageCompletion: { image in - avatarPhotoEditCompletion?(image) - }, videoCompletion: { image, url, adjustments in - avatarVideoEditCompletion?(image, url, adjustments) - }) - - Queue.mainQueue().after(0.4) { - strongSelf.dismiss(forceAway: true) - } - } - })) - } - private func editEntry(_ rawEntry: AvatarGalleryEntry) { let actionSheet = ActionSheetController(presentationData: self.presentationData) let dismissAction: () -> Void = { [weak actionSheet] in @@ -786,6 +722,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr var focusOnItem: Int? var updatedEntries = self.entries var replaceItems = false + var dismiss = false switch entry { case .topImage: @@ -793,7 +730,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } else { if entry == self.entries.first { let _ = updatePeerPhoto(postbox: self.context.account.postbox, network: self.context.account.network, stateManager: self.context.account.stateManager, accountPeerId: self.context.account.peerId, peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() - self.dismiss(forceAway: true) + dismiss = true } else { if let index = self.entries.firstIndex(of: entry) { self.entries.remove(at: index) @@ -807,7 +744,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr let _ = removeAccountPhoto(network: self.context.account.network, reference: reference).start() } if entry == self.entries.first { - self.dismiss(forceAway: true) + dismiss = true } else { if let index = self.entries.firstIndex(of: entry) { replaceItems = true @@ -822,7 +759,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr if entry == self.entries.first { let _ = updatePeerPhoto(postbox: self.context.account.postbox, network: self.context.account.network, stateManager: self.context.account.stateManager, accountPeerId: self.context.account.peerId, peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() - self.dismiss(forceAway: true) + dismiss = true } else { if let index = self.entries.firstIndex(of: entry) { replaceItems = true @@ -844,6 +781,16 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr }) }), centralItemIndex: focusOnItem, synchronous: true) self.entries = updatedEntries } + if dismiss { + self._hiddenMedia.set(.single(nil)) + Queue.mainQueue().after(0.2) { + self.dismiss(forceAway: true) + } + } else { + if let firstEntry = self.entries.first { + self._hiddenMedia.set(.single(firstEntry)) + } + } } let actionSheet = ActionSheetController(presentationData: presentationData) let items: [ActionSheetItem] = [