diff --git a/Telegram-iOS.xcworkspace/xcshareddata/xcschemes/Telegram-iOS-Hockeyapp-Internal.xcscheme b/Telegram-iOS.xcworkspace/xcshareddata/xcschemes/Telegram-iOS-Hockeyapp-Internal.xcscheme index 2db06cb59b..61e39f8bfe 100644 --- a/Telegram-iOS.xcworkspace/xcshareddata/xcschemes/Telegram-iOS-Hockeyapp-Internal.xcscheme +++ b/Telegram-iOS.xcworkspace/xcshareddata/xcschemes/Telegram-iOS-Hockeyapp-Internal.xcscheme @@ -81,7 +81,6 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" enableAddressSanitizer = "YES" enableASanStackUseAfterReturn = "YES" - enableUBSanitizer = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/submodules/Display/Display/GenerateImage.swift b/submodules/Display/Display/GenerateImage.swift index 7c02da4e7c..7fd2d96038 100644 --- a/submodules/Display/Display/GenerateImage.swift +++ b/submodules/Display/Display/GenerateImage.swift @@ -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, 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, 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) diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift index 986b3d8307..cd1448d983 100644 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift +++ b/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift @@ -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) } diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift index 541d311c4a..bb317b7bf1 100644 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift +++ b/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift @@ -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 diff --git a/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift b/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift index cdd435ad6a..e962023208 100644 --- a/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift +++ b/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift @@ -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) { diff --git a/submodules/TelegramUI/TelegramUI/StickerResources.swift b/submodules/TelegramUI/TelegramUI/StickerResources.swift index 73e54f2246..772a8c99a2 100644 --- a/submodules/TelegramUI/TelegramUI/StickerResources.swift +++ b/submodules/TelegramUI/TelegramUI/StickerResources.swift @@ -551,9 +551,6 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile } } - let img = context.generateImage() - let cgImg = img?.cgImage - return context } } diff --git a/submodules/TelegramUI/TelegramUI/YUV.m b/submodules/TelegramUI/TelegramUI/YUV.m index 78381d714e..08a52aba58 100644 --- a/submodules/TelegramUI/TelegramUI/YUV.m +++ b/submodules/TelegramUI/TelegramUI/YUV.m @@ -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);