mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Demo
This commit is contained in:
parent
097f012155
commit
c2a357a9d6
@ -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",
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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))));
|
||||||
|
@ -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))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user