mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Spoiler improvements
This commit is contained in:
parent
c7532e61e0
commit
8cdd2076d4
@ -10,7 +10,47 @@ import MozjpegBinding
|
||||
import Accelerate
|
||||
import ManagedFile
|
||||
|
||||
private func generateBlurredThumbnail(image: UIImage) -> UIImage? {
|
||||
private func adjustSaturationInContext(context: DrawingContext, saturation: CGFloat) {
|
||||
var buffer = vImage_Buffer()
|
||||
buffer.data = context.bytes
|
||||
buffer.width = UInt(context.size.width * context.scale)
|
||||
buffer.height = UInt(context.size.height * context.scale)
|
||||
buffer.rowBytes = context.bytesPerRow
|
||||
|
||||
let divisor: Int32 = 0x1000
|
||||
|
||||
let rwgt: CGFloat = 0.3086
|
||||
let gwgt: CGFloat = 0.6094
|
||||
let bwgt: CGFloat = 0.0820
|
||||
|
||||
let adjustSaturation = saturation
|
||||
|
||||
let a = (1.0 - adjustSaturation) * rwgt + adjustSaturation
|
||||
let b = (1.0 - adjustSaturation) * rwgt
|
||||
let c = (1.0 - adjustSaturation) * rwgt
|
||||
let d = (1.0 - adjustSaturation) * gwgt
|
||||
let e = (1.0 - adjustSaturation) * gwgt + adjustSaturation
|
||||
let f = (1.0 - adjustSaturation) * gwgt
|
||||
let g = (1.0 - adjustSaturation) * bwgt
|
||||
let h = (1.0 - adjustSaturation) * bwgt
|
||||
let i = (1.0 - adjustSaturation) * bwgt + adjustSaturation
|
||||
|
||||
let satMatrix: [CGFloat] = [
|
||||
a, b, c, 0,
|
||||
d, e, f, 0,
|
||||
g, h, i, 0,
|
||||
0, 0, 0, 1
|
||||
]
|
||||
|
||||
var matrix: [Int16] = satMatrix.map { value in
|
||||
return Int16(value * CGFloat(divisor))
|
||||
}
|
||||
|
||||
vImageMatrixMultiply_ARGB8888(&buffer, &buffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile))
|
||||
}
|
||||
|
||||
|
||||
private func generateBlurredThumbnail(image: UIImage, adjustSaturation: Bool = false) -> UIImage? {
|
||||
let thumbnailContextSize = CGSize(width: 32.0, height: 32.0)
|
||||
guard let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) else {
|
||||
return nil
|
||||
@ -24,6 +64,10 @@ private func generateBlurredThumbnail(image: UIImage) -> UIImage? {
|
||||
}
|
||||
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
|
||||
|
||||
if adjustSaturation {
|
||||
adjustSaturationInContext(context: thumbnailContext, saturation: 1.7)
|
||||
}
|
||||
|
||||
return thumbnailContext.generateImage()
|
||||
}
|
||||
|
||||
@ -158,10 +202,12 @@ private func loadImage(data: Data) -> UIImage? {
|
||||
public final class DirectMediaImageCache {
|
||||
public final class GetMediaResult {
|
||||
public let image: UIImage?
|
||||
public let blurredImage: UIImage?
|
||||
public let loadSignal: Signal<UIImage?, NoError>?
|
||||
|
||||
init(image: UIImage?, loadSignal: Signal<UIImage?, NoError>?) {
|
||||
init(image: UIImage?, blurredImage: UIImage? = nil, loadSignal: Signal<UIImage?, NoError>?) {
|
||||
self.image = image
|
||||
self.blurredImage = blurredImage
|
||||
self.loadSignal = loadSignal
|
||||
}
|
||||
}
|
||||
@ -284,7 +330,7 @@ public final class DirectMediaImageCache {
|
||||
return self.getProgressiveSize(mediaReference: MediaReference.message(message: MessageReference(message), media: file).abstract, width: width, representations: file.previewRepresentations)
|
||||
}
|
||||
|
||||
private func getImageSynchronous(message: Message, userLocation: MediaResourceUserLocation, media: Media, width: Int, possibleWidths: [Int]) -> GetMediaResult? {
|
||||
private func getImageSynchronous(message: Message, userLocation: MediaResourceUserLocation, media: Media, width: Int, possibleWidths: [Int], includeBlurred: Bool) -> GetMediaResult? {
|
||||
var immediateThumbnailData: Data?
|
||||
var resource: (resource: MediaResourceReference, size: Int64)?
|
||||
if let image = media as? TelegramMediaImage {
|
||||
@ -298,39 +344,50 @@ public final class DirectMediaImageCache {
|
||||
guard let resource = resource else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
var resultImage: UIImage?
|
||||
var blurredImage: UIImage?
|
||||
for otherWidth in possibleWidths.reversed() {
|
||||
if otherWidth == width {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.resource.id, imageType: .square(width: otherWidth)))), let image = loadImage(data: data) {
|
||||
return GetMediaResult(image: image, loadSignal: nil)
|
||||
if blurredImage == nil, includeBlurred, let data = immediateThumbnailData.flatMap(decodeTinyThumbnail), let image = loadImage(data: data), let blurredImageValue = generateBlurredThumbnail(image: image, adjustSaturation: true) {
|
||||
blurredImage = blurredImageValue
|
||||
}
|
||||
return GetMediaResult(image: image, blurredImage: blurredImage, loadSignal: nil)
|
||||
}
|
||||
} else {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.resource.id, imageType: .square(width: otherWidth)))), let image = loadImage(data: data) {
|
||||
blurredImage = image
|
||||
resultImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if blurredImage == nil {
|
||||
if resultImage == nil {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.resource.id, imageType: .blurredThumbnail))), let image = loadImage(data: data) {
|
||||
blurredImage = image
|
||||
resultImage = image
|
||||
} else if let data = immediateThumbnailData.flatMap(decodeTinyThumbnail), let image = loadImage(data: data) {
|
||||
if let blurredImageValue = generateBlurredThumbnail(image: image) {
|
||||
resultImage = blurredImageValue
|
||||
}
|
||||
if includeBlurred, let blurredImageValue = generateBlurredThumbnail(image: image, adjustSaturation: true) {
|
||||
blurredImage = blurredImageValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if blurredImage == nil, includeBlurred, let data = immediateThumbnailData.flatMap(decodeTinyThumbnail), let image = loadImage(data: data), let blurredImageValue = generateBlurredThumbnail(image: image, adjustSaturation: true) {
|
||||
blurredImage = blurredImageValue
|
||||
}
|
||||
|
||||
return GetMediaResult(image: blurredImage, loadSignal: self.getLoadSignal(width: width, userLocation: userLocation, userContentType: .image, resource: resource.resource, resourceSizeLimit: resource.size))
|
||||
return GetMediaResult(image: resultImage, blurredImage: blurredImage, loadSignal: self.getLoadSignal(width: width, userLocation: userLocation, userContentType: .image, resource: resource.resource, resourceSizeLimit: resource.size))
|
||||
}
|
||||
|
||||
public func getImage(message: Message, media: Media, width: Int, possibleWidths: [Int], synchronous: Bool) -> GetMediaResult? {
|
||||
public func getImage(message: Message, media: Media, width: Int, possibleWidths: [Int], includeBlurred: Bool = false, synchronous: Bool) -> GetMediaResult? {
|
||||
if synchronous {
|
||||
return self.getImageSynchronous(message: message, userLocation: .peer(message.id.peerId), media: media, width: width, possibleWidths: possibleWidths)
|
||||
return self.getImageSynchronous(message: message, userLocation: .peer(message.id.peerId), media: media, width: width, possibleWidths: possibleWidths, includeBlurred: includeBlurred)
|
||||
} else {
|
||||
return GetMediaResult(image: nil, loadSignal: Signal { subscriber in
|
||||
let result = self.getImageSynchronous(message: message, userLocation: .peer(message.id.peerId), media: media, width: width, possibleWidths: possibleWidths)
|
||||
return GetMediaResult(image: nil, blurredImage: nil, loadSignal: Signal { subscriber in
|
||||
let result = self.getImageSynchronous(message: message, userLocation: .peer(message.id.peerId), media: media, width: width, possibleWidths: possibleWidths, includeBlurred: includeBlurred)
|
||||
guard let result = result else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
|
@ -6,6 +6,89 @@ import Display
|
||||
import AppBundle
|
||||
import LegacyComponents
|
||||
|
||||
public class MediaDustLayer: CALayer {
|
||||
private var emitter: CAEmitterCell?
|
||||
private var emitterLayer: CAEmitterLayer?
|
||||
|
||||
private var size: CGSize?
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func setupEmitterLayerIfNeeded() {
|
||||
guard self.emitterLayer == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
let emitter = CAEmitterCell()
|
||||
emitter.color = UIColor(rgb: 0xffffff, alpha: 0.0).cgColor
|
||||
emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
|
||||
emitter.contentsScale = 1.8
|
||||
emitter.emissionRange = .pi * 2.0
|
||||
emitter.lifetime = 8.0
|
||||
emitter.scale = 0.5
|
||||
emitter.velocityRange = 0.0
|
||||
emitter.name = "dustCell"
|
||||
emitter.alphaRange = 1.0
|
||||
emitter.setValue("point", forKey: "particleType")
|
||||
emitter.setValue(1.0, forKey: "mass")
|
||||
emitter.setValue(0.01, forKey: "massRange")
|
||||
self.emitter = emitter
|
||||
|
||||
let alphaBehavior = createEmitterBehavior(type: "valueOverLife")
|
||||
alphaBehavior.setValue("color.alpha", forKey: "keyPath")
|
||||
alphaBehavior.setValue([0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1], forKey: "values")
|
||||
alphaBehavior.setValue(true, forKey: "additive")
|
||||
|
||||
let scaleBehavior = createEmitterBehavior(type: "valueOverLife")
|
||||
scaleBehavior.setValue("scale", forKey: "keyPath")
|
||||
scaleBehavior.setValue([0.0, 0.5], forKey: "values")
|
||||
scaleBehavior.setValue([0.0, 0.05], forKey: "locations")
|
||||
|
||||
let behaviors = [alphaBehavior, scaleBehavior]
|
||||
|
||||
let emitterLayer = CAEmitterLayer()
|
||||
emitterLayer.masksToBounds = true
|
||||
emitterLayer.allowsGroupOpacity = true
|
||||
emitterLayer.lifetime = 1
|
||||
emitterLayer.emitterCells = [emitter]
|
||||
emitterLayer.seed = arc4random()
|
||||
emitterLayer.emitterShape = .rectangle
|
||||
emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
|
||||
self.addSublayer(emitterLayer)
|
||||
|
||||
self.emitterLayer = emitterLayer
|
||||
}
|
||||
|
||||
private func updateEmitter() {
|
||||
guard let size = self.size else {
|
||||
return
|
||||
}
|
||||
|
||||
self.setupEmitterLayerIfNeeded()
|
||||
|
||||
self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.emitterLayer?.emitterSize = size
|
||||
self.emitterLayer?.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
|
||||
let square = Float(size.width * size.height)
|
||||
Queue.mainQueue().async {
|
||||
self.emitter?.birthRate = min(100000.0, square * 0.02)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize) {
|
||||
self.size = size
|
||||
|
||||
self.updateEmitter()
|
||||
}
|
||||
}
|
||||
|
||||
public class MediaDustNode: ASDisplayNode {
|
||||
private var currentParams: (size: CGSize, color: UIColor)?
|
||||
private var animColor: CGColor?
|
||||
|
@ -24,6 +24,7 @@ import TelegramUIPreferences
|
||||
import CheckNode
|
||||
import AppBundle
|
||||
import ChatControllerInteraction
|
||||
import InvisibleInkDustNode
|
||||
|
||||
private final class FrameSequenceThumbnailNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
@ -776,9 +777,11 @@ private protocol ItemLayer: SparseItemGridLayer {
|
||||
var disposable: Disposable? { get set }
|
||||
|
||||
var hasContents: Bool { get set }
|
||||
func setSpoilerContents(_ contents: Any?)
|
||||
|
||||
func updateDuration(duration: Int32?, isMin: Bool, minFactor: CGFloat)
|
||||
func updateSelection(theme: CheckNodeTheme, isSelected: Bool?, animated: Bool)
|
||||
func updateHasSpoiler(hasSpoiler: Bool)
|
||||
|
||||
func bind(item: VisualMediaItem)
|
||||
func unbind()
|
||||
@ -789,6 +792,7 @@ private final class GenericItemLayer: CALayer, ItemLayer {
|
||||
var durationLayer: DurationLayer?
|
||||
var minFactor: CGFloat = 1.0
|
||||
var selectionLayer: GridMessageSelectionLayer?
|
||||
var dustLayer: MediaDustLayer?
|
||||
var disposable: Disposable?
|
||||
|
||||
var hasContents: Bool = false
|
||||
@ -816,6 +820,12 @@ private final class GenericItemLayer: CALayer, ItemLayer {
|
||||
self.contents = image.cgImage
|
||||
}
|
||||
}
|
||||
|
||||
func setSpoilerContents(_ contents: Any?) {
|
||||
if let image = contents as? UIImage {
|
||||
self.dustLayer?.contents = image.cgImage
|
||||
}
|
||||
}
|
||||
|
||||
override func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
@ -873,6 +883,24 @@ private final class GenericItemLayer: CALayer, ItemLayer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateHasSpoiler(hasSpoiler: Bool) {
|
||||
if hasSpoiler {
|
||||
if let _ = self.dustLayer {
|
||||
} else {
|
||||
let dustLayer = MediaDustLayer()
|
||||
self.dustLayer = dustLayer
|
||||
self.addSublayer(dustLayer)
|
||||
if !self.bounds.isEmpty {
|
||||
dustLayer.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
dustLayer.updateLayout(size: self.bounds.size)
|
||||
}
|
||||
}
|
||||
} else if let dustLayer = self.dustLayer {
|
||||
self.dustLayer = nil
|
||||
dustLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
func unbind() {
|
||||
self.item = nil
|
||||
@ -894,6 +922,7 @@ private final class CaptureProtectedItemLayer: AVSampleBufferDisplayLayer, ItemL
|
||||
var durationLayer: DurationLayer?
|
||||
var minFactor: CGFloat = 1.0
|
||||
var selectionLayer: GridMessageSelectionLayer?
|
||||
var dustLayer: MediaDustLayer?
|
||||
var disposable: Disposable?
|
||||
|
||||
var hasContents: Bool = false
|
||||
@ -935,6 +964,12 @@ private final class CaptureProtectedItemLayer: AVSampleBufferDisplayLayer, ItemL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setSpoilerContents(_ contents: Any?) {
|
||||
if let image = contents as? UIImage {
|
||||
self.dustLayer?.contents = image.cgImage
|
||||
}
|
||||
}
|
||||
|
||||
func bind(item: VisualMediaItem) {
|
||||
self.item = item
|
||||
@ -988,6 +1023,24 @@ private final class CaptureProtectedItemLayer: AVSampleBufferDisplayLayer, ItemL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateHasSpoiler(hasSpoiler: Bool) {
|
||||
if hasSpoiler {
|
||||
if let _ = self.dustLayer {
|
||||
} else {
|
||||
let dustLayer = MediaDustLayer()
|
||||
self.dustLayer = dustLayer
|
||||
self.addSublayer(dustLayer)
|
||||
if !self.bounds.isEmpty {
|
||||
dustLayer.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
dustLayer.updateLayout(size: self.bounds.size)
|
||||
}
|
||||
}
|
||||
} else if let dustLayer = self.dustLayer {
|
||||
self.dustLayer = nil
|
||||
dustLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
func unbind() {
|
||||
self.item = nil
|
||||
@ -1194,6 +1247,8 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
||||
var onBeginFastScrollingImpl: (() -> Void)?
|
||||
var getShimmerColorsImpl: (() -> SparseItemGrid.ShimmerColors)?
|
||||
var updateShimmerLayersImpl: ((SparseItemGridDisplayItem) -> Void)?
|
||||
|
||||
var revealedSpoilerMessageIds = Set<MessageId>()
|
||||
|
||||
private var shimmerImages: [CGFloat: UIImage] = [:]
|
||||
|
||||
@ -1395,7 +1450,9 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
||||
}
|
||||
|
||||
let message = item.message
|
||||
|
||||
let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) && !self.revealedSpoilerMessageIds.contains(message.id)
|
||||
layer.updateHasSpoiler(hasSpoiler: hasSpoiler)
|
||||
|
||||
var selectedMedia: Media?
|
||||
for media in message.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
@ -1408,7 +1465,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
||||
}
|
||||
|
||||
if let selectedMedia = selectedMedia {
|
||||
if let result = directMediaImageCache.getImage(message: message, media: selectedMedia, width: imageWidthSpec, possibleWidths: SparseItemGridBindingImpl.widthSpecs.1, synchronous: synchronous == .full) {
|
||||
if let result = directMediaImageCache.getImage(message: message, media: selectedMedia, width: imageWidthSpec, possibleWidths: SparseItemGridBindingImpl.widthSpecs.1, includeBlurred: hasSpoiler, synchronous: synchronous == .full) {
|
||||
if let image = result.image {
|
||||
layer.setContents(image)
|
||||
switch synchronous {
|
||||
@ -1423,6 +1480,9 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
||||
layer.hasContents = true
|
||||
}
|
||||
}
|
||||
if let image = result.blurredImage {
|
||||
layer.setSpoilerContents(image)
|
||||
}
|
||||
if let loadSignal = result.loadSignal {
|
||||
layer.disposable?.dispose()
|
||||
let startTimestamp = CFAbsoluteTimeGetCurrent()
|
||||
@ -1494,7 +1554,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
||||
} else {
|
||||
layer.updateSelection(theme: self.checkNodeTheme, isSelected: nil, animated: false)
|
||||
}
|
||||
|
||||
|
||||
layer.bind(item: item)
|
||||
}
|
||||
}
|
||||
@ -1664,7 +1724,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
|
||||
init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, contentType: ContentType, captureProtected: Bool) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
@ -2316,6 +2376,8 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
if let item = itemLayer.item {
|
||||
if self.itemInteraction.hiddenMedia[item.message.id] != nil {
|
||||
itemLayer.isHidden = true
|
||||
itemLayer.updateHasSpoiler(hasSpoiler: false)
|
||||
self.itemGridBinding.revealedSpoilerMessageIds.insert(item.message.id)
|
||||
} else {
|
||||
itemLayer.isHidden = false
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user