mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
261 lines
12 KiB
Swift
261 lines
12 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import YuvConversion
|
|
import Accelerate
|
|
|
|
final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
|
private let templateImageSupport: Bool
|
|
|
|
private var highlightedContentNode: ASDisplayNode?
|
|
private var highlightedColor: UIColor?
|
|
private var highlightReplacesContent = false
|
|
public var renderAsTemplateImage: Bool = false
|
|
|
|
public private(set) var currentFrameImage: UIImage?
|
|
|
|
init(templateImageSupport: Bool) {
|
|
self.templateImageSupport = templateImageSupport
|
|
|
|
super.init()
|
|
|
|
if templateImageSupport {
|
|
self.setViewBlock({
|
|
return UIImageView()
|
|
})
|
|
}
|
|
}
|
|
|
|
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void, averageColor: ((UIColor) -> Void)?) {
|
|
assert(bytesPerRow > 0)
|
|
let renderAsTemplateImage = self.renderAsTemplateImage
|
|
queue.async { [weak self] in
|
|
switch type {
|
|
case .argb:
|
|
let calculatedBytesPerRow = DeviceGraphicsContextSettings.shared.bytesPerRow(forWidth: Int(width))
|
|
assert(bytesPerRow == calculatedBytesPerRow)
|
|
case .yuva:
|
|
break
|
|
case .dct:
|
|
break
|
|
}
|
|
|
|
var image: UIImage?
|
|
var averageColorValue: UIColor?
|
|
|
|
autoreleasepool {
|
|
image = generateImagePixel(CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, pixelGenerator: { _, pixelData, contextBytesPerRow in
|
|
switch type {
|
|
case .yuva:
|
|
data.withUnsafeBytes { bytes -> Void in
|
|
guard let baseAddress = bytes.baseAddress else {
|
|
return
|
|
}
|
|
if bytesPerRow <= 0 || height <= 0 || width <= 0 || bytesPerRow * height > bytes.count {
|
|
assert(false)
|
|
return
|
|
}
|
|
decodeYUVAToRGBA(baseAddress.assumingMemoryBound(to: UInt8.self), pixelData, Int32(width), Int32(height), Int32(contextBytesPerRow))
|
|
}
|
|
case .argb:
|
|
var data = data
|
|
data.withUnsafeMutableBytes { bytes -> Void in
|
|
guard let baseAddress = bytes.baseAddress else {
|
|
return
|
|
}
|
|
if mulAlpha {
|
|
var srcData = vImage_Buffer(data: baseAddress.assumingMemoryBound(to: UInt8.self), height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow)
|
|
var destData = vImage_Buffer(data: pixelData, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow)
|
|
|
|
let permuteMap: [UInt8] = [3, 2, 1, 0]
|
|
vImagePermuteChannels_ARGB8888(&srcData, &destData, permuteMap, vImage_Flags(kvImageDoNotTile))
|
|
vImagePremultiplyData_ARGB8888(&destData, &destData, vImage_Flags(kvImageDoNotTile))
|
|
vImagePermuteChannels_ARGB8888(&destData, &destData, permuteMap, vImage_Flags(kvImageDoNotTile))
|
|
} else {
|
|
memcpy(pixelData, baseAddress.assumingMemoryBound(to: UInt8.self), bytes.count)
|
|
}
|
|
}
|
|
case .dct:
|
|
break
|
|
}
|
|
})
|
|
if renderAsTemplateImage {
|
|
image = image?.withRenderingMode(.alwaysTemplate)
|
|
}
|
|
|
|
if averageColor != nil {
|
|
let blurredWidth = 16
|
|
let blurredHeight = 16
|
|
let blurredBytesPerRow = blurredWidth * 4
|
|
guard let context = DrawingContext(size: CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)), scale: 1.0, opaque: true, bytesPerRow: blurredBytesPerRow) else {
|
|
return
|
|
}
|
|
|
|
let size = CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight))
|
|
|
|
if let image, let cgImage = image.cgImage {
|
|
context.withFlippedContext { c in
|
|
c.setFillColor(UIColor.white.cgColor)
|
|
c.fill(CGRect(origin: CGPoint(), size: size))
|
|
c.draw(cgImage, in: CGRect(origin: CGPoint(x: -size.width / 2.0, y: -size.height / 2.0), size: CGSize(width: size.width * 1.8, height: size.height * 1.8)))
|
|
}
|
|
}
|
|
|
|
var destinationBuffer = vImage_Buffer()
|
|
destinationBuffer.width = UInt(blurredWidth)
|
|
destinationBuffer.height = UInt(blurredHeight)
|
|
destinationBuffer.data = context.bytes
|
|
destinationBuffer.rowBytes = context.bytesPerRow
|
|
|
|
vImageBoxConvolve_ARGB8888(&destinationBuffer,
|
|
&destinationBuffer,
|
|
nil,
|
|
0, 0,
|
|
UInt32(15),
|
|
UInt32(15),
|
|
nil,
|
|
vImage_Flags(kvImageTruncateKernel))
|
|
|
|
let divisor: Int32 = 0x1000
|
|
|
|
let rwgt: CGFloat = 0.3086
|
|
let gwgt: CGFloat = 0.6094
|
|
let bwgt: CGFloat = 0.0820
|
|
|
|
let adjustSaturation: CGFloat = 1.7
|
|
|
|
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(&destinationBuffer, &destinationBuffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile))
|
|
|
|
context.withFlippedContext { c in
|
|
c.setFillColor(UIColor.white.withMultipliedAlpha(0.1).cgColor)
|
|
c.fill(CGRect(origin: CGPoint(), size: size))
|
|
}
|
|
|
|
var sumR: UInt64 = 0
|
|
var sumG: UInt64 = 0
|
|
var sumB: UInt64 = 0
|
|
var sumA: UInt64 = 0
|
|
|
|
for y in 0 ..< blurredHeight {
|
|
let row = context.bytes.assumingMemoryBound(to: UInt8.self).advanced(by: y * blurredBytesPerRow)
|
|
for x in 0 ..< blurredWidth {
|
|
let pixel = row.advanced(by: x * 4)
|
|
sumB += UInt64(pixel.advanced(by: 0).pointee)
|
|
sumG += UInt64(pixel.advanced(by: 1).pointee)
|
|
sumR += UInt64(pixel.advanced(by: 2).pointee)
|
|
sumA += UInt64(pixel.advanced(by: 3).pointee)
|
|
}
|
|
}
|
|
sumR /= UInt64(blurredWidth * blurredHeight)
|
|
sumG /= UInt64(blurredWidth * blurredHeight)
|
|
sumB /= UInt64(blurredWidth * blurredHeight)
|
|
sumA /= UInt64(blurredWidth * blurredHeight)
|
|
sumA = 255
|
|
|
|
averageColorValue = UIColor(red: CGFloat(sumR) / 255.0, green: CGFloat(sumG) / 255.0, blue: CGFloat(sumB) / 255.0, alpha: CGFloat(sumA) / 255.0)
|
|
}
|
|
}
|
|
|
|
Queue.mainQueue().async {
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.currentFrameImage = image
|
|
if strongSelf.templateImageSupport {
|
|
(strongSelf.view as? UIImageView)?.image = image
|
|
} else {
|
|
strongSelf.contents = image?.cgImage
|
|
}
|
|
strongSelf.updateHighlightedContentNode()
|
|
if strongSelf.highlightedContentNode?.frame != strongSelf.bounds {
|
|
strongSelf.highlightedContentNode?.frame = strongSelf.bounds
|
|
}
|
|
completion()
|
|
|
|
if let averageColor, let averageColorValue {
|
|
averageColor(averageColorValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func updateHighlightedContentNode() {
|
|
guard let highlightedContentNode = self.highlightedContentNode, let highlightedColor = self.highlightedColor else {
|
|
return
|
|
}
|
|
(highlightedContentNode.view as! UIImageView).image = self.currentFrameImage?.withRenderingMode(.alwaysTemplate)
|
|
highlightedContentNode.tintColor = highlightedColor
|
|
if self.highlightReplacesContent {
|
|
if self.templateImageSupport {
|
|
(self.view as? UIImageView)?.image = nil
|
|
} else {
|
|
self.contents = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
|
self.highlightReplacesContent = replace
|
|
var updated = false
|
|
if let current = self.highlightedColor, let color = color {
|
|
updated = !current.isEqual(color)
|
|
} else if (self.highlightedColor != nil) != (color != nil) {
|
|
updated = true
|
|
}
|
|
|
|
if !updated {
|
|
return
|
|
}
|
|
|
|
self.highlightedColor = color
|
|
|
|
if let _ = color {
|
|
if let highlightedContentNode = self.highlightedContentNode {
|
|
highlightedContentNode.alpha = 1.0
|
|
} else {
|
|
let highlightedContentNode = ASDisplayNode(viewBlock: {
|
|
return UIImageView()
|
|
}, didLoad: nil)
|
|
highlightedContentNode.displaysAsynchronously = false
|
|
|
|
self.highlightedContentNode = highlightedContentNode
|
|
highlightedContentNode.frame = self.bounds
|
|
self.addSubnode(highlightedContentNode)
|
|
}
|
|
self.updateHighlightedContentNode()
|
|
} else if let highlightedContentNode = self.highlightedContentNode {
|
|
highlightedContentNode.alpha = 0.0
|
|
highlightedContentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] completed in
|
|
guard let strongSelf = self, completed else {
|
|
return
|
|
}
|
|
strongSelf.highlightedContentNode?.removeFromSupernode()
|
|
strongSelf.highlightedContentNode = nil
|
|
})
|
|
}
|
|
}
|
|
}
|