Fix animated stickers

This commit is contained in:
Peter 2019-08-01 01:45:55 +03:00
parent dccb44dfdb
commit 23c76db8f4
7 changed files with 118 additions and 29 deletions

View File

@ -81,7 +81,6 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableAddressSanitizer = "YES"
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -1,5 +1,6 @@
import Foundation
import UIKit
import Accelerate
private let deviceColorSpace: CGColorSpace = {
if #available(iOSApplicationExtension 9.3, *) {
@ -13,6 +14,8 @@ private let deviceColorSpace: CGColorSpace = {
}
}()
private let grayscaleColorSpace = CGColorSpaceCreateDeviceGray()
let deviceScale = UIScreen.main.scale
public func generateImagePixel(_ size: CGSize, scale: CGFloat, pixelGenerator: (CGSize, UnsafeMutablePointer<UInt8>, Int) -> Void) -> UIImage? {
@ -39,6 +42,74 @@ public func generateImagePixel(_ size: CGSize, scale: CGFloat, pixelGenerator: (
return UIImage(cgImage: image, scale: scale, orientation: .up)
}
private func withImageBytes(image: UIImage, _ f: (UnsafePointer<UInt8>, Int, Int, Int) -> Void) {
let selectedScale = image.scale
let scaledSize = CGSize(width: image.size.width * selectedScale, height: image.size.height * selectedScale)
let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15)
let length = bytesPerRow * Int(scaledSize.height)
let bytes = malloc(length)!.assumingMemoryBound(to: UInt8.self)
memset(bytes, 0, length)
let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
return
}
context.scaleBy(x: selectedScale, y: selectedScale)
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: image.size))
f(bytes, Int(scaledSize.width), Int(scaledSize.height), bytesPerRow)
}
public func generateGrayscaleAlphaMaskImage(image: UIImage) -> UIImage? {
let selectedScale = image.scale
let scaledSize = CGSize(width: image.size.width * selectedScale, height: image.size.height * selectedScale)
let bytesPerRow = (1 * Int(scaledSize.width) + 15) & (~15)
let length = bytesPerRow * Int(scaledSize.height)
let bytes = malloc(length)!.assumingMemoryBound(to: UInt8.self)
memset(bytes, 0, length)
guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in
free(bytes)
})
else {
return nil
}
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: grayscaleColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
return nil
}
context.scaleBy(x: selectedScale, y: selectedScale)
withImageBytes(image: image, { pixels, width, height, imageBytesPerRow in
var src = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: pixels), height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: imageBytesPerRow)
let permuteMap: [UInt8] = [3, 2, 1, 0]
vImagePermuteChannels_ARGB8888(&src, &src, permuteMap, vImage_Flags(kvImageDoNotTile))
vImageUnpremultiplyData_ARGB8888(&src, &src, vImage_Flags(kvImageDoNotTile))
for y in 0 ..< Int(scaledSize.height) {
let srcRowBytes = pixels.advanced(by: y * imageBytesPerRow)
let dstRowBytes = bytes.advanced(by: y * bytesPerRow)
for x in 0 ..< Int(scaledSize.width) {
let a = srcRowBytes.advanced(by: x * 4 + 0).pointee
dstRowBytes.advanced(by: x).pointee = 0xff &- a
}
}
})
guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 8, bytesPerRow: bytesPerRow, space: grayscaleColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent)
else {
return nil
}
return UIImage(cgImage: image, scale: selectedScale, orientation: .up)
}
public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) -> Void, opaque: Bool = false, scale: CGFloat? = nil) -> UIImage? {
let selectedScale = scale ?? deviceScale
let scaledSize = CGSize(width: size.width * selectedScale, height: size.height * selectedScale)

View File

@ -195,6 +195,13 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
lhs = lhs.advanced(by: 1)
rhs = rhs.advanced(by: 1)
}
var lhsRest = UnsafeMutableRawPointer(frameBytes).assumingMemoryBound(to: UInt8.self).advanced(by: (decodeBufferLength / 8) * 8)
var rhsRest = UnsafeMutableRawPointer(decodeBytes).assumingMemoryBound(to: UInt8.self).advanced(by: (decodeBufferLength / 8) * 8)
for _ in (decodeBufferLength / 8) * 8 ..< decodeBufferLength {
lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee
lhsRest = lhsRest.advanced(by: 1)
rhsRest = rhsRest.advanced(by: 1)
}
frameData = Data(bytes: frameBytes, count: decodeBufferLength)
}

View File

@ -99,19 +99,21 @@ func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: St
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
if let colorImage = context.generateImage() {
if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) {
let colorContext = DrawingContext(size: size, scale: 1.0, clear: false)
colorContext.withFlippedContext { c in
c.setFillColor(UIColor.black.cgColor)
c.fill(CGRect(origin: CGPoint(), size: size))
c.draw(colorSourceImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
}
guard let colorImage = colorContext.generateImage() else {
return
}
let colorData = NSMutableData()
let alphaData = NSMutableData()
let alphaImage = generateImage(size, contextGenerator: { size, context in
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: CGPoint(), size: size), mask: colorImage.cgImage!)
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}, scale: 1.0)
if let alphaImage = alphaImage, let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
@ -257,8 +259,12 @@ func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize,
lhs = lhs.advanced(by: 1)
rhs = rhs.advanced(by: 1)
}
for i in (yuvaLength / 8) * 8 ..< yuvaLength {
var lhsRest = previousYuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
var rhsRest = yuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
for _ in (yuvaLength / 8) * 8 ..< yuvaLength {
lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee
lhsRest = lhsRest.advanced(by: 1)
rhsRest = rhsRest.advanced(by: 1)
}
deltaTime += CACurrentMediaTime() - deltaStartTime

View File

@ -271,7 +271,7 @@ final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentati
let height: Int32
var uniqueId: String {
return "animated-sticker-\(self.width)x\(self.height)-v6"
return "animated-sticker-\(self.width)x\(self.height)-v7"
}
init(width: Int32, height: Int32) {

View File

@ -551,9 +551,6 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile
}
}
let img = context.generateImage()
let cgImg = img?.cgImage
return context
}
}

View File

@ -22,12 +22,16 @@ void encodeRGBAToYUVA(uint8_t *yuva, uint8_t const *argb, int width, int height,
error = vImageUnpremultiplyData_ARGB8888(&src, &src, kvImageDoNotTile);
uint8_t *buf = (uint8_t *)argb;
uint8_t *alpha = yuva + (width * height * 1 + width * height * 1);
for (int i = 0; i < width * height; i += 2) {
uint8_t a0 = (buf[i * 4 + 0] >> 4) << 4;
uint8_t a1 = (buf[(i + 1) * 4 + 0] >> 4) << 4;
alpha[i / 2] = (a0 & (0xf0U)) | ((a1 & (0xf0U)) >> 4);
uint8_t *alpha = yuva + width * height * 2;
int i = 0;
for (int y = 0; y < height; y += 1) {
uint8_t const *argbRow = argb + y * bytesPerRow;
for (int x = 0; x < width; x += 2) {
uint8_t a0 = (argbRow[x * 4 + 0] >> 4) << 4;
uint8_t a1 = (argbRow[(x + 1) * 4 + 0] >> 4) << 4;
alpha[i / 2] = (a0 & (0xf0U)) | ((a1 & (0xf0U)) >> 4);
i += 2;
}
}
vImage_Buffer destYp;
@ -79,12 +83,17 @@ void decodeYUVAToRGBA(uint8_t const *yuva, uint8_t *argb, int width, int height,
error = vImageConvert_420Yp8_CbCr8ToARGB8888(&srcYp, &srcCbCr, &dest, &info, NULL, 0xff, kvImageDoNotTile);
uint8_t const *alpha = yuva + (width * height * 1 + width * height * 1);
for (int i = 0; i < width * height; i += 2) {
uint8_t a = alpha[i / 2];
uint8_t a1 = (a & (0xf0U));
uint8_t a2 = ((a & (0x0fU)) << 4);
argb[i * 4 + 0] = a1 | (a1 >> 4);
argb[(i + 1) * 4 + 0] = a2 | (a2 >> 4);
int i = 0;
for (int y = 0; y < height; y += 1) {
uint8_t *argbRow = argb + y * bytesPerRow;
for (int x = 0; x < width; x += 2) {
uint8_t a = alpha[i / 2];
uint8_t a1 = (a & (0xf0U));
uint8_t a2 = ((a & (0x0fU)) << 4);
argbRow[x * 4 + 0] = a1 | (a1 >> 4);
argbRow[(x + 1) * 4 + 0] = a2 | (a2 >> 4);
i += 2;
}
}
error = vImagePremultiplyData_ARGB8888(&dest, &dest, kvImageDoNotTile);