This commit is contained in:
Ali 2021-03-19 19:34:01 +04:00
parent 097f012155
commit c2a357a9d6
8 changed files with 97 additions and 28 deletions

View File

@ -18,6 +18,7 @@ swift_library(
"//submodules/Emoji:Emoji", "//submodules/Emoji:Emoji",
"//submodules/ImageCompression:ImageCompression", "//submodules/ImageCompression:ImageCompression",
"//submodules/TinyThumbnail:TinyThumbnail", "//submodules/TinyThumbnail:TinyThumbnail",
"//submodules/FastBlur:FastBlur",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -224,7 +224,7 @@ public final class AvatarNode: ASDisplayNode {
public init(font: UIFont) { public init(font: UIFont) {
self.font = font self.font = font
self.imageNode = ImageNode(enableHasImage: true) self.imageNode = ImageNode(enableHasImage: true, enableAnimatedTransition: true)
super.init() super.init()

View File

@ -8,6 +8,7 @@ import TelegramCore
import SyncCore import SyncCore
import ImageCompression import ImageCompression
import TinyThumbnail import TinyThumbnail
import FastBlur
private let roundCorners = { () -> UIImage in private let roundCorners = { () -> UIImage in
let diameter: CGFloat = 60.0 let diameter: CGFloat = 60.0
@ -23,19 +24,32 @@ private let roundCorners = { () -> UIImage in
return image return image
}() }()
public func peerAvatarImageData(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<Data?, NoError>? { public enum PeerAvatarImageType {
case blurred
case complete
}
public func peerAvatarImageData(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<(Data, PeerAvatarImageType)?, NoError>? {
if let smallProfileImage = representation { if let smallProfileImage = representation {
let resourceData = account.postbox.mediaBox.resourceData(smallProfileImage.resource, attemptSynchronously: synchronousLoad) let resourceData = account.postbox.mediaBox.resourceData(smallProfileImage.resource, attemptSynchronously: synchronousLoad)
let imageData = resourceData let imageData = resourceData
|> take(1) |> take(1)
|> mapToSignal { maybeData -> Signal<Data?, NoError> in |> mapToSignal { maybeData -> Signal<(Data, PeerAvatarImageType)?, NoError> in
if maybeData.complete { if maybeData.complete {
return .single(try? Data(contentsOf: URL(fileURLWithPath: maybeData.path))) if let data = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path)) {
return .single((data, .complete))
} else {
return .single(nil)
}
} else { } else {
return Signal { subscriber in return Signal { subscriber in
let resourceDataDisposable = resourceData.start(next: { data in let resourceDataDisposable = resourceData.start(next: { data in
if data.complete { if data.complete {
subscriber.putNext(try? Data(contentsOf: URL(fileURLWithPath: maybeData.path))) if let dataValue = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path)) {
subscriber.putNext((dataValue, .complete))
} else {
subscriber.putNext(nil)
}
subscriber.putCompletion() subscriber.putCompletion()
} else { } else {
subscriber.putNext(nil) subscriber.putNext(nil)
@ -61,6 +75,24 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
} }
} }
return imageData return imageData
|> mapToSignal { data -> Signal<(Data, PeerAvatarImageType)?, NoError> in
guard let (dataValue, type) = data, case .complete = type else {
return .single(data)
}
if let mappedImage = UIImage(data: dataValue), let miniData = compressImageMiniThumbnail(mappedImage, type: .avatar) {
//print("Demo avatar size: \(miniData.count) bytes")
if let decodedData = decodeTinyThumbnail(data: miniData) {
return Signal<(Data, PeerAvatarImageType)?, NoError>.single((decodedData, .blurred))
|> then(
Signal<(Data, PeerAvatarImageType)?, NoError>.single((dataValue, .complete))
|> delay(1.0, queue: .concurrentDefaultQueue())
)
}
}
return .single(data)
}
} else { } else {
return nil return nil
} }
@ -102,7 +134,7 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
return .single(nil) return .single(nil)
} }
let roundedImage = generateImage(displayDimensions, contextGenerator: { size, context -> Void in let roundedImage = generateImage(displayDimensions, contextGenerator: { size, context -> Void in
if let data = data { if let (data, dataType) = data {
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), var dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), var dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions)) context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setBlendMode(.copy) context.setBlendMode(.copy)
@ -112,13 +144,16 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
context.clip() context.clip()
} }
let mappedImage = UIImage(cgImage: dataImage) if case .blurred = dataType {
if let miniData = compressImageMiniThumbnail(mappedImage, type: .avatar) { let imageContextSize = CGSize(width: 64.0, height: 64.0)
if let decodedData = decodeTinyThumbnail(data: miniData) { let imageContext = DrawingContext(size: imageContextSize, scale: 1.0, premultiplied: true, clear: true)
if let decodedImage = UIImage(data: decodedData) { imageContext.withFlippedContext { c in
dataImage = decodedImage.cgImage! c.draw(dataImage, in: CGRect(origin: CGPoint(), size: imageContextSize).insetBy(dx: inset, dy: inset))
}
} }
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
dataImage = imageContext.generateImage()!.cgImage!
} }
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset)) context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
@ -152,7 +187,7 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
let unroundedImage: UIImage? let unroundedImage: UIImage?
if provideUnrounded { if provideUnrounded {
unroundedImage = generateImage(displayDimensions, contextGenerator: { size, context -> Void in unroundedImage = generateImage(displayDimensions, contextGenerator: { size, context -> Void in
if let data = data { if let (data, _) = data {
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions)) context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setBlendMode(.copy) context.setBlendMode(.copy)

View File

@ -126,6 +126,7 @@ public class ImageNode: ASDisplayNode {
private let hasImage: ValuePromise<Bool>? private let hasImage: ValuePromise<Bool>?
private var first = true private var first = true
private let enableEmpty: Bool private let enableEmpty: Bool
private let enableAnimatedTransition: Bool
private let _contentReady = Promise<Bool>() private let _contentReady = Promise<Bool>()
private var didSetReady: Bool = false private var didSetReady: Bool = false
@ -141,13 +142,14 @@ public class ImageNode: ASDisplayNode {
} }
} }
public init(enableHasImage: Bool = false, enableEmpty: Bool = false) { public init(enableHasImage: Bool = false, enableEmpty: Bool = false, enableAnimatedTransition: Bool = false) {
if enableHasImage { if enableHasImage {
self.hasImage = ValuePromise(false, ignoreRepeated: true) self.hasImage = ValuePromise(false, ignoreRepeated: true)
} else { } else {
self.hasImage = nil self.hasImage = nil
} }
self.enableEmpty = enableEmpty self.enableEmpty = enableEmpty
self.enableAnimatedTransition = enableAnimatedTransition
super.init() super.init()
} }
@ -160,17 +162,33 @@ public class ImageNode: ASDisplayNode {
self.disposable.set((signal |> deliverOnMainQueue).start(next: {[weak self] next in self.disposable.set((signal |> deliverOnMainQueue).start(next: {[weak self] next in
dispatcher.dispatch { dispatcher.dispatch {
if let strongSelf = self { if let strongSelf = self {
if let image = next?.cgImage { var animate = strongSelf.enableAnimatedTransition
strongSelf.contents = image
} else if strongSelf.enableEmpty {
strongSelf.contents = nil
}
if strongSelf.first && next != nil { if strongSelf.first && next != nil {
strongSelf.first = false strongSelf.first = false
animate = false
if strongSelf.isNodeLoaded { if strongSelf.isNodeLoaded {
strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
} }
} }
if let image = next?.cgImage {
if animate, let previousContents = strongSelf.contents {
strongSelf.contents = image
let tempLayer = CALayer()
tempLayer.contents = previousContents
tempLayer.frame = strongSelf.layer.bounds
strongSelf.layer.addSublayer(tempLayer)
tempLayer.opacity = 0.0
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: true, completion: { [weak tempLayer] _ in
tempLayer?.removeFromSuperlayer()
})
//strongSelf.layer.animate(from: previousContents as! CGImage, to: image, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
} else {
strongSelf.contents = image
}
} else if strongSelf.enableEmpty {
strongSelf.contents = nil
}
if !reportedHasImage { if !reportedHasImage {
if let hasImage = strongSelf.hasImage { if let hasImage = strongSelf.hasImage {
reportedHasImage = true reportedHasImage = true

View File

@ -64,5 +64,25 @@ public enum MiniThumbnailType {
} }
public func compressImageMiniThumbnail(_ image: UIImage, type: MiniThumbnailType = .image) -> Data? { public func compressImageMiniThumbnail(_ image: UIImage, type: MiniThumbnailType = .image) -> Data? {
return compressMiniThumbnail(image, type == .avatar) switch type {
case .image:
return compressMiniThumbnail(image, CGSize(width: 40.0, height: 40.0))
case .avatar:
var size: CGFloat = 8.0
var data = compressMiniThumbnail(image, CGSize(width: size, height: size))
while true {
size += 1.0
if let candidateData = compressMiniThumbnail(image, CGSize(width: size, height: size)) {
if candidateData.count >= 32 {
break
} else {
data = candidateData
}
} else {
break
}
}
return data
}
} }

View File

@ -2,4 +2,4 @@
NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage); NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage);
NSArray<NSNumber *> * _Nonnull extractJPEGDataScans(NSData * _Nonnull data); NSArray<NSNumber *> * _Nonnull extractJPEGDataScans(NSData * _Nonnull data);
NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image, bool smaller); NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image, CGSize size);

View File

@ -146,12 +146,7 @@ NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage) {
return result; return result;
} }
NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image, bool smaller) { NSData * _Nullable compressMiniThumbnail(UIImage * _Nonnull image, CGSize size) {
CGSize size = CGSizeMake(40.0f, 40.0f);
if (smaller) {
size.width = 8.0f;
size.height = 8.0f;
}
CGSize fittedSize = image.size; CGSize fittedSize = image.size;
if (fittedSize.width > size.width) { if (fittedSize.width > size.width) {
fittedSize = CGSizeMake(size.width, (int)((fittedSize.height * size.width / MAX(fittedSize.width, 1.0f)))); fittedSize = CGSizeMake(size.width, (int)((fittedSize.height * size.width / MAX(fittedSize.width, 1.0f))));

View File

@ -404,7 +404,7 @@ final class WatchMediaHandler: WatchRequestHandler {
if let imageData = imageData { if let imageData = imageData {
return imageData return imageData
|> map { data -> UIImage? in |> map { data -> UIImage? in
if let data = data, let image = generateImage(targetSize, contextGenerator: { size, context -> Void in if let (data, _) = data, let image = generateImage(targetSize, contextGenerator: { size, context -> Void in
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
context.setBlendMode(.copy) context.setBlendMode(.copy)
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: targetSize)) context.draw(dataImage, in: CGRect(origin: CGPoint(), size: targetSize))