mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'experiments/animation-residual-coding'
This commit is contained in:
commit
228bc712e9
@ -873,7 +873,7 @@ private final class NotificationServiceHandler {
|
||||
|
||||
enum Action {
|
||||
case logout
|
||||
case poll(peerId: PeerId, content: NotificationContent)
|
||||
case poll(peerId: PeerId, content: NotificationContent, messageId: MessageId?)
|
||||
case deleteMessage([MessageId])
|
||||
case readMessage(MessageId)
|
||||
case call(CallData)
|
||||
@ -889,7 +889,7 @@ private final class NotificationServiceHandler {
|
||||
action = .logout
|
||||
case "MESSAGE_MUTED":
|
||||
if let peerId = peerId {
|
||||
action = .poll(peerId: peerId, content: NotificationContent(isLockedMessage: nil))
|
||||
action = .poll(peerId: peerId, content: NotificationContent(isLockedMessage: nil), messageId: nil)
|
||||
}
|
||||
case "MESSAGE_DELETED":
|
||||
if let peerId = peerId {
|
||||
@ -930,9 +930,12 @@ private final class NotificationServiceHandler {
|
||||
return
|
||||
}
|
||||
|
||||
var messageIdValue: MessageId?
|
||||
if let messageId = messageId {
|
||||
content.userInfo["msg_id"] = "\(messageId)"
|
||||
interactionAuthorId = peerId
|
||||
|
||||
messageIdValue = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageId)
|
||||
}
|
||||
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
@ -1021,7 +1024,7 @@ private final class NotificationServiceHandler {
|
||||
}
|
||||
}*/
|
||||
|
||||
action = .poll(peerId: peerId, content: content)
|
||||
action = .poll(peerId: peerId, content: content, messageId: messageIdValue)
|
||||
|
||||
updateCurrentContent(content)
|
||||
}
|
||||
@ -1066,7 +1069,7 @@ private final class NotificationServiceHandler {
|
||||
let content = NotificationContent(isLockedMessage: nil)
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
case let .poll(peerId, initialContent):
|
||||
case let .poll(peerId, initialContent, messageId):
|
||||
Logger.shared.log("NotificationService \(episode)", "Will poll")
|
||||
if let stateManager = strongSelf.stateManager {
|
||||
let pollCompletion: (NotificationContent) -> Void = { content in
|
||||
@ -1325,13 +1328,32 @@ private final class NotificationServiceHandler {
|
||||
}
|
||||
|
||||
let pollWithUpdatedContent: Signal<NotificationContent, NoError>
|
||||
if let interactionAuthorId = interactionAuthorId {
|
||||
if interactionAuthorId != nil || messageId != nil {
|
||||
pollWithUpdatedContent = stateManager.postbox.transaction { transaction -> NotificationContent in
|
||||
var content = initialContent
|
||||
|
||||
if let interactionAuthorId = interactionAuthorId {
|
||||
if inAppNotificationSettings.displayNameOnLockscreen, let peer = transaction.getPeer(interactionAuthorId) {
|
||||
content.addSenderInfo(mediaBox: stateManager.postbox.mediaBox, accountPeerId: stateManager.accountPeerId, peer: peer)
|
||||
}
|
||||
}
|
||||
|
||||
if let messageId = messageId {
|
||||
if let readState = transaction.getCombinedPeerReadState(messageId.peerId) {
|
||||
for (namespace, state) in readState.states {
|
||||
if namespace == messageId.namespace {
|
||||
switch state {
|
||||
case let .idBased(maxIncomingReadId, _, _, _, _):
|
||||
if maxIncomingReadId >= messageId.id {
|
||||
content = NotificationContent(isLockedMessage: nil)
|
||||
}
|
||||
case .indexBased:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
155
Tests/AnimationCacheTest/BUILD
Normal file
155
Tests/AnimationCacheTest/BUILD
Normal file
@ -0,0 +1,155 @@
|
||||
load("@build_bazel_rules_apple//apple:ios.bzl",
|
||||
"ios_application",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl",
|
||||
"swift_library",
|
||||
)
|
||||
|
||||
load("//build-system/bazel-utils:plist_fragment.bzl",
|
||||
"plist_fragment",
|
||||
)
|
||||
|
||||
module_name = "AnimationCacheTest"
|
||||
|
||||
filegroup(
|
||||
name = "AppResources",
|
||||
srcs = glob([
|
||||
"Resources/**/*",
|
||||
], exclude = ["Resources/**/.*"]),
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "Lib",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
data = [
|
||||
":AppResources",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/rlottie:RLottieBinding",
|
||||
],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "BuildNumberInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
"""
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "VersionInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
"""
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "AppNameInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Test</string>
|
||||
"""
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "AppInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Test</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.telegram.{module_name}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Telegram</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<false/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UIViewEdgeAntialiasing</key>
|
||||
<false/>
|
||||
<key>UIViewGroupOpacity</key>
|
||||
<false/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
""".format(module_name=module_name)
|
||||
)
|
||||
|
||||
ios_application(
|
||||
name = module_name,
|
||||
bundle_id = "org.telegram.{}".format(module_name),
|
||||
families = ["iphone", "ipad"],
|
||||
minimum_os_version = "9.0",
|
||||
provisioning_profile = "@build_configuration//provisioning:Wildcard.mobileprovision",
|
||||
infoplists = [
|
||||
":AppInfoPlist",
|
||||
":BuildNumberInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
],
|
||||
resources = [
|
||||
"//Tests/Common:LaunchScreen",
|
||||
],
|
||||
frameworks = [
|
||||
],
|
||||
deps = [
|
||||
"//Tests/Common:Main",
|
||||
":Lib",
|
||||
],
|
||||
)
|
1
Tests/AnimationCacheTest/Resources/Cat.json
Normal file
1
Tests/AnimationCacheTest/Resources/Cat.json
Normal file
File diff suppressed because one or more lines are too long
1
Tests/AnimationCacheTest/Resources/Test.json
Normal file
1
Tests/AnimationCacheTest/Resources/Test.json
Normal file
File diff suppressed because one or more lines are too long
1
Tests/AnimationCacheTest/Resources/Test2.json
Normal file
1
Tests/AnimationCacheTest/Resources/Test2.json
Normal file
File diff suppressed because one or more lines are too long
21
Tests/AnimationCacheTest/Sources/AppDelegate.swift
Normal file
21
Tests/AnimationCacheTest/Sources/AppDelegate.swift
Normal file
@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
@objc(Application)
|
||||
public final class Application: UIApplication {
|
||||
}
|
||||
|
||||
@objc(AppDelegate)
|
||||
public final class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
public var window: UIWindow?
|
||||
|
||||
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
let window = UIWindow()
|
||||
self.window = window
|
||||
|
||||
window.rootViewController = ViewController()
|
||||
window.makeKeyAndVisible()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
123
Tests/AnimationCacheTest/Sources/ViewController.swift
Normal file
123
Tests/AnimationCacheTest/Sources/ViewController.swift
Normal file
@ -0,0 +1,123 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
import Display
|
||||
import RLottieBinding
|
||||
import AnimationCache
|
||||
import SwiftSignalKit
|
||||
|
||||
public final class ViewController: UIViewController {
|
||||
private var imageView: UIImageView?
|
||||
private var imageViewLarge: UIImageView?
|
||||
|
||||
private var cache: AnimationCache?
|
||||
private var animationCacheItem: AnimationCacheItem?
|
||||
|
||||
//private let playbackSize = CGSize(width: 512, height: 512)
|
||||
private let playbackSize = CGSize(width: 48.0, height: 48.0)
|
||||
//private let playbackSize = CGSize(width: 16, height: 16)
|
||||
|
||||
private var fpsCount: Int = 0
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.backgroundColor = .white
|
||||
|
||||
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0.0, y: 20.0), size: CGSize(width: 48.0, height: 48.0)))
|
||||
self.imageView = imageView
|
||||
self.view.addSubview(imageView)
|
||||
|
||||
let imageViewLarge = UIImageView(frame: CGRect(origin: CGPoint(x: 0.0, y: 20.0 + 48.0 + 10.0), size: CGSize(width: 256.0, height: 256.0)))
|
||||
//imageViewLarge.layer.magnificationFilter = .nearest
|
||||
self.imageViewLarge = imageViewLarge
|
||||
self.view.addSubview(imageViewLarge)
|
||||
|
||||
self.loadItem()
|
||||
|
||||
if #available(iOS 10.0, *) {
|
||||
let timer = Foundation.Timer(timeInterval: 1.0, repeats: true, block: { _ in
|
||||
print(self.fpsCount)
|
||||
self.fpsCount = 0
|
||||
})
|
||||
RunLoop.main.add(timer, forMode: .common)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadItem() {
|
||||
let basePath = NSTemporaryDirectory() + "/animation-cache"
|
||||
let _ = try? FileManager.default.removeItem(atPath: basePath)
|
||||
let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: basePath), withIntermediateDirectories: true)
|
||||
self.cache = AnimationCacheImpl(basePath: basePath, allocateTempFile: {
|
||||
return basePath + "/\(Int64.random(in: 0 ... Int64.max))"
|
||||
})
|
||||
|
||||
let path = Bundle.main.path(forResource: "Test2", ofType: "json")!
|
||||
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
||||
|
||||
let scaledSize = CGSize(width: self.playbackSize.width * 2.0, height: self.playbackSize.height * 2.0)
|
||||
let _ = (self.cache!.get(sourceId: "Item\(Int64.random(in: 0 ... Int64.max))", size: scaledSize, fetch: { size, writer in
|
||||
writer.queue.async {
|
||||
let lottieInstance = LottieInstance(data: data, fitzModifier: .none, colorReplacements: nil, cacheKey: "")!
|
||||
|
||||
for i in 0 ..< min(600, Int(lottieInstance.frameCount)) {
|
||||
//for _ in 0 ..< 10 {
|
||||
writer.add(with: { surface in
|
||||
let _ = i
|
||||
lottieInstance.renderFrame(with: Int32(i), into: surface.argb, width: Int32(surface.width), height: Int32(surface.height), bytesPerRow: Int32(surface.bytesPerRow))
|
||||
|
||||
return 1.0 / 60.0
|
||||
}, proposedWidth: Int(scaledSize.width), proposedHeight: Int(scaledSize.height), insertKeyframe: false)
|
||||
//}
|
||||
}
|
||||
|
||||
writer.finish()
|
||||
}
|
||||
|
||||
return EmptyDisposable
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { result in
|
||||
if !result.isFinal {
|
||||
return
|
||||
}
|
||||
|
||||
self.animationCacheItem = result.item
|
||||
|
||||
self.updateImage()
|
||||
})
|
||||
}
|
||||
|
||||
private func updateImage() {
|
||||
guard let animationCacheItem = self.animationCacheItem else {
|
||||
self.loadItem()
|
||||
return
|
||||
}
|
||||
|
||||
if let frame = animationCacheItem.advance(advance: .frames(1), requestedFormat: .rgba) {
|
||||
switch frame.format {
|
||||
case let .rgba(data, width, height, bytesPerRow):
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: false, bytesPerRow: bytesPerRow)
|
||||
|
||||
data.withUnsafeBytes { bytes -> Void in
|
||||
memcpy(context.bytes, bytes.baseAddress!, height * bytesPerRow)
|
||||
}
|
||||
|
||||
self.imageView?.image = context.generateImage()
|
||||
self.imageViewLarge?.image = self.imageView?.image
|
||||
|
||||
self.fpsCount += 1
|
||||
|
||||
/*DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0 / 60.0, execute: { [weak self] in
|
||||
self?.updateImage()
|
||||
})*/
|
||||
DispatchQueue.main.async {
|
||||
self.updateImage()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else {
|
||||
self.loadItem()
|
||||
}
|
||||
}
|
||||
}
|
@ -16,4 +16,19 @@ public final class Lock {
|
||||
f()
|
||||
pthread_mutex_unlock(&self.mutex)
|
||||
}
|
||||
|
||||
public func throwingLocked(_ f: () throws -> Void) throws {
|
||||
var error: Error?
|
||||
pthread_mutex_lock(&self.mutex)
|
||||
do {
|
||||
try f()
|
||||
} catch let e {
|
||||
error = e
|
||||
}
|
||||
pthread_mutex_unlock(&self.mutex)
|
||||
|
||||
if let error = error {
|
||||
throw(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1090087980] = { return SecretApi144.MessageEntity.parse_messageEntityStrike($0) }
|
||||
dict[34469328] = { return SecretApi144.MessageEntity.parse_messageEntityBlockquote($0) }
|
||||
dict[Int32(bitPattern: 0xc8cf05f8 as UInt32)] = { return SecretApi144.MessageEntity.parse_messageEntityCustomEmoji($0) }
|
||||
dict[Int32(bitPattern: 0x32ca960f as UInt32)] = { return SecretApi144.MessageEntity.parse_messageEntitySpoiler($0) }
|
||||
dict[144661578] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) }
|
||||
dict[893913689] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) }
|
||||
dict[1485441687] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) }
|
||||
@ -886,6 +887,7 @@ public struct SecretApi144 {
|
||||
case messageEntityStrike(offset: Int32, length: Int32)
|
||||
case messageEntityBlockquote(offset: Int32, length: Int32)
|
||||
case messageEntityCustomEmoji(offset: Int32, length: Int32, documentId: Int64)
|
||||
case messageEntitySpoiler(offset: Int32, length: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -997,6 +999,13 @@ public struct SecretApi144 {
|
||||
serializeInt32(length, buffer: buffer, boxed: false)
|
||||
serializeInt64(documentId, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .messageEntitySpoiler(let offset, let length):
|
||||
if boxed {
|
||||
buffer.appendInt32(Int32(bitPattern: 0x32ca960f as UInt32))
|
||||
}
|
||||
serializeInt32(offset, buffer: buffer, boxed: false)
|
||||
serializeInt32(length, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? {
|
||||
@ -1218,6 +1227,20 @@ public struct SecretApi144 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
fileprivate static func parse_messageEntitySpoiler(_ reader: BufferReader) -> MessageEntity? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return SecretApi144.MessageEntity.messageEntitySpoiler(offset: _1!, length: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum DecryptedMessageMedia {
|
||||
|
@ -910,7 +910,7 @@ private func decryptedEntities144(_ entities: [MessageTextEntity]?) -> [SecretAp
|
||||
case .BankCard:
|
||||
break
|
||||
case .Spoiler:
|
||||
break
|
||||
result.append(.messageEntitySpoiler(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
||||
case let .CustomEmoji(_, fileId):
|
||||
result.append(.messageEntityCustomEmoji(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count), documentId: fileId))
|
||||
case .Custom:
|
||||
|
@ -1224,6 +1224,8 @@ private func parseEntities(_ entities: [SecretApi144.MessageEntity]) -> TextEnti
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Underline))
|
||||
case let .messageEntityBlockquote(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BlockQuote))
|
||||
case let .messageEntitySpoiler(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Spoiler))
|
||||
case let .messageEntityCustomEmoji(offset, length, documentId):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .CustomEmoji(stickerPack: nil, fileId: documentId)))
|
||||
case .messageEntityUnknown:
|
||||
|
@ -49,17 +49,19 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
|
||||
public let title: String
|
||||
public let shortName: String
|
||||
public let thumbnail: TelegramMediaImageRepresentation?
|
||||
public let thumbnailFileId: Int64?
|
||||
public let immediateThumbnailData: Data?
|
||||
public let hash: Int32
|
||||
public let count: Int32
|
||||
|
||||
public init(id: ItemCollectionId, flags: StickerPackCollectionInfoFlags, accessHash: Int64, title: String, shortName: String, thumbnail: TelegramMediaImageRepresentation?, immediateThumbnailData: Data?, hash: Int32, count: Int32) {
|
||||
public init(id: ItemCollectionId, flags: StickerPackCollectionInfoFlags, accessHash: Int64, title: String, shortName: String, thumbnail: TelegramMediaImageRepresentation?, thumbnailFileId: Int64?, immediateThumbnailData: Data?, hash: Int32, count: Int32) {
|
||||
self.id = id
|
||||
self.flags = flags
|
||||
self.accessHash = accessHash
|
||||
self.title = title
|
||||
self.shortName = shortName
|
||||
self.thumbnail = thumbnail
|
||||
self.thumbnailFileId = thumbnailFileId
|
||||
self.immediateThumbnailData = immediateThumbnailData
|
||||
self.hash = hash
|
||||
self.count = count
|
||||
@ -71,6 +73,7 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
|
||||
self.title = decoder.decodeStringForKey("t", orElse: "")
|
||||
self.shortName = decoder.decodeStringForKey("s", orElse: "")
|
||||
self.thumbnail = decoder.decodeObjectForKey("th", decoder: { TelegramMediaImageRepresentation(decoder: $0) }) as? TelegramMediaImageRepresentation
|
||||
self.thumbnailFileId = decoder.decodeOptionalInt64ForKey("tfi")
|
||||
self.immediateThumbnailData = decoder.decodeDataForKey("itd")
|
||||
self.hash = decoder.decodeInt32ForKey("h", orElse: 0)
|
||||
self.flags = StickerPackCollectionInfoFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0))
|
||||
@ -88,6 +91,11 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "th")
|
||||
}
|
||||
if let thumbnailFileId = self.thumbnailFileId {
|
||||
encoder.encodeInt64(thumbnailFileId, forKey: "tfi")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "tfi")
|
||||
}
|
||||
if let immediateThumbnailData = self.immediateThumbnailData {
|
||||
encoder.encodeData(immediateThumbnailData, forKey: "itd")
|
||||
} else {
|
||||
@ -119,6 +127,10 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.thumbnailFileId != rhs.thumbnailFileId {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.flags != rhs.flags {
|
||||
return false
|
||||
}
|
||||
|
@ -58,9 +58,7 @@ extension StickerPackCollectionInfo {
|
||||
immediateThumbnailData = data
|
||||
}
|
||||
|
||||
let _ = thumbDocumentId
|
||||
|
||||
self.init(id: ItemCollectionId(namespace: namespace, id: id), flags: setFlags, accessHash: accessHash, title: title, shortName: shortName, thumbnail: thumbnailRepresentation, immediateThumbnailData: immediateThumbnailData, hash: nHash, count: count)
|
||||
self.init(id: ItemCollectionId(namespace: namespace, id: id), flags: setFlags, accessHash: accessHash, title: title, shortName: shortName, thumbnail: thumbnailRepresentation, thumbnailFileId: thumbDocumentId, immediateThumbnailData: immediateThumbnailData, hash: nHash, count: count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCo
|
||||
if let namespace = namespace {
|
||||
var mappedInfo = info
|
||||
if items.isEmpty {
|
||||
mappedInfo = StickerPackCollectionInfo(id: info.id, flags: info.flags, accessHash: info.accessHash, title: info.title, shortName: info.shortName, thumbnail: info.thumbnail, immediateThumbnailData: info.immediateThumbnailData, hash: Int32(bitPattern: arc4random()), count: info.count)
|
||||
mappedInfo = StickerPackCollectionInfo(id: info.id, flags: info.flags, accessHash: info.accessHash, title: info.title, shortName: info.shortName, thumbnail: info.thumbnail, thumbnailFileId: info.thumbnailFileId, immediateThumbnailData: info.immediateThumbnailData, hash: Int32(bitPattern: arc4random()), count: info.count)
|
||||
}
|
||||
addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: namespace, content: .add([mappedInfo.id]), noDelay: items.isEmpty)
|
||||
var updatedInfos = transaction.getItemCollectionsInfos(namespace: mappedInfo.id.namespace).map { $0.1 as! StickerPackCollectionInfo }
|
||||
|
@ -5,9 +5,15 @@
|
||||
|
||||
#import <ImageDCT/YuvConversion.h>
|
||||
|
||||
typedef NS_ENUM(NSUInteger, ImageDCTTableType) {
|
||||
ImageDCTTableTypeLuma,
|
||||
ImageDCTTableTypeChroma,
|
||||
ImageDCTTableTypeDelta
|
||||
};
|
||||
|
||||
@interface ImageDCTTable : NSObject
|
||||
|
||||
- (instancetype _Nonnull)initWithQuality:(NSInteger)quality isChroma:(bool)isChroma;
|
||||
- (instancetype _Nonnull)initWithQuality:(NSInteger)quality type:(ImageDCTTableType)type;
|
||||
- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data;
|
||||
|
||||
- (NSData * _Nonnull)serializedData;
|
||||
@ -20,6 +26,8 @@
|
||||
|
||||
- (void)forwardWithPixels:(uint8_t const * _Nonnull)pixels coefficients:(int16_t * _Nonnull)coefficients width:(NSInteger)width height:(NSInteger)height bytesPerRow:(NSInteger)bytesPerRow __attribute__((objc_direct));
|
||||
- (void)inverseWithCoefficients:(int16_t const * _Nonnull)coefficients pixels:(uint8_t * _Nonnull)pixels width:(NSInteger)width height:(NSInteger)height coefficientsPerRow:(NSInteger)coefficientsPerRow bytesPerRow:(NSInteger)bytesPerRow __attribute__((objc_direct));
|
||||
- (void)forward4x4:(int16_t const * _Nonnull)normalizedCoefficients coefficients:(int16_t * _Nonnull)coefficients width:(NSInteger)width height:(NSInteger)height __attribute__((objc_direct));
|
||||
- (void)inverse4x4:(int16_t const * _Nonnull)coefficients normalizedCoefficients:(int16_t * _Nonnull)normalizedCoefficients width:(NSInteger)width height:(NSInteger)height __attribute__((objc_direct));
|
||||
|
||||
@end
|
||||
|
||||
|
@ -3,8 +3,19 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus__
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, uint8_t *outV, uint8_t *outA, int width, int height, int bytesPerRow);
|
||||
void combineYUVAPlanesIntoARBB(uint8_t *argb, uint8_t const *inY, uint8_t const *inU, uint8_t const *inV, uint8_t const *inA, int width, int height, int bytesPerRow);
|
||||
void combineYUVAPlanesIntoARGB(uint8_t *argb, uint8_t const *inY, uint8_t const *inU, uint8_t const *inV, uint8_t const *inA, int width, int height, int bytesPerRow);
|
||||
void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outBytesPerRow, uint8_t const *inPlane, int inWidth, int inHeight, int inBytesPerRow);
|
||||
|
||||
void subtractArraysInt16(int16_t const *a, int16_t const *b, uint16_t *dest, int length);
|
||||
void subtractArraysUInt8Int16(uint8_t const *a, int16_t const *b, uint8_t *dest, int length);
|
||||
|
||||
#ifdef __cplusplus__
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* YuvConversion_h */
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "DCTCommon.h"
|
||||
|
||||
#include <vector>
|
||||
#include <Accelerate/Accelerate.h>
|
||||
|
||||
#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */
|
||||
#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */
|
||||
@ -128,6 +129,16 @@ static DCTELEM std_chrominance_quant_tbl[DCTSIZE2] = {
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99
|
||||
};
|
||||
static DCTELEM std_delta_quant_tbl[DCTSIZE2] = {
|
||||
16, 16, 16, 16, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 16
|
||||
};
|
||||
|
||||
int jpeg_quality_scaling(int quality)
|
||||
/* Convert a user-specified quality rating to a percentage scaling factor
|
||||
@ -285,14 +296,16 @@ static const int zigZagInv[DCTSIZE2] = {
|
||||
53,60,61,54,47,55,62,63
|
||||
};
|
||||
|
||||
static const int zigZag[DCTSIZE2] = {
|
||||
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
|
||||
static const int zigZag4x4Inv[4 * 4] = {
|
||||
0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15
|
||||
};
|
||||
|
||||
void performForwardDct(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow, DCTELEM *divisors) {
|
||||
DCTELEM block[DCTSIZE2];
|
||||
JCOEF coefBlock[DCTSIZE2];
|
||||
|
||||
int acOffset = (width / DCTSIZE) * (height / DCTSIZE);
|
||||
|
||||
for (int y = 0; y < height; y += DCTSIZE) {
|
||||
for (int x = 0; x < width; x += DCTSIZE) {
|
||||
for (int blockY = 0; blockY < DCTSIZE; blockY++) {
|
||||
@ -305,9 +318,17 @@ void performForwardDct(uint8_t const *pixels, int16_t *coefficients, int width,
|
||||
|
||||
quantize(coefBlock, divisors, block);
|
||||
|
||||
coefficients[(y / DCTSIZE) * (width / DCTSIZE) + x / DCTSIZE] = coefBlock[0];
|
||||
|
||||
for (int blockY = 0; blockY < DCTSIZE; blockY++) {
|
||||
for (int blockX = 0; blockX < DCTSIZE; blockX++) {
|
||||
coefficients[(y + blockY) * bytesPerRow + (x + blockX)] = coefBlock[zigZagInv[blockY * DCTSIZE + blockX]];
|
||||
if (blockX == 0 && blockY == 0) {
|
||||
continue;
|
||||
}
|
||||
int16_t element = coefBlock[zigZagInv[blockY * DCTSIZE + blockX]];
|
||||
//coefficients[(y + blockY) * bytesPerRow + (x + blockX)] = element;
|
||||
coefficients[acOffset] = element;
|
||||
acOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -318,11 +339,21 @@ void performInverseDct(int16_t const * coefficients, uint8_t *pixels, int width,
|
||||
DCTELEM coefficientBlock[DCTSIZE2];
|
||||
JSAMPLE pixelBlock[DCTSIZE2];
|
||||
|
||||
int acOffset = (width / DCTSIZE) * (height / DCTSIZE);
|
||||
|
||||
for (int y = 0; y < height; y += DCTSIZE) {
|
||||
for (int x = 0; x < width; x += DCTSIZE) {
|
||||
coefficientBlock[0] = coefficients[(y / DCTSIZE) * (width / DCTSIZE) + x / DCTSIZE];
|
||||
|
||||
for (int blockY = 0; blockY < DCTSIZE; blockY++) {
|
||||
for (int blockX = 0; blockX < DCTSIZE; blockX++) {
|
||||
coefficientBlock[zigZag[blockY * DCTSIZE + blockX]] = coefficients[(y + blockY) * coefficientsPerRow + (x + blockX)];
|
||||
if (blockX == 0 && blockY == 0) {
|
||||
continue;
|
||||
}
|
||||
int16_t element = coefficients[acOffset];
|
||||
acOffset++;
|
||||
coefficientBlock[zigZagInv[blockY * DCTSIZE + blockX]] = element;
|
||||
//coefficientBlock[zigZagInv[blockY * DCTSIZE + blockX]] = coefficients[(y + blockY) * coefficientsPerRow + (x + blockX)];
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,18 +368,516 @@ void performInverseDct(int16_t const * coefficients, uint8_t *pixels, int width,
|
||||
}
|
||||
}
|
||||
|
||||
void matrix_multiply_4x4_neon(float32_t *A, float32_t *B, float32_t *C) {
|
||||
// these are the columns A
|
||||
float32x4_t A0;
|
||||
float32x4_t A1;
|
||||
float32x4_t A2;
|
||||
float32x4_t A3;
|
||||
|
||||
// these are the columns B
|
||||
float32x4_t B0;
|
||||
float32x4_t B1;
|
||||
float32x4_t B2;
|
||||
float32x4_t B3;
|
||||
|
||||
// these are the columns C
|
||||
float32x4_t C0;
|
||||
float32x4_t C1;
|
||||
float32x4_t C2;
|
||||
float32x4_t C3;
|
||||
|
||||
A0 = vld1q_f32(A);
|
||||
A1 = vld1q_f32(A+4);
|
||||
A2 = vld1q_f32(A+8);
|
||||
A3 = vld1q_f32(A+12);
|
||||
|
||||
// Zero accumulators for C values
|
||||
C0 = vmovq_n_f32(0);
|
||||
C1 = vmovq_n_f32(0);
|
||||
C2 = vmovq_n_f32(0);
|
||||
C3 = vmovq_n_f32(0);
|
||||
|
||||
// Multiply accumulate in 4x1 blocks, i.e. each column in C
|
||||
B0 = vld1q_f32(B);
|
||||
C0 = vfmaq_laneq_f32(C0, A0, B0, 0);
|
||||
C0 = vfmaq_laneq_f32(C0, A1, B0, 1);
|
||||
C0 = vfmaq_laneq_f32(C0, A2, B0, 2);
|
||||
C0 = vfmaq_laneq_f32(C0, A3, B0, 3);
|
||||
vst1q_f32(C, C0);
|
||||
|
||||
B1 = vld1q_f32(B+4);
|
||||
C1 = vfmaq_laneq_f32(C1, A0, B1, 0);
|
||||
C1 = vfmaq_laneq_f32(C1, A1, B1, 1);
|
||||
C1 = vfmaq_laneq_f32(C1, A2, B1, 2);
|
||||
C1 = vfmaq_laneq_f32(C1, A3, B1, 3);
|
||||
vst1q_f32(C+4, C1);
|
||||
|
||||
B2 = vld1q_f32(B+8);
|
||||
C2 = vfmaq_laneq_f32(C2, A0, B2, 0);
|
||||
C2 = vfmaq_laneq_f32(C2, A1, B2, 1);
|
||||
C2 = vfmaq_laneq_f32(C2, A2, B2, 2);
|
||||
C2 = vfmaq_laneq_f32(C2, A3, B2, 3);
|
||||
vst1q_f32(C+8, C2);
|
||||
|
||||
B3 = vld1q_f32(B+12);
|
||||
C3 = vfmaq_laneq_f32(C3, A0, B3, 0);
|
||||
C3 = vfmaq_laneq_f32(C3, A1, B3, 1);
|
||||
C3 = vfmaq_laneq_f32(C3, A2, B3, 2);
|
||||
C3 = vfmaq_laneq_f32(C3, A3, B3, 3);
|
||||
vst1q_f32(C+12, C3);
|
||||
}
|
||||
|
||||
typedef int16_t tran_low_t;
|
||||
typedef int32_t tran_high_t;
|
||||
typedef int16_t tran_coef_t;
|
||||
|
||||
static const tran_coef_t cospi_1_64 = 16364;
|
||||
static const tran_coef_t cospi_2_64 = 16305;
|
||||
static const tran_coef_t cospi_3_64 = 16207;
|
||||
static const tran_coef_t cospi_4_64 = 16069;
|
||||
static const tran_coef_t cospi_5_64 = 15893;
|
||||
static const tran_coef_t cospi_6_64 = 15679;
|
||||
static const tran_coef_t cospi_7_64 = 15426;
|
||||
static const tran_coef_t cospi_8_64 = 15137;
|
||||
static const tran_coef_t cospi_9_64 = 14811;
|
||||
static const tran_coef_t cospi_10_64 = 14449;
|
||||
static const tran_coef_t cospi_11_64 = 14053;
|
||||
static const tran_coef_t cospi_12_64 = 13623;
|
||||
static const tran_coef_t cospi_13_64 = 13160;
|
||||
static const tran_coef_t cospi_14_64 = 12665;
|
||||
static const tran_coef_t cospi_15_64 = 12140;
|
||||
static const tran_coef_t cospi_16_64 = 11585;
|
||||
static const tran_coef_t cospi_17_64 = 11003;
|
||||
static const tran_coef_t cospi_18_64 = 10394;
|
||||
static const tran_coef_t cospi_19_64 = 9760;
|
||||
static const tran_coef_t cospi_20_64 = 9102;
|
||||
static const tran_coef_t cospi_21_64 = 8423;
|
||||
static const tran_coef_t cospi_22_64 = 7723;
|
||||
static const tran_coef_t cospi_23_64 = 7005;
|
||||
static const tran_coef_t cospi_24_64 = 6270;
|
||||
static const tran_coef_t cospi_25_64 = 5520;
|
||||
static const tran_coef_t cospi_26_64 = 4756;
|
||||
static const tran_coef_t cospi_27_64 = 3981;
|
||||
static const tran_coef_t cospi_28_64 = 3196;
|
||||
static const tran_coef_t cospi_29_64 = 2404;
|
||||
static const tran_coef_t cospi_30_64 = 1606;
|
||||
static const tran_coef_t cospi_31_64 = 804;
|
||||
|
||||
// 16384 * sqrt(2) * sin(kPi/9) * 2 / 3
|
||||
static const tran_coef_t sinpi_1_9 = 5283;
|
||||
static const tran_coef_t sinpi_2_9 = 9929;
|
||||
static const tran_coef_t sinpi_3_9 = 13377;
|
||||
static const tran_coef_t sinpi_4_9 = 15212;
|
||||
|
||||
#define DCT_CONST_BITS 14
|
||||
#define DCT_CONST_ROUNDING (1 << (DCT_CONST_BITS - 1))
|
||||
|
||||
#define ROUND_POWER_OF_TWO(value, n) (((value) + (1 << ((n)-1))) >> (n))
|
||||
|
||||
static inline tran_high_t fdct_round_shift(tran_high_t input) {
|
||||
tran_high_t rv = ROUND_POWER_OF_TWO(input, DCT_CONST_BITS);
|
||||
// TODO(debargha, peter.derivaz): Find new bounds for this assert
|
||||
// and make the bounds consts.
|
||||
// assert(INT16_MIN <= rv && rv <= INT16_MAX);
|
||||
return rv;
|
||||
}
|
||||
|
||||
void fdct4x4_float(const int16_t *input, tran_low_t *output) {
|
||||
float inputFloat[4 * 4];
|
||||
for (int i = 0; i < 4 * 4; i++) {
|
||||
inputFloat[i] = (float)input[i];
|
||||
}
|
||||
float outputFloat[4 * 4];
|
||||
|
||||
int i, j, u, v;
|
||||
for (u = 0; u < 4; ++u) {
|
||||
for (v = 0; v < 4; ++v) {
|
||||
outputFloat[u * 4 + v] = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
for (j = 0; j < 4; j++) {
|
||||
outputFloat[u * 4 + v] += inputFloat[i * 4 + j] * cos(M_PI/((float)4)*(i+1./2.)*u)*cos(M_PI/((float)4)*(j+1./2.)*v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4 * 4; i++) {
|
||||
output[i] = (float)outputFloat[i];
|
||||
}
|
||||
}
|
||||
|
||||
void vpx_fdct4x4_c(const int16_t *input, tran_low_t *output, int stride) {
|
||||
// The 2D transform is done with two passes which are actually pretty
|
||||
// similar. In the first one, we transform the columns and transpose
|
||||
// the results. In the second one, we transform the rows. To achieve that,
|
||||
// as the first pass results are transposed, we transpose the columns (that
|
||||
// is the transposed rows) and transpose the results (so that it goes back
|
||||
// in normal/row positions).
|
||||
int pass;
|
||||
// We need an intermediate buffer between passes.
|
||||
tran_low_t intermediate[4 * 4];
|
||||
const tran_low_t *in_low = NULL;
|
||||
tran_low_t *out = intermediate;
|
||||
// Do the two transform/transpose passes
|
||||
for (pass = 0; pass < 2; ++pass) {
|
||||
tran_high_t in_high[4]; // canbe16
|
||||
tran_high_t step[4]; // canbe16
|
||||
tran_high_t temp1, temp2; // needs32
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
// Load inputs.
|
||||
if (pass == 0) {
|
||||
in_high[0] = input[0 * stride] * 16;
|
||||
in_high[1] = input[1 * stride] * 16;
|
||||
in_high[2] = input[2 * stride] * 16;
|
||||
in_high[3] = input[3 * stride] * 16;
|
||||
if (i == 0 && in_high[0]) {
|
||||
++in_high[0];
|
||||
}
|
||||
} else {
|
||||
assert(in_low != NULL);
|
||||
in_high[0] = in_low[0 * 4];
|
||||
in_high[1] = in_low[1 * 4];
|
||||
in_high[2] = in_low[2 * 4];
|
||||
in_high[3] = in_low[3 * 4];
|
||||
++in_low;
|
||||
}
|
||||
// Transform.
|
||||
step[0] = in_high[0] + in_high[3];
|
||||
step[1] = in_high[1] + in_high[2];
|
||||
step[2] = in_high[1] - in_high[2];
|
||||
step[3] = in_high[0] - in_high[3];
|
||||
temp1 = (step[0] + step[1]) * cospi_16_64;
|
||||
temp2 = (step[0] - step[1]) * cospi_16_64;
|
||||
out[0] = (tran_low_t)fdct_round_shift(temp1);
|
||||
out[2] = (tran_low_t)fdct_round_shift(temp2);
|
||||
temp1 = step[2] * cospi_24_64 + step[3] * cospi_8_64;
|
||||
temp2 = -step[2] * cospi_8_64 + step[3] * cospi_24_64;
|
||||
out[1] = (tran_low_t)fdct_round_shift(temp1);
|
||||
out[3] = (tran_low_t)fdct_round_shift(temp2);
|
||||
// Do next column (which is a transposed row in second/horizontal pass)
|
||||
++input;
|
||||
out += 4;
|
||||
}
|
||||
// Setup in/out for next pass.
|
||||
in_low = intermediate;
|
||||
out = output;
|
||||
}
|
||||
|
||||
{
|
||||
int i, j;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
for (j = 0; j < 4; ++j) output[j + i * 4] = (output[j + i * 4] + 1) >> 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define ROUND_POWER_OF_TWO(value, n) (((value) + (1 << ((n)-1))) >> (n))
|
||||
|
||||
static inline tran_high_t dct_const_round_shift(tran_high_t input) {
|
||||
tran_high_t rv = ROUND_POWER_OF_TWO(input, DCT_CONST_BITS);
|
||||
return (tran_high_t)rv;
|
||||
}
|
||||
|
||||
static inline tran_high_t check_range(tran_high_t input) {
|
||||
#ifdef CONFIG_COEFFICIENT_RANGE_CHECKING
|
||||
// For valid VP9 input streams, intermediate stage coefficients should always
|
||||
// stay within the range of a signed 16 bit integer. Coefficients can go out
|
||||
// of this range for invalid/corrupt VP9 streams. However, strictly checking
|
||||
// this range for every intermediate coefficient can burdensome for a decoder,
|
||||
// therefore the following assertion is only enabled when configured with
|
||||
// --enable-coefficient-range-checking.
|
||||
assert(INT16_MIN <= input);
|
||||
assert(input <= INT16_MAX);
|
||||
#endif // CONFIG_COEFFICIENT_RANGE_CHECKING
|
||||
return input;
|
||||
}
|
||||
|
||||
#define WRAPLOW(x) ((int32_t)check_range(x))
|
||||
|
||||
void idct4_c(const tran_low_t *input, tran_low_t *output) {
|
||||
int16_t step[4];
|
||||
tran_high_t temp1, temp2;
|
||||
|
||||
// stage 1
|
||||
temp1 = ((int16_t)input[0] + (int16_t)input[2]) * cospi_16_64;
|
||||
temp2 = ((int16_t)input[0] - (int16_t)input[2]) * cospi_16_64;
|
||||
step[0] = WRAPLOW(dct_const_round_shift(temp1));
|
||||
step[1] = WRAPLOW(dct_const_round_shift(temp2));
|
||||
temp1 = (int16_t)input[1] * cospi_24_64 - (int16_t)input[3] * cospi_8_64;
|
||||
temp2 = (int16_t)input[1] * cospi_8_64 + (int16_t)input[3] * cospi_24_64;
|
||||
step[2] = WRAPLOW(dct_const_round_shift(temp1));
|
||||
step[3] = WRAPLOW(dct_const_round_shift(temp2));
|
||||
|
||||
// stage 2
|
||||
output[0] = WRAPLOW(step[0] + step[3]);
|
||||
output[1] = WRAPLOW(step[1] + step[2]);
|
||||
output[2] = WRAPLOW(step[1] - step[2]);
|
||||
output[3] = WRAPLOW(step[0] - step[3]);
|
||||
}
|
||||
|
||||
void vpx_idct4x4_16_add_c(const tran_low_t *input, tran_low_t *dest, int stride) {
|
||||
int i, j;
|
||||
tran_low_t out[4 * 4];
|
||||
tran_low_t *outptr = out;
|
||||
tran_low_t temp_in[4], temp_out[4];
|
||||
|
||||
// Rows
|
||||
for (i = 0; i < 4; ++i) {
|
||||
idct4_c(input, outptr);
|
||||
input += 4;
|
||||
outptr += 4;
|
||||
}
|
||||
|
||||
// Columns
|
||||
for (i = 0; i < 4; ++i) {
|
||||
for (j = 0; j < 4; ++j) temp_in[j] = out[j * 4 + i];
|
||||
idct4_c(temp_in, temp_out);
|
||||
for (j = 0; j < 4; ++j) {
|
||||
dest[j * stride + i] = ROUND_POWER_OF_TWO(temp_out[j], 4);
|
||||
//dest[j * stride + i] = clip_pixel_add(dest[j * stride + i], ROUND_POWER_OF_TWO(temp_out[j], 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline int16x8_t load_tran_low_to_s16q(const tran_low_t *buf) {
|
||||
return vld1q_s16(buf);
|
||||
}
|
||||
|
||||
static inline void transpose_s16_4x4q(int16x8_t *a0, int16x8_t *a1) {
|
||||
// Swap 32 bit elements. Goes from:
|
||||
// a0: 00 01 02 03 10 11 12 13
|
||||
// a1: 20 21 22 23 30 31 32 33
|
||||
// to:
|
||||
// b0.val[0]: 00 01 20 21 10 11 30 31
|
||||
// b0.val[1]: 02 03 22 23 12 13 32 33
|
||||
|
||||
const int32x4x2_t b0 =
|
||||
vtrnq_s32(vreinterpretq_s32_s16(*a0), vreinterpretq_s32_s16(*a1));
|
||||
|
||||
// Swap 64 bit elements resulting in:
|
||||
// c0: 00 01 20 21 02 03 22 23
|
||||
// c1: 10 11 30 31 12 13 32 33
|
||||
|
||||
const int32x4_t c0 =
|
||||
vcombine_s32(vget_low_s32(b0.val[0]), vget_low_s32(b0.val[1]));
|
||||
const int32x4_t c1 =
|
||||
vcombine_s32(vget_high_s32(b0.val[0]), vget_high_s32(b0.val[1]));
|
||||
|
||||
// Swap 16 bit elements resulting in:
|
||||
// d0.val[0]: 00 10 20 30 02 12 22 32
|
||||
// d0.val[1]: 01 11 21 31 03 13 23 33
|
||||
|
||||
const int16x8x2_t d0 =
|
||||
vtrnq_s16(vreinterpretq_s16_s32(c0), vreinterpretq_s16_s32(c1));
|
||||
|
||||
*a0 = d0.val[0];
|
||||
*a1 = d0.val[1];
|
||||
}
|
||||
|
||||
static inline int16x8_t dct_const_round_shift_low_8(const int32x4_t *const in) {
|
||||
return vcombine_s16(vrshrn_n_s32(in[0], DCT_CONST_BITS),
|
||||
vrshrn_n_s32(in[1], DCT_CONST_BITS));
|
||||
}
|
||||
|
||||
static inline void dct_const_round_shift_low_8_dual(const int32x4_t *const t32,
|
||||
int16x8_t *const d0,
|
||||
int16x8_t *const d1) {
|
||||
*d0 = dct_const_round_shift_low_8(t32 + 0);
|
||||
*d1 = dct_const_round_shift_low_8(t32 + 2);
|
||||
}
|
||||
|
||||
static const int16_t kCospi[16] = {
|
||||
16384 /* cospi_0_64 */, 15137 /* cospi_8_64 */,
|
||||
11585 /* cospi_16_64 */, 6270 /* cospi_24_64 */,
|
||||
16069 /* cospi_4_64 */, 13623 /* cospi_12_64 */,
|
||||
-9102 /* -cospi_20_64 */, 3196 /* cospi_28_64 */,
|
||||
16305 /* cospi_2_64 */, 1606 /* cospi_30_64 */,
|
||||
14449 /* cospi_10_64 */, 7723 /* cospi_22_64 */,
|
||||
15679 /* cospi_6_64 */, -4756 /* -cospi_26_64 */,
|
||||
12665 /* cospi_14_64 */, -10394 /* -cospi_18_64 */
|
||||
};
|
||||
|
||||
static inline void idct4x4_16_kernel_bd8(int16x8_t *const a) {
|
||||
const int16x4_t cospis = vld1_s16(kCospi);
|
||||
int16x4_t b[4];
|
||||
int32x4_t c[4];
|
||||
int16x8_t d[2];
|
||||
|
||||
b[0] = vget_low_s16(a[0]);
|
||||
b[1] = vget_high_s16(a[0]);
|
||||
b[2] = vget_low_s16(a[1]);
|
||||
b[3] = vget_high_s16(a[1]);
|
||||
c[0] = vmull_lane_s16(b[0], cospis, 2);
|
||||
c[2] = vmull_lane_s16(b[1], cospis, 2);
|
||||
c[1] = vsubq_s32(c[0], c[2]);
|
||||
c[0] = vaddq_s32(c[0], c[2]);
|
||||
c[3] = vmull_lane_s16(b[2], cospis, 3);
|
||||
c[2] = vmull_lane_s16(b[2], cospis, 1);
|
||||
c[3] = vmlsl_lane_s16(c[3], b[3], cospis, 1);
|
||||
c[2] = vmlal_lane_s16(c[2], b[3], cospis, 3);
|
||||
dct_const_round_shift_low_8_dual(c, &d[0], &d[1]);
|
||||
a[0] = vaddq_s16(d[0], d[1]);
|
||||
a[1] = vsubq_s16(d[0], d[1]);
|
||||
}
|
||||
|
||||
static inline void transpose_idct4x4_16_bd8(int16x8_t *const a) {
|
||||
transpose_s16_4x4q(&a[0], &a[1]);
|
||||
idct4x4_16_kernel_bd8(a);
|
||||
}
|
||||
|
||||
inline void vpx_idct4x4_16_add_neon(const int16x8_t &top64, const int16x8_t &bottom64, int16_t *dest, int16_t multiplier) {
|
||||
int16x8_t a[2];
|
||||
|
||||
assert(!((intptr_t)dest % sizeof(uint32_t)));
|
||||
|
||||
int16x8_t mul = vdupq_n_s16(multiplier);
|
||||
|
||||
// Rows
|
||||
a[0] = vmulq_s16(top64, mul);
|
||||
a[1] = vmulq_s16(bottom64, mul);
|
||||
transpose_idct4x4_16_bd8(a);
|
||||
|
||||
// Columns
|
||||
a[1] = vcombine_s16(vget_high_s16(a[1]), vget_low_s16(a[1]));
|
||||
transpose_idct4x4_16_bd8(a);
|
||||
a[0] = vrshrq_n_s16(a[0], 4);
|
||||
a[1] = vrshrq_n_s16(a[1], 4);
|
||||
|
||||
vst1q_s16(dest, a[0]);
|
||||
dest += 2 * 4;
|
||||
vst1_s16(dest, vget_high_s16(a[1]));
|
||||
dest += 4;
|
||||
vst1_s16(dest, vget_low_s16(a[1]));
|
||||
}
|
||||
|
||||
static int dct4x4QuantDC = 60;
|
||||
static int dct4x4QuantAC = 60;
|
||||
|
||||
void performForward4x4Dct(int16_t const *normalizedCoefficients, int16_t *coefficients, int width, int height, DCTELEM *divisors) {
|
||||
DCTELEM block[4 * 4];
|
||||
DCTELEM coefBlock[4 * 4];
|
||||
|
||||
//int acOffset = (width / 4) * (height / 4);
|
||||
|
||||
for (int y = 0; y < height; y += 4) {
|
||||
for (int x = 0; x < width; x += 4) {
|
||||
for (int blockY = 0; blockY < 4; blockY++) {
|
||||
for (int blockX = 0; blockX < 4; blockX++) {
|
||||
block[blockY * 4 + blockX] = normalizedCoefficients[(y + blockY) * width + (x + blockX)];
|
||||
}
|
||||
}
|
||||
|
||||
vpx_fdct4x4_c(block, coefBlock, 4);
|
||||
|
||||
coefBlock[0] /= dct4x4QuantDC;
|
||||
|
||||
for (int blockY = 0; blockY < 4; blockY++) {
|
||||
for (int blockX = 0; blockX < 4; blockX++) {
|
||||
if (blockX == 0 && blockY == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
coefBlock[blockY * 4 + blockX] /= dct4x4QuantAC;
|
||||
}
|
||||
}
|
||||
|
||||
//coefficients[(y / 4) * (width / 4) + x / 4] = coefBlock[0];
|
||||
|
||||
for (int blockY = 0; blockY < 4; blockY++) {
|
||||
for (int blockX = 0; blockX < 4; blockX++) {
|
||||
/*if (blockX == 0 && blockY == 0) {
|
||||
continue;
|
||||
}*/
|
||||
|
||||
coefficients[(y + blockY) * width + (x + blockX)] = coefBlock[zigZag4x4Inv[blockY * 4 + blockX]];
|
||||
//coefficients[acOffset] = coefBlock[zigZag4x4Inv[blockY * 4 + blockX]];
|
||||
//acOffset++;
|
||||
//coefficients[(y + blockY) * width + (x + blockX)] = coefBlock[blockY * 4 + blockX];
|
||||
//int targetIndex = (blockY * 4 + blockX) * (width / 4 * height / 4) + blockIndex;
|
||||
//coefficients[targetIndex] = coefBlock[zigZag4x4Inv[blockY * 4 + blockX]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void performInverse4x4Dct(int16_t const * coefficients, int16_t *normalizedCoefficients, int width, int height, DctAuxiliaryData *auxiliaryData, IFAST_MULT_TYPE *ifmtbl) {
|
||||
//DCTELEM coefficientBlock[4 * 4];
|
||||
DCTELEM resultBlock[4 * 4];
|
||||
|
||||
for (int y = 0; y < height; y += 4) {
|
||||
for (int x = 0; x < width; x += 4) {
|
||||
uint32x2_t sa = vld1_u32((uint32_t *)&coefficients[(y + 0) * width + x]);
|
||||
uint32x2_t sb = vld1_u32((uint32_t *)&coefficients[(y + 1) * width + x]);
|
||||
uint32x2_t sc = vld1_u32((uint32_t *)&coefficients[(y + 2) * width + x]);
|
||||
uint32x2_t sd = vld1_u32((uint32_t *)&coefficients[(y + 3) * width + x]);
|
||||
|
||||
uint8x16_t top = vreinterpretq_u8_u32(vcombine_u32(sa, sb));
|
||||
uint8x16_t bottom = vreinterpretq_u8_u32(vcombine_u32(sc, sd));
|
||||
uint8x16x2_t quad = vzipq_u8(top, bottom);
|
||||
|
||||
uint8_t topReorderIndices[16] = {0, 2, 4, 6, 20, 22, 24, 26, 8, 10, 16, 18, 28, 30, 17, 19};
|
||||
uint8_t bottomReorderIndices[16] = {12, 14, 1, 3, 13, 15, 21, 23, 5, 7, 9, 11, 25, 27, 29, 31};
|
||||
|
||||
uint8x16_t qtop = vqtbl2q_u8(quad, vld1q_u8(topReorderIndices));
|
||||
uint8x16_t qbottom = vqtbl2q_u8(quad, vld1q_u8(bottomReorderIndices));
|
||||
|
||||
uint16x8_t qtop16 = vreinterpretq_s16_u8(qtop);
|
||||
uint16x8_t qbottom16 = vreinterpretq_s16_u8(qbottom);
|
||||
|
||||
int16x8_t top64 = vreinterpretq_s16_u16(qtop16);
|
||||
int16x8_t bottom64 = vreinterpretq_s16_u16(qbottom16);
|
||||
|
||||
/*for (int blockY = 0; blockY < 4; blockY++) {
|
||||
for (int blockX = 0; blockX < 4; blockX++) {
|
||||
coefficientBlock[zigZag4x4Inv[blockY * 4 + blockX]] = coefficients[(y + blockY) * width + (x + blockX)];
|
||||
}
|
||||
}*/
|
||||
|
||||
vpx_idct4x4_16_add_neon(top64, bottom64, resultBlock, dct4x4QuantAC);
|
||||
|
||||
uint32x2_t a = vld1_u32((uint32_t *)&resultBlock[4 * 0]);
|
||||
uint32x2_t b = vld1_u32((uint32_t *)&resultBlock[4 * 1]);
|
||||
uint32x2_t c = vld1_u32((uint32_t *)&resultBlock[4 * 2]);
|
||||
uint32x2_t d = vld1_u32((uint32_t *)&resultBlock[4 * 3]);
|
||||
|
||||
vst1_u32((uint32_t *)&normalizedCoefficients[(y + 0) * width + x], a);
|
||||
vst1_u32((uint32_t *)&normalizedCoefficients[(y + 1) * width + x], b);
|
||||
vst1_u32((uint32_t *)&normalizedCoefficients[(y + 2) * width + x], c);
|
||||
vst1_u32((uint32_t *)&normalizedCoefficients[(y + 3) * width + x], d);
|
||||
|
||||
for (int blockY = 0; blockY < 4; blockY++) {
|
||||
for (int blockX = 0; blockX < 4; blockX++) {
|
||||
//normalizedCoefficients[(y + blockY) * width + (x + blockX)] = resultBlock[blockY * 4 + blockX];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace dct {
|
||||
|
||||
DCTTable DCTTable::generate(int quality, bool isChroma) {
|
||||
DCTTable DCTTable::generate(int quality, DCTTable::Type type) {
|
||||
DCTTable result;
|
||||
result.table.resize(DCTSIZE2);
|
||||
|
||||
if (isChroma) {
|
||||
jpeg_set_quality(result.table.data(), std_chrominance_quant_tbl, quality);
|
||||
} else {
|
||||
switch (type) {
|
||||
case DCTTable::Type::Luma:
|
||||
jpeg_set_quality(result.table.data(), std_luminance_quant_tbl, quality);
|
||||
break;
|
||||
case DCTTable::Type::Chroma:
|
||||
jpeg_set_quality(result.table.data(), std_chrominance_quant_tbl, quality);
|
||||
break;
|
||||
case DCTTable::Type::Delta:
|
||||
jpeg_set_quality(result.table.data(), std_delta_quant_tbl, quality);
|
||||
break;
|
||||
default:
|
||||
jpeg_set_quality(result.table.data(), std_luminance_quant_tbl, quality);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -395,4 +924,12 @@ void DCT::inverse(int16_t const *coefficients, uint8_t *pixels, int width, int h
|
||||
performInverseDct(coefficients, pixels, width, height, coefficientsPerRow, bytesPerRow, _internal->auxiliaryData, (IFAST_MULT_TYPE *)_internal->inverseDctData.data());
|
||||
}
|
||||
|
||||
void DCT::forward4x4(int16_t const *normalizedCoefficients, int16_t *coefficients, int width, int height) {
|
||||
performForward4x4Dct(normalizedCoefficients, coefficients, width, height, (DCTELEM *)_internal->forwardDctData.data());
|
||||
}
|
||||
|
||||
void DCT::inverse4x4(int16_t const *coefficients, int16_t *normalizedCoefficients, int width, int height) {
|
||||
performInverse4x4Dct(coefficients, normalizedCoefficients, width, height, _internal->auxiliaryData, (IFAST_MULT_TYPE *)_internal->inverseDctData.data());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,13 @@ namespace dct {
|
||||
class DCTInternal;
|
||||
|
||||
struct DCTTable {
|
||||
static DCTTable generate(int quality, bool isChroma);
|
||||
enum class Type {
|
||||
Luma,
|
||||
Chroma,
|
||||
Delta
|
||||
};
|
||||
|
||||
static DCTTable generate(int quality, Type type);
|
||||
static DCTTable initializeEmpty();
|
||||
|
||||
std::vector<int16_t> table;
|
||||
@ -24,6 +30,8 @@ public:
|
||||
|
||||
void forward(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow);
|
||||
void inverse(int16_t const *coefficients, uint8_t *pixels, int width, int height, int coefficientsPerRow, int bytesPerRow);
|
||||
void forward4x4(int16_t const *normalizedCoefficients, int16_t *coefficients, int width, int height);
|
||||
void inverse4x4(int16_t const *coefficients, int16_t *normalizedCoefficients, int width, int height);
|
||||
|
||||
private:
|
||||
DCTInternal *_internal;
|
||||
|
@ -18,6 +18,7 @@ struct DctAuxiliaryData *createDctAuxiliaryData();
|
||||
void freeDctAuxiliaryData(struct DctAuxiliaryData *data);
|
||||
|
||||
void dct_jpeg_idct_ifast(struct DctAuxiliaryData *auxiliaryData, void *dct_table, JCOEFPTR coef_block, JSAMPROW output_buf);
|
||||
void dct_jpeg_idct_ifast_normalized(struct DctAuxiliaryData *auxiliaryData, void *dct_table, JCOEFPTR coef_block, JCOEFPTR output_buf);
|
||||
void dct_jpeg_fdct_ifast(DCTELEM *data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -96,6 +96,27 @@ __attribute__((aligned(16))) static const int16_t jsimd_fdct_ifast_neon_consts[]
|
||||
F_0_382, F_0_541, F_0_707, F_0_306
|
||||
};
|
||||
|
||||
#define FIX_0_382683433 ((JLONG)98) /* FIX(0.382683433) */
|
||||
#define FIX_0_541196100 ((JLONG)139) /* FIX(0.541196100) */
|
||||
#define FIX_0_707106781 ((JLONG)181) /* FIX(0.707106781) */
|
||||
#define FIX_1_306562965 ((JLONG)334) /* FIX(1.306562965) */
|
||||
|
||||
#define FIX_1_082392200 ((JLONG)277) /* FIX(1.082392200) */
|
||||
#define FIX_1_414213562 ((JLONG)362) /* FIX(1.414213562) */
|
||||
#define FIX_1_847759065 ((JLONG)473) /* FIX(1.847759065) */
|
||||
#define FIX_2_613125930 ((JLONG)669) /* FIX(2.613125930) */
|
||||
|
||||
#define CONST_BITS 8
|
||||
#define RIGHT_SHIFT(x, shft) ((x) >> (shft))
|
||||
#define IRIGHT_SHIFT(x, shft) ((x) >> (shft))
|
||||
#define DESCALE(x, n) RIGHT_SHIFT(x, n)
|
||||
#define IDESCALE(x, n) ((int)IRIGHT_SHIFT(x, n))
|
||||
#define MULTIPLY(var, const) ((DCTELEM)DESCALE((var) * (const), CONST_BITS))
|
||||
|
||||
#define DEQUANTIZE(coef, quantval) (((IFAST_MULT_TYPE)(coef)) * (quantval))
|
||||
|
||||
#define NO_ZERO_ROW_TEST
|
||||
|
||||
void dct_jpeg_fdct_ifast(DCTELEM *data) {
|
||||
/* Load an 8x8 block of samples into Neon registers. De-interleaving loads
|
||||
* are used, followed by vuzp to transpose the block such that we have a
|
||||
@ -674,4 +695,626 @@ void dct_jpeg_idct_ifast(struct DctAuxiliaryData *auxiliaryData, void *dct_table
|
||||
vst1q_lane_u64((uint64_t *)outptr7, vreinterpretq_u64_u8(rows_37), 1);
|
||||
}
|
||||
|
||||
void dct_jpeg_idct_ifast_normalized_neon(struct DctAuxiliaryData *auxiliaryData, void *dct_table, JCOEFPTR coef_block, JCOEFPTR output_buf)
|
||||
{
|
||||
IFAST_MULT_TYPE *quantptr = dct_table;
|
||||
|
||||
/* Load DCT coefficients. */
|
||||
int16x8_t row0 = vld1q_s16(coef_block + 0 * DCTSIZE);
|
||||
int16x8_t row1 = vld1q_s16(coef_block + 1 * DCTSIZE);
|
||||
int16x8_t row2 = vld1q_s16(coef_block + 2 * DCTSIZE);
|
||||
int16x8_t row3 = vld1q_s16(coef_block + 3 * DCTSIZE);
|
||||
int16x8_t row4 = vld1q_s16(coef_block + 4 * DCTSIZE);
|
||||
int16x8_t row5 = vld1q_s16(coef_block + 5 * DCTSIZE);
|
||||
int16x8_t row6 = vld1q_s16(coef_block + 6 * DCTSIZE);
|
||||
int16x8_t row7 = vld1q_s16(coef_block + 7 * DCTSIZE);
|
||||
|
||||
/* Load quantization table values for DC coefficients. */
|
||||
int16x8_t quant_row0 = vld1q_s16(quantptr + 0 * DCTSIZE);
|
||||
/* Dequantize DC coefficients. */
|
||||
row0 = vmulq_s16(row0, quant_row0);
|
||||
|
||||
/* Construct bitmap to test if all AC coefficients are 0. */
|
||||
int16x8_t bitmap = vorrq_s16(row1, row2);
|
||||
bitmap = vorrq_s16(bitmap, row3);
|
||||
bitmap = vorrq_s16(bitmap, row4);
|
||||
bitmap = vorrq_s16(bitmap, row5);
|
||||
bitmap = vorrq_s16(bitmap, row6);
|
||||
bitmap = vorrq_s16(bitmap, row7);
|
||||
|
||||
int64_t left_ac_bitmap = vgetq_lane_s64(vreinterpretq_s64_s16(bitmap), 0);
|
||||
int64_t right_ac_bitmap = vgetq_lane_s64(vreinterpretq_s64_s16(bitmap), 1);
|
||||
|
||||
/* Load IDCT conversion constants. */
|
||||
const int16x4_t consts = vld1_s16(jsimd_idct_ifast_neon_consts);
|
||||
|
||||
if (left_ac_bitmap == 0 && right_ac_bitmap == 0) {
|
||||
/* All AC coefficients are zero.
|
||||
* Compute DC values and duplicate into vectors.
|
||||
*/
|
||||
int16x8_t dcval = row0;
|
||||
row1 = dcval;
|
||||
row2 = dcval;
|
||||
row3 = dcval;
|
||||
row4 = dcval;
|
||||
row5 = dcval;
|
||||
row6 = dcval;
|
||||
row7 = dcval;
|
||||
} else if (left_ac_bitmap == 0) {
|
||||
/* AC coefficients are zero for columns 0, 1, 2, and 3.
|
||||
* Use DC values for these columns.
|
||||
*/
|
||||
int16x4_t dcval = vget_low_s16(row0);
|
||||
|
||||
/* Commence regular fast IDCT computation for columns 4, 5, 6, and 7. */
|
||||
|
||||
/* Load quantization table. */
|
||||
int16x4_t quant_row1 = vld1_s16(quantptr + 1 * DCTSIZE + 4);
|
||||
int16x4_t quant_row2 = vld1_s16(quantptr + 2 * DCTSIZE + 4);
|
||||
int16x4_t quant_row3 = vld1_s16(quantptr + 3 * DCTSIZE + 4);
|
||||
int16x4_t quant_row4 = vld1_s16(quantptr + 4 * DCTSIZE + 4);
|
||||
int16x4_t quant_row5 = vld1_s16(quantptr + 5 * DCTSIZE + 4);
|
||||
int16x4_t quant_row6 = vld1_s16(quantptr + 6 * DCTSIZE + 4);
|
||||
int16x4_t quant_row7 = vld1_s16(quantptr + 7 * DCTSIZE + 4);
|
||||
|
||||
/* Even part: dequantize DCT coefficients. */
|
||||
int16x4_t tmp0 = vget_high_s16(row0);
|
||||
int16x4_t tmp1 = vmul_s16(vget_high_s16(row2), quant_row2);
|
||||
int16x4_t tmp2 = vmul_s16(vget_high_s16(row4), quant_row4);
|
||||
int16x4_t tmp3 = vmul_s16(vget_high_s16(row6), quant_row6);
|
||||
|
||||
int16x4_t tmp10 = vadd_s16(tmp0, tmp2); /* phase 3 */
|
||||
int16x4_t tmp11 = vsub_s16(tmp0, tmp2);
|
||||
|
||||
int16x4_t tmp13 = vadd_s16(tmp1, tmp3); /* phases 5-3 */
|
||||
int16x4_t tmp1_sub_tmp3 = vsub_s16(tmp1, tmp3);
|
||||
int16x4_t tmp12 = vqdmulh_lane_s16(tmp1_sub_tmp3, consts, 1);
|
||||
tmp12 = vadd_s16(tmp12, tmp1_sub_tmp3);
|
||||
tmp12 = vsub_s16(tmp12, tmp13);
|
||||
|
||||
tmp0 = vadd_s16(tmp10, tmp13); /* phase 2 */
|
||||
tmp3 = vsub_s16(tmp10, tmp13);
|
||||
tmp1 = vadd_s16(tmp11, tmp12);
|
||||
tmp2 = vsub_s16(tmp11, tmp12);
|
||||
|
||||
/* Odd part: dequantize DCT coefficients. */
|
||||
int16x4_t tmp4 = vmul_s16(vget_high_s16(row1), quant_row1);
|
||||
int16x4_t tmp5 = vmul_s16(vget_high_s16(row3), quant_row3);
|
||||
int16x4_t tmp6 = vmul_s16(vget_high_s16(row5), quant_row5);
|
||||
int16x4_t tmp7 = vmul_s16(vget_high_s16(row7), quant_row7);
|
||||
|
||||
int16x4_t z13 = vadd_s16(tmp6, tmp5); /* phase 6 */
|
||||
int16x4_t neg_z10 = vsub_s16(tmp5, tmp6);
|
||||
int16x4_t z11 = vadd_s16(tmp4, tmp7);
|
||||
int16x4_t z12 = vsub_s16(tmp4, tmp7);
|
||||
|
||||
tmp7 = vadd_s16(z11, z13); /* phase 5 */
|
||||
int16x4_t z11_sub_z13 = vsub_s16(z11, z13);
|
||||
tmp11 = vqdmulh_lane_s16(z11_sub_z13, consts, 1);
|
||||
tmp11 = vadd_s16(tmp11, z11_sub_z13);
|
||||
|
||||
int16x4_t z10_add_z12 = vsub_s16(z12, neg_z10);
|
||||
int16x4_t z5 = vqdmulh_lane_s16(z10_add_z12, consts, 2);
|
||||
z5 = vadd_s16(z5, z10_add_z12);
|
||||
tmp10 = vqdmulh_lane_s16(z12, consts, 0);
|
||||
tmp10 = vadd_s16(tmp10, z12);
|
||||
tmp10 = vsub_s16(tmp10, z5);
|
||||
tmp12 = vqdmulh_lane_s16(neg_z10, consts, 3);
|
||||
tmp12 = vadd_s16(tmp12, vadd_s16(neg_z10, neg_z10));
|
||||
tmp12 = vadd_s16(tmp12, z5);
|
||||
|
||||
tmp6 = vsub_s16(tmp12, tmp7); /* phase 2 */
|
||||
tmp5 = vsub_s16(tmp11, tmp6);
|
||||
tmp4 = vadd_s16(tmp10, tmp5);
|
||||
|
||||
row0 = vcombine_s16(dcval, vadd_s16(tmp0, tmp7));
|
||||
row7 = vcombine_s16(dcval, vsub_s16(tmp0, tmp7));
|
||||
row1 = vcombine_s16(dcval, vadd_s16(tmp1, tmp6));
|
||||
row6 = vcombine_s16(dcval, vsub_s16(tmp1, tmp6));
|
||||
row2 = vcombine_s16(dcval, vadd_s16(tmp2, tmp5));
|
||||
row5 = vcombine_s16(dcval, vsub_s16(tmp2, tmp5));
|
||||
row4 = vcombine_s16(dcval, vadd_s16(tmp3, tmp4));
|
||||
row3 = vcombine_s16(dcval, vsub_s16(tmp3, tmp4));
|
||||
} else if (right_ac_bitmap == 0) {
|
||||
/* AC coefficients are zero for columns 4, 5, 6, and 7.
|
||||
* Use DC values for these columns.
|
||||
*/
|
||||
int16x4_t dcval = vget_high_s16(row0);
|
||||
|
||||
/* Commence regular fast IDCT computation for columns 0, 1, 2, and 3. */
|
||||
|
||||
/* Load quantization table. */
|
||||
int16x4_t quant_row1 = vld1_s16(quantptr + 1 * DCTSIZE);
|
||||
int16x4_t quant_row2 = vld1_s16(quantptr + 2 * DCTSIZE);
|
||||
int16x4_t quant_row3 = vld1_s16(quantptr + 3 * DCTSIZE);
|
||||
int16x4_t quant_row4 = vld1_s16(quantptr + 4 * DCTSIZE);
|
||||
int16x4_t quant_row5 = vld1_s16(quantptr + 5 * DCTSIZE);
|
||||
int16x4_t quant_row6 = vld1_s16(quantptr + 6 * DCTSIZE);
|
||||
int16x4_t quant_row7 = vld1_s16(quantptr + 7 * DCTSIZE);
|
||||
|
||||
/* Even part: dequantize DCT coefficients. */
|
||||
int16x4_t tmp0 = vget_low_s16(row0);
|
||||
int16x4_t tmp1 = vmul_s16(vget_low_s16(row2), quant_row2);
|
||||
int16x4_t tmp2 = vmul_s16(vget_low_s16(row4), quant_row4);
|
||||
int16x4_t tmp3 = vmul_s16(vget_low_s16(row6), quant_row6);
|
||||
|
||||
int16x4_t tmp10 = vadd_s16(tmp0, tmp2); /* phase 3 */
|
||||
int16x4_t tmp11 = vsub_s16(tmp0, tmp2);
|
||||
|
||||
int16x4_t tmp13 = vadd_s16(tmp1, tmp3); /* phases 5-3 */
|
||||
int16x4_t tmp1_sub_tmp3 = vsub_s16(tmp1, tmp3);
|
||||
int16x4_t tmp12 = vqdmulh_lane_s16(tmp1_sub_tmp3, consts, 1);
|
||||
tmp12 = vadd_s16(tmp12, tmp1_sub_tmp3);
|
||||
tmp12 = vsub_s16(tmp12, tmp13);
|
||||
|
||||
tmp0 = vadd_s16(tmp10, tmp13); /* phase 2 */
|
||||
tmp3 = vsub_s16(tmp10, tmp13);
|
||||
tmp1 = vadd_s16(tmp11, tmp12);
|
||||
tmp2 = vsub_s16(tmp11, tmp12);
|
||||
|
||||
/* Odd part: dequantize DCT coefficients. */
|
||||
int16x4_t tmp4 = vmul_s16(vget_low_s16(row1), quant_row1);
|
||||
int16x4_t tmp5 = vmul_s16(vget_low_s16(row3), quant_row3);
|
||||
int16x4_t tmp6 = vmul_s16(vget_low_s16(row5), quant_row5);
|
||||
int16x4_t tmp7 = vmul_s16(vget_low_s16(row7), quant_row7);
|
||||
|
||||
int16x4_t z13 = vadd_s16(tmp6, tmp5); /* phase 6 */
|
||||
int16x4_t neg_z10 = vsub_s16(tmp5, tmp6);
|
||||
int16x4_t z11 = vadd_s16(tmp4, tmp7);
|
||||
int16x4_t z12 = vsub_s16(tmp4, tmp7);
|
||||
|
||||
tmp7 = vadd_s16(z11, z13); /* phase 5 */
|
||||
int16x4_t z11_sub_z13 = vsub_s16(z11, z13);
|
||||
tmp11 = vqdmulh_lane_s16(z11_sub_z13, consts, 1);
|
||||
tmp11 = vadd_s16(tmp11, z11_sub_z13);
|
||||
|
||||
int16x4_t z10_add_z12 = vsub_s16(z12, neg_z10);
|
||||
int16x4_t z5 = vqdmulh_lane_s16(z10_add_z12, consts, 2);
|
||||
z5 = vadd_s16(z5, z10_add_z12);
|
||||
tmp10 = vqdmulh_lane_s16(z12, consts, 0);
|
||||
tmp10 = vadd_s16(tmp10, z12);
|
||||
tmp10 = vsub_s16(tmp10, z5);
|
||||
tmp12 = vqdmulh_lane_s16(neg_z10, consts, 3);
|
||||
tmp12 = vadd_s16(tmp12, vadd_s16(neg_z10, neg_z10));
|
||||
tmp12 = vadd_s16(tmp12, z5);
|
||||
|
||||
tmp6 = vsub_s16(tmp12, tmp7); /* phase 2 */
|
||||
tmp5 = vsub_s16(tmp11, tmp6);
|
||||
tmp4 = vadd_s16(tmp10, tmp5);
|
||||
|
||||
row0 = vcombine_s16(vadd_s16(tmp0, tmp7), dcval);
|
||||
row7 = vcombine_s16(vsub_s16(tmp0, tmp7), dcval);
|
||||
row1 = vcombine_s16(vadd_s16(tmp1, tmp6), dcval);
|
||||
row6 = vcombine_s16(vsub_s16(tmp1, tmp6), dcval);
|
||||
row2 = vcombine_s16(vadd_s16(tmp2, tmp5), dcval);
|
||||
row5 = vcombine_s16(vsub_s16(tmp2, tmp5), dcval);
|
||||
row4 = vcombine_s16(vadd_s16(tmp3, tmp4), dcval);
|
||||
row3 = vcombine_s16(vsub_s16(tmp3, tmp4), dcval);
|
||||
} else {
|
||||
/* Some AC coefficients are non-zero; full IDCT calculation required. */
|
||||
|
||||
/* Load quantization table. */
|
||||
int16x8_t quant_row1 = vld1q_s16(quantptr + 1 * DCTSIZE);
|
||||
int16x8_t quant_row2 = vld1q_s16(quantptr + 2 * DCTSIZE);
|
||||
int16x8_t quant_row3 = vld1q_s16(quantptr + 3 * DCTSIZE);
|
||||
int16x8_t quant_row4 = vld1q_s16(quantptr + 4 * DCTSIZE);
|
||||
int16x8_t quant_row5 = vld1q_s16(quantptr + 5 * DCTSIZE);
|
||||
int16x8_t quant_row6 = vld1q_s16(quantptr + 6 * DCTSIZE);
|
||||
int16x8_t quant_row7 = vld1q_s16(quantptr + 7 * DCTSIZE);
|
||||
|
||||
/* Even part: dequantize DCT coefficients. */
|
||||
int16x8_t tmp0 = row0;
|
||||
int16x8_t tmp1 = vmulq_s16(row2, quant_row2);
|
||||
int16x8_t tmp2 = vmulq_s16(row4, quant_row4);
|
||||
int16x8_t tmp3 = vmulq_s16(row6, quant_row6);
|
||||
|
||||
int16x8_t tmp10 = vaddq_s16(tmp0, tmp2); /* phase 3 */
|
||||
int16x8_t tmp11 = vsubq_s16(tmp0, tmp2);
|
||||
|
||||
int16x8_t tmp13 = vaddq_s16(tmp1, tmp3); /* phases 5-3 */
|
||||
int16x8_t tmp1_sub_tmp3 = vsubq_s16(tmp1, tmp3);
|
||||
int16x8_t tmp12 = vqdmulhq_lane_s16(tmp1_sub_tmp3, consts, 1);
|
||||
tmp12 = vaddq_s16(tmp12, tmp1_sub_tmp3);
|
||||
tmp12 = vsubq_s16(tmp12, tmp13);
|
||||
|
||||
tmp0 = vaddq_s16(tmp10, tmp13); /* phase 2 */
|
||||
tmp3 = vsubq_s16(tmp10, tmp13);
|
||||
tmp1 = vaddq_s16(tmp11, tmp12);
|
||||
tmp2 = vsubq_s16(tmp11, tmp12);
|
||||
|
||||
/* Odd part: dequantize DCT coefficients. */
|
||||
int16x8_t tmp4 = vmulq_s16(row1, quant_row1);
|
||||
int16x8_t tmp5 = vmulq_s16(row3, quant_row3);
|
||||
int16x8_t tmp6 = vmulq_s16(row5, quant_row5);
|
||||
int16x8_t tmp7 = vmulq_s16(row7, quant_row7);
|
||||
|
||||
int16x8_t z13 = vaddq_s16(tmp6, tmp5); /* phase 6 */
|
||||
int16x8_t neg_z10 = vsubq_s16(tmp5, tmp6);
|
||||
int16x8_t z11 = vaddq_s16(tmp4, tmp7);
|
||||
int16x8_t z12 = vsubq_s16(tmp4, tmp7);
|
||||
|
||||
tmp7 = vaddq_s16(z11, z13); /* phase 5 */
|
||||
int16x8_t z11_sub_z13 = vsubq_s16(z11, z13);
|
||||
tmp11 = vqdmulhq_lane_s16(z11_sub_z13, consts, 1);
|
||||
tmp11 = vaddq_s16(tmp11, z11_sub_z13);
|
||||
|
||||
int16x8_t z10_add_z12 = vsubq_s16(z12, neg_z10);
|
||||
int16x8_t z5 = vqdmulhq_lane_s16(z10_add_z12, consts, 2);
|
||||
z5 = vaddq_s16(z5, z10_add_z12);
|
||||
tmp10 = vqdmulhq_lane_s16(z12, consts, 0);
|
||||
tmp10 = vaddq_s16(tmp10, z12);
|
||||
tmp10 = vsubq_s16(tmp10, z5);
|
||||
tmp12 = vqdmulhq_lane_s16(neg_z10, consts, 3);
|
||||
tmp12 = vaddq_s16(tmp12, vaddq_s16(neg_z10, neg_z10));
|
||||
tmp12 = vaddq_s16(tmp12, z5);
|
||||
|
||||
tmp6 = vsubq_s16(tmp12, tmp7); /* phase 2 */
|
||||
tmp5 = vsubq_s16(tmp11, tmp6);
|
||||
tmp4 = vaddq_s16(tmp10, tmp5);
|
||||
|
||||
row0 = vaddq_s16(tmp0, tmp7);
|
||||
row7 = vsubq_s16(tmp0, tmp7);
|
||||
row1 = vaddq_s16(tmp1, tmp6);
|
||||
row6 = vsubq_s16(tmp1, tmp6);
|
||||
row2 = vaddq_s16(tmp2, tmp5);
|
||||
row5 = vsubq_s16(tmp2, tmp5);
|
||||
row4 = vaddq_s16(tmp3, tmp4);
|
||||
row3 = vsubq_s16(tmp3, tmp4);
|
||||
}
|
||||
|
||||
/* Transpose rows to work on columns in pass 2. */
|
||||
int16x8x2_t rows_01 = vtrnq_s16(row0, row1);
|
||||
int16x8x2_t rows_23 = vtrnq_s16(row2, row3);
|
||||
int16x8x2_t rows_45 = vtrnq_s16(row4, row5);
|
||||
int16x8x2_t rows_67 = vtrnq_s16(row6, row7);
|
||||
|
||||
int32x4x2_t rows_0145_l = vtrnq_s32(vreinterpretq_s32_s16(rows_01.val[0]),
|
||||
vreinterpretq_s32_s16(rows_45.val[0]));
|
||||
int32x4x2_t rows_0145_h = vtrnq_s32(vreinterpretq_s32_s16(rows_01.val[1]),
|
||||
vreinterpretq_s32_s16(rows_45.val[1]));
|
||||
int32x4x2_t rows_2367_l = vtrnq_s32(vreinterpretq_s32_s16(rows_23.val[0]),
|
||||
vreinterpretq_s32_s16(rows_67.val[0]));
|
||||
int32x4x2_t rows_2367_h = vtrnq_s32(vreinterpretq_s32_s16(rows_23.val[1]),
|
||||
vreinterpretq_s32_s16(rows_67.val[1]));
|
||||
|
||||
int32x4x2_t cols_04 = vzipq_s32(rows_0145_l.val[0], rows_2367_l.val[0]);
|
||||
int32x4x2_t cols_15 = vzipq_s32(rows_0145_h.val[0], rows_2367_h.val[0]);
|
||||
int32x4x2_t cols_26 = vzipq_s32(rows_0145_l.val[1], rows_2367_l.val[1]);
|
||||
int32x4x2_t cols_37 = vzipq_s32(rows_0145_h.val[1], rows_2367_h.val[1]);
|
||||
|
||||
int16x8_t col0 = vreinterpretq_s16_s32(cols_04.val[0]);
|
||||
int16x8_t col1 = vreinterpretq_s16_s32(cols_15.val[0]);
|
||||
int16x8_t col2 = vreinterpretq_s16_s32(cols_26.val[0]);
|
||||
int16x8_t col3 = vreinterpretq_s16_s32(cols_37.val[0]);
|
||||
int16x8_t col4 = vreinterpretq_s16_s32(cols_04.val[1]);
|
||||
int16x8_t col5 = vreinterpretq_s16_s32(cols_15.val[1]);
|
||||
int16x8_t col6 = vreinterpretq_s16_s32(cols_26.val[1]);
|
||||
int16x8_t col7 = vreinterpretq_s16_s32(cols_37.val[1]);
|
||||
|
||||
/* 1-D IDCT, pass 2 */
|
||||
|
||||
/* Even part */
|
||||
int16x8_t tmp10 = vaddq_s16(col0, col4);
|
||||
int16x8_t tmp11 = vsubq_s16(col0, col4);
|
||||
|
||||
int16x8_t tmp13 = vaddq_s16(col2, col6);
|
||||
int16x8_t col2_sub_col6 = vsubq_s16(col2, col6);
|
||||
int16x8_t tmp12 = vqdmulhq_lane_s16(col2_sub_col6, consts, 1);
|
||||
tmp12 = vaddq_s16(tmp12, col2_sub_col6);
|
||||
tmp12 = vsubq_s16(tmp12, tmp13);
|
||||
|
||||
int16x8_t tmp0 = vaddq_s16(tmp10, tmp13);
|
||||
int16x8_t tmp3 = vsubq_s16(tmp10, tmp13);
|
||||
int16x8_t tmp1 = vaddq_s16(tmp11, tmp12);
|
||||
int16x8_t tmp2 = vsubq_s16(tmp11, tmp12);
|
||||
|
||||
/* Odd part */
|
||||
int16x8_t z13 = vaddq_s16(col5, col3);
|
||||
int16x8_t neg_z10 = vsubq_s16(col3, col5);
|
||||
int16x8_t z11 = vaddq_s16(col1, col7);
|
||||
int16x8_t z12 = vsubq_s16(col1, col7);
|
||||
|
||||
int16x8_t tmp7 = vaddq_s16(z11, z13); /* phase 5 */
|
||||
int16x8_t z11_sub_z13 = vsubq_s16(z11, z13);
|
||||
tmp11 = vqdmulhq_lane_s16(z11_sub_z13, consts, 1);
|
||||
tmp11 = vaddq_s16(tmp11, z11_sub_z13);
|
||||
|
||||
int16x8_t z10_add_z12 = vsubq_s16(z12, neg_z10);
|
||||
int16x8_t z5 = vqdmulhq_lane_s16(z10_add_z12, consts, 2);
|
||||
z5 = vaddq_s16(z5, z10_add_z12);
|
||||
tmp10 = vqdmulhq_lane_s16(z12, consts, 0);
|
||||
tmp10 = vaddq_s16(tmp10, z12);
|
||||
tmp10 = vsubq_s16(tmp10, z5);
|
||||
tmp12 = vqdmulhq_lane_s16(neg_z10, consts, 3);
|
||||
tmp12 = vaddq_s16(tmp12, vaddq_s16(neg_z10, neg_z10));
|
||||
tmp12 = vaddq_s16(tmp12, z5);
|
||||
|
||||
int16x8_t tmp6 = vsubq_s16(tmp12, tmp7); /* phase 2 */
|
||||
int16x8_t tmp5 = vsubq_s16(tmp11, tmp6);
|
||||
int16x8_t tmp4 = vaddq_s16(tmp10, tmp5);
|
||||
|
||||
col0 = vaddq_s16(tmp0, tmp7);
|
||||
col7 = vsubq_s16(tmp0, tmp7);
|
||||
col1 = vaddq_s16(tmp1, tmp6);
|
||||
col6 = vsubq_s16(tmp1, tmp6);
|
||||
col2 = vaddq_s16(tmp2, tmp5);
|
||||
col5 = vsubq_s16(tmp2, tmp5);
|
||||
col4 = vaddq_s16(tmp3, tmp4);
|
||||
col3 = vsubq_s16(tmp3, tmp4);
|
||||
|
||||
/* Scale down by a factor of 8, narrowing to 8-bit. */
|
||||
int8x16_t cols_01_s8 = vcombine_s8(vqshrn_n_s16(col0, PASS1_BITS + 3),
|
||||
vqshrn_n_s16(col1, PASS1_BITS + 3));
|
||||
int8x16_t cols_45_s8 = vcombine_s8(vqshrn_n_s16(col4, PASS1_BITS + 3),
|
||||
vqshrn_n_s16(col5, PASS1_BITS + 3));
|
||||
int8x16_t cols_23_s8 = vcombine_s8(vqshrn_n_s16(col2, PASS1_BITS + 3),
|
||||
vqshrn_n_s16(col3, PASS1_BITS + 3));
|
||||
int8x16_t cols_67_s8 = vcombine_s8(vqshrn_n_s16(col6, PASS1_BITS + 3),
|
||||
vqshrn_n_s16(col7, PASS1_BITS + 3));
|
||||
/* Clamp to range [0-255]. */
|
||||
uint8x16_t cols_01 =
|
||||
vreinterpretq_u8_s8
|
||||
(vaddq_s8(cols_01_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE))));
|
||||
uint8x16_t cols_45 =
|
||||
vreinterpretq_u8_s8
|
||||
(vaddq_s8(cols_45_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE))));
|
||||
uint8x16_t cols_23 =
|
||||
vreinterpretq_u8_s8
|
||||
(vaddq_s8(cols_23_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE))));
|
||||
uint8x16_t cols_67 =
|
||||
vreinterpretq_u8_s8
|
||||
(vaddq_s8(cols_67_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE))));
|
||||
|
||||
/* Transpose block to prepare for store. */
|
||||
uint32x4x2_t cols_0415 = vzipq_u32(vreinterpretq_u32_u8(cols_01),
|
||||
vreinterpretq_u32_u8(cols_45));
|
||||
uint32x4x2_t cols_2637 = vzipq_u32(vreinterpretq_u32_u8(cols_23),
|
||||
vreinterpretq_u32_u8(cols_67));
|
||||
|
||||
uint8x16x2_t cols_0145 = vtrnq_u8(vreinterpretq_u8_u32(cols_0415.val[0]),
|
||||
vreinterpretq_u8_u32(cols_0415.val[1]));
|
||||
uint8x16x2_t cols_2367 = vtrnq_u8(vreinterpretq_u8_u32(cols_2637.val[0]),
|
||||
vreinterpretq_u8_u32(cols_2637.val[1]));
|
||||
uint16x8x2_t rows_0426 = vtrnq_u16(vreinterpretq_u16_u8(cols_0145.val[0]),
|
||||
vreinterpretq_u16_u8(cols_2367.val[0]));
|
||||
uint16x8x2_t rows_1537 = vtrnq_u16(vreinterpretq_u16_u8(cols_0145.val[1]),
|
||||
vreinterpretq_u16_u8(cols_2367.val[1]));
|
||||
|
||||
uint8x16_t rows_04 = vreinterpretq_u8_u16(rows_0426.val[0]);
|
||||
uint8x16_t rows_15 = vreinterpretq_u8_u16(rows_1537.val[0]);
|
||||
uint8x16_t rows_26 = vreinterpretq_u8_u16(rows_0426.val[1]);
|
||||
uint8x16_t rows_37 = vreinterpretq_u8_u16(rows_1537.val[1]);
|
||||
|
||||
JCOEFPTR outptr0 = output_buf + DCTSIZE * 0;
|
||||
JCOEFPTR outptr1 = output_buf + DCTSIZE * 1;
|
||||
JCOEFPTR outptr2 = output_buf + DCTSIZE * 2;
|
||||
JCOEFPTR outptr3 = output_buf + DCTSIZE * 3;
|
||||
JCOEFPTR outptr4 = output_buf + DCTSIZE * 4;
|
||||
JCOEFPTR outptr5 = output_buf + DCTSIZE * 5;
|
||||
JCOEFPTR outptr6 = output_buf + DCTSIZE * 6;
|
||||
JCOEFPTR outptr7 = output_buf + DCTSIZE * 7;
|
||||
|
||||
/* Store DCT block to memory. */
|
||||
vst1q_lane_u64((uint64_t *)outptr0, vreinterpretq_u64_u16(rows_04), 0);
|
||||
vst1q_lane_u64((uint64_t *)outptr1, vreinterpretq_u64_u16(rows_15), 0);
|
||||
vst1q_lane_u64((uint64_t *)outptr2, vreinterpretq_u64_u16(rows_26), 0);
|
||||
vst1q_lane_u64((uint64_t *)outptr3, vreinterpretq_u64_u16(rows_37), 0);
|
||||
vst1q_lane_u64((uint64_t *)outptr4, vreinterpretq_u64_u16(rows_04), 1);
|
||||
vst1q_lane_u64((uint64_t *)outptr5, vreinterpretq_u64_u16(rows_15), 1);
|
||||
vst1q_lane_u64((uint64_t *)outptr6, vreinterpretq_u64_u16(rows_26), 1);
|
||||
vst1q_lane_u64((uint64_t *)outptr7, vreinterpretq_u64_u16(rows_37), 1);
|
||||
}
|
||||
|
||||
void dct_jpeg_idct_ifast_normalized(struct DctAuxiliaryData *auxiliaryData, void *dct_table, JCOEFPTR coef_block, JCOEFPTR output_buf) {
|
||||
DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
|
||||
DCTELEM tmp10, tmp11, tmp12, tmp13;
|
||||
DCTELEM z5, z10, z11, z12, z13;
|
||||
JCOEFPTR inptr;
|
||||
IFAST_MULT_TYPE *quantptr;
|
||||
int *wsptr;
|
||||
JCOEFPTR outptr;
|
||||
int ctr;
|
||||
int workspace[DCTSIZE2]; /* buffers data between passes */
|
||||
|
||||
/* Pass 1: process columns from input, store into work array. */
|
||||
|
||||
inptr = coef_block;
|
||||
quantptr = dct_table;
|
||||
wsptr = workspace;
|
||||
for (ctr = DCTSIZE; ctr > 0; ctr--) {
|
||||
/* Due to quantization, we will usually find that many of the input
|
||||
* coefficients are zero, especially the AC terms. We can exploit this
|
||||
* by short-circuiting the IDCT calculation for any column in which all
|
||||
* the AC terms are zero. In that case each output is equal to the
|
||||
* DC coefficient (with scale factor as needed).
|
||||
* With typical images and quantization tables, half or more of the
|
||||
* column DCT calculations can be simplified this way.
|
||||
*/
|
||||
|
||||
if (inptr[DCTSIZE * 1] == 0 && inptr[DCTSIZE * 2] == 0 &&
|
||||
inptr[DCTSIZE * 3] == 0 && inptr[DCTSIZE * 4] == 0 &&
|
||||
inptr[DCTSIZE * 5] == 0 && inptr[DCTSIZE * 6] == 0 &&
|
||||
inptr[DCTSIZE * 7] == 0) {
|
||||
/* AC terms all zero */
|
||||
int dcval = (int)DEQUANTIZE(inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0]);
|
||||
|
||||
wsptr[DCTSIZE * 0] = dcval;
|
||||
wsptr[DCTSIZE * 1] = dcval;
|
||||
wsptr[DCTSIZE * 2] = dcval;
|
||||
wsptr[DCTSIZE * 3] = dcval;
|
||||
wsptr[DCTSIZE * 4] = dcval;
|
||||
wsptr[DCTSIZE * 5] = dcval;
|
||||
wsptr[DCTSIZE * 6] = dcval;
|
||||
wsptr[DCTSIZE * 7] = dcval;
|
||||
|
||||
inptr++; /* advance pointers to next column */
|
||||
quantptr++;
|
||||
wsptr++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Even part */
|
||||
|
||||
tmp0 = DEQUANTIZE(inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0]);
|
||||
tmp1 = DEQUANTIZE(inptr[DCTSIZE * 2], quantptr[DCTSIZE * 2]);
|
||||
tmp2 = DEQUANTIZE(inptr[DCTSIZE * 4], quantptr[DCTSIZE * 4]);
|
||||
tmp3 = DEQUANTIZE(inptr[DCTSIZE * 6], quantptr[DCTSIZE * 6]);
|
||||
|
||||
tmp10 = tmp0 + tmp2; /* phase 3 */
|
||||
tmp11 = tmp0 - tmp2;
|
||||
|
||||
tmp13 = tmp1 + tmp3; /* phases 5-3 */
|
||||
tmp12 = MULTIPLY(tmp1 - tmp3, FIX_1_414213562) - tmp13; /* 2*c4 */
|
||||
|
||||
tmp0 = tmp10 + tmp13; /* phase 2 */
|
||||
tmp3 = tmp10 - tmp13;
|
||||
tmp1 = tmp11 + tmp12;
|
||||
tmp2 = tmp11 - tmp12;
|
||||
|
||||
/* Odd part */
|
||||
|
||||
tmp4 = DEQUANTIZE(inptr[DCTSIZE * 1], quantptr[DCTSIZE * 1]);
|
||||
tmp5 = DEQUANTIZE(inptr[DCTSIZE * 3], quantptr[DCTSIZE * 3]);
|
||||
tmp6 = DEQUANTIZE(inptr[DCTSIZE * 5], quantptr[DCTSIZE * 5]);
|
||||
tmp7 = DEQUANTIZE(inptr[DCTSIZE * 7], quantptr[DCTSIZE * 7]);
|
||||
|
||||
z13 = tmp6 + tmp5; /* phase 6 */
|
||||
z10 = tmp6 - tmp5;
|
||||
z11 = tmp4 + tmp7;
|
||||
z12 = tmp4 - tmp7;
|
||||
|
||||
tmp7 = z11 + z13; /* phase 5 */
|
||||
tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */
|
||||
|
||||
z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */
|
||||
tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */
|
||||
tmp12 = MULTIPLY(z10, -FIX_2_613125930) + z5; /* -2*(c2+c6) */
|
||||
|
||||
tmp6 = tmp12 - tmp7; /* phase 2 */
|
||||
tmp5 = tmp11 - tmp6;
|
||||
tmp4 = tmp10 + tmp5;
|
||||
|
||||
wsptr[DCTSIZE * 0] = (int)(tmp0 + tmp7);
|
||||
wsptr[DCTSIZE * 7] = (int)(tmp0 - tmp7);
|
||||
wsptr[DCTSIZE * 1] = (int)(tmp1 + tmp6);
|
||||
wsptr[DCTSIZE * 6] = (int)(tmp1 - tmp6);
|
||||
wsptr[DCTSIZE * 2] = (int)(tmp2 + tmp5);
|
||||
wsptr[DCTSIZE * 5] = (int)(tmp2 - tmp5);
|
||||
wsptr[DCTSIZE * 4] = (int)(tmp3 + tmp4);
|
||||
wsptr[DCTSIZE * 3] = (int)(tmp3 - tmp4);
|
||||
|
||||
inptr++; /* advance pointers to next column */
|
||||
quantptr++;
|
||||
wsptr++;
|
||||
}
|
||||
|
||||
/* Pass 2: process rows from work array, store into output array. */
|
||||
/* Note that we must descale the results by a factor of 8 == 2**3, */
|
||||
/* and also undo the PASS1_BITS scaling. */
|
||||
|
||||
wsptr = workspace;
|
||||
for (ctr = 0; ctr < DCTSIZE; ctr++) {
|
||||
outptr = output_buf + ctr * DCTSIZE;
|
||||
/* Rows of zeroes can be exploited in the same way as we did with columns.
|
||||
* However, the column calculation has created many nonzero AC terms, so
|
||||
* the simplification applies less often (typically 5% to 10% of the time).
|
||||
* On machines with very fast multiplication, it's possible that the
|
||||
* test takes more time than it's worth. In that case this section
|
||||
* may be commented out.
|
||||
*/
|
||||
|
||||
#ifndef NO_ZERO_ROW_TEST
|
||||
if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 &&
|
||||
wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) {
|
||||
/* AC terms all zero */
|
||||
//JSAMPLE dcval = range_limit[IDESCALE(wsptr[0], PASS1_BITS + 3) & RANGE_MASK];
|
||||
JCOEF dcval = wsptr[0];
|
||||
|
||||
outptr[0] = dcval;
|
||||
outptr[1] = dcval;
|
||||
outptr[2] = dcval;
|
||||
outptr[3] = dcval;
|
||||
outptr[4] = dcval;
|
||||
outptr[5] = dcval;
|
||||
outptr[6] = dcval;
|
||||
outptr[7] = dcval;
|
||||
|
||||
wsptr += DCTSIZE; /* advance pointer to next row */
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Even part */
|
||||
|
||||
tmp10 = ((DCTELEM)wsptr[0] + (DCTELEM)wsptr[4]);
|
||||
tmp11 = ((DCTELEM)wsptr[0] - (DCTELEM)wsptr[4]);
|
||||
|
||||
tmp13 = ((DCTELEM)wsptr[2] + (DCTELEM)wsptr[6]);
|
||||
tmp12 =
|
||||
MULTIPLY((DCTELEM)wsptr[2] - (DCTELEM)wsptr[6], FIX_1_414213562) - tmp13;
|
||||
|
||||
tmp0 = tmp10 + tmp13;
|
||||
tmp3 = tmp10 - tmp13;
|
||||
tmp1 = tmp11 + tmp12;
|
||||
tmp2 = tmp11 - tmp12;
|
||||
|
||||
/* Odd part */
|
||||
|
||||
z13 = (DCTELEM)wsptr[5] + (DCTELEM)wsptr[3];
|
||||
z10 = (DCTELEM)wsptr[5] - (DCTELEM)wsptr[3];
|
||||
z11 = (DCTELEM)wsptr[1] + (DCTELEM)wsptr[7];
|
||||
z12 = (DCTELEM)wsptr[1] - (DCTELEM)wsptr[7];
|
||||
|
||||
tmp7 = z11 + z13; /* phase 5 */
|
||||
tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */
|
||||
|
||||
z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */
|
||||
tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */
|
||||
tmp12 = MULTIPLY(z10, -FIX_2_613125930) + z5; /* -2*(c2+c6) */
|
||||
|
||||
tmp6 = tmp12 - tmp7; /* phase 2 */
|
||||
tmp5 = tmp11 - tmp6;
|
||||
tmp4 = tmp10 + tmp5;
|
||||
|
||||
/* Final output stage: scale down by a factor of 8 and range-limit */
|
||||
|
||||
/*outptr[0] =
|
||||
range_limit[IDESCALE(tmp0 + tmp7, PASS1_BITS + 3) & RANGE_MASK];
|
||||
outptr[7] =
|
||||
range_limit[IDESCALE(tmp0 - tmp7, PASS1_BITS + 3) & RANGE_MASK];
|
||||
outptr[1] =
|
||||
range_limit[IDESCALE(tmp1 + tmp6, PASS1_BITS + 3) & RANGE_MASK];
|
||||
outptr[6] =
|
||||
range_limit[IDESCALE(tmp1 - tmp6, PASS1_BITS + 3) & RANGE_MASK];
|
||||
outptr[2] =
|
||||
range_limit[IDESCALE(tmp2 + tmp5, PASS1_BITS + 3) & RANGE_MASK];
|
||||
outptr[5] =
|
||||
range_limit[IDESCALE(tmp2 - tmp5, PASS1_BITS + 3) & RANGE_MASK];
|
||||
outptr[4] =
|
||||
range_limit[IDESCALE(tmp3 + tmp4, PASS1_BITS + 3) & RANGE_MASK];
|
||||
outptr[3] =
|
||||
range_limit[IDESCALE(tmp3 - tmp4, PASS1_BITS + 3) & RANGE_MASK];*/
|
||||
|
||||
outptr[0] = IDESCALE(tmp0 + tmp7, PASS1_BITS + 3);
|
||||
outptr[7] = IDESCALE(tmp0 - tmp7, PASS1_BITS + 3);
|
||||
outptr[1] = IDESCALE(tmp1 + tmp6, PASS1_BITS + 3);
|
||||
outptr[6] = IDESCALE(tmp1 - tmp6, PASS1_BITS + 3);
|
||||
outptr[2] = IDESCALE(tmp2 + tmp5, PASS1_BITS + 3);
|
||||
outptr[5] = IDESCALE(tmp2 - tmp5, PASS1_BITS + 3);
|
||||
outptr[4] = IDESCALE(tmp3 + tmp4, PASS1_BITS + 3);
|
||||
outptr[3] = IDESCALE(tmp3 - tmp4, PASS1_BITS + 3);
|
||||
|
||||
/*outptr[0] = tmp0 + tmp7;
|
||||
outptr[7] = tmp0 - tmp7;
|
||||
outptr[1] = tmp1 + tmp6;
|
||||
outptr[6] = tmp1 - tmp6;
|
||||
outptr[2] = tmp2 + tmp5;
|
||||
outptr[5] = tmp2 - tmp5;
|
||||
outptr[4] = tmp3 + tmp4;
|
||||
outptr[3] = tmp3 - tmp4;*/
|
||||
|
||||
wsptr += DCTSIZE; /* advance pointer to next row */
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -13,10 +13,25 @@
|
||||
|
||||
@implementation ImageDCTTable
|
||||
|
||||
- (instancetype _Nonnull)initWithQuality:(NSInteger)quality isChroma:(bool)isChroma {
|
||||
- (instancetype _Nonnull)initWithQuality:(NSInteger)quality type:(ImageDCTTableType)type; {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_table = dct::DCTTable::generate((int)quality, isChroma);
|
||||
dct::DCTTable::Type mappedType;
|
||||
switch (type) {
|
||||
case ImageDCTTableTypeLuma:
|
||||
mappedType = dct::DCTTable::Type::Luma;
|
||||
break;
|
||||
case ImageDCTTableTypeChroma:
|
||||
mappedType = dct::DCTTable::Type::Chroma;
|
||||
break;
|
||||
case ImageDCTTableTypeDelta:
|
||||
mappedType = dct::DCTTable::Type::Delta;
|
||||
break;
|
||||
default:
|
||||
mappedType = dct::DCTTable::Type::Luma;
|
||||
break;
|
||||
}
|
||||
_table = dct::DCTTable::generate((int)quality, mappedType);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -63,4 +78,12 @@
|
||||
_dct->inverse(coefficients, pixels, (int)width, (int)height, (int)coefficientsPerRow, (int)bytesPerRow);
|
||||
}
|
||||
|
||||
- (void)forward4x4:(int16_t const * _Nonnull)normalizedCoefficients coefficients:(int16_t * _Nonnull)coefficients width:(NSInteger)width height:(NSInteger)height {
|
||||
_dct->forward4x4(normalizedCoefficients, coefficients, (int)width, (int)height);
|
||||
}
|
||||
|
||||
- (void)inverse4x4:(int16_t const * _Nonnull)coefficients normalizedCoefficients:(int16_t * _Nonnull)normalizedCoefficients width:(NSInteger)width height:(NSInteger)height {
|
||||
_dct->inverse4x4(coefficients, normalizedCoefficients, (int)width, (int)height);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -53,7 +53,7 @@ void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU,
|
||||
vImageExtractChannel_ARGB8888(&src, &destA, 3, kvImageDoNotTile);
|
||||
}
|
||||
|
||||
void combineYUVAPlanesIntoARBB(uint8_t *argb, uint8_t const *inY, uint8_t const *inU, uint8_t const *inV, uint8_t const *inA, int width, int height, int bytesPerRow) {
|
||||
void combineYUVAPlanesIntoARGB(uint8_t *argb, uint8_t const *inY, uint8_t const *inU, uint8_t const *inV, uint8_t const *inA, int width, int height, int bytesPerRow) {
|
||||
static vImage_YpCbCrToARGB info;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
@ -94,8 +94,11 @@ void combineYUVAPlanesIntoARBB(uint8_t *argb, uint8_t const *inY, uint8_t const
|
||||
srcA.rowBytes = width;
|
||||
|
||||
error = vImageConvert_420Yp8_Cb8_Cr8ToARGB8888(&srcYp, &srcCb, &srcCr, &destArgb, &info, permuteMap, 255, kvImageDoNotTile);
|
||||
|
||||
error = vImageOverwriteChannels_ARGB8888(&srcA, &destArgb, &destArgb, 1 << 0, kvImageDoNotTile);
|
||||
|
||||
//error = vImageOverwriteChannels_ARGB8888(&srcYp, &destArgb, &destArgb, 1 << 1, kvImageDoNotTile);
|
||||
//error = vImageOverwriteChannels_ARGB8888(&srcYp, &destArgb, &destArgb, 1 << 2, kvImageDoNotTile);
|
||||
//error = vImageOverwriteChannels_ARGB8888(&srcYp, &destArgb, &destArgb, 1 << 3, kvImageDoNotTile);
|
||||
}
|
||||
|
||||
void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outBytesPerRow, uint8_t const *inPlane, int inWidth, int inHeight, int inBytesPerRow) {
|
||||
@ -113,3 +116,42 @@ void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outByte
|
||||
|
||||
vImageScale_Planar8(&src, &dst, nil, kvImageDoNotTile);
|
||||
}
|
||||
|
||||
void subtractArraysInt16(int16_t const *a, int16_t const *b, uint16_t *dest, int length) {
|
||||
for (int i = 0; i < length; i += 8) {
|
||||
int16x8_t lhs = vld1q_s16((int16_t *)&a[i]);
|
||||
int16x8_t rhs = vld1q_s16((int16_t *)&b[i]);
|
||||
int16x8_t result = vsubq_s16(lhs, rhs);
|
||||
vst1q_s16((int16_t *)&dest[i], result);
|
||||
}
|
||||
if (length % 8 != 0) {
|
||||
for (int i = length - (length % 8); i < length; i++) {
|
||||
dest[i] = a[i] - b[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void subtractArraysUInt8Int16(uint8_t const *a, int16_t const *b, uint8_t *dest, int length) {
|
||||
for (int i = 0; i < length; i += 8) {
|
||||
uint8x8_t lhs8 = vld1_u8(&a[i]);
|
||||
int16x8_t lhs = vreinterpretq_s16_u16(vmovl_u8(lhs8));
|
||||
|
||||
int16x8_t rhs = vld1q_s16((int16_t *)&b[i]);
|
||||
int16x8_t result = vsubq_s16(lhs, rhs);
|
||||
|
||||
uint8x8_t result8 = vqmovun_s16(result);
|
||||
vst1_u8(&dest[i], result8);
|
||||
}
|
||||
if (length % 8 != 0) {
|
||||
for (int i = length - (length % 8); i < length; i++) {
|
||||
int16_t result = ((int16_t)a[i]) - b[i];
|
||||
if (result < 0) {
|
||||
result = 0;
|
||||
}
|
||||
if (result > 255) {
|
||||
result = 255;
|
||||
}
|
||||
dest[i] = (int8_t)result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import ImageDCT
|
||||
import Accelerate
|
||||
|
||||
private func alignUp(size: Int, align: Int) -> Int {
|
||||
precondition(((align - 1) & align) == 0, "Align must be a power of two")
|
||||
@ -9,7 +10,7 @@ private func alignUp(size: Int, align: Int) -> Int {
|
||||
return (size + alignmentMask) & ~alignmentMask
|
||||
}
|
||||
|
||||
final class ImagePlane {
|
||||
final class ImagePlane: CustomStringConvertible {
|
||||
let width: Int
|
||||
let height: Int
|
||||
let bytesPerRow: Int
|
||||
@ -25,6 +26,28 @@ final class ImagePlane {
|
||||
self.components = components
|
||||
self.data = Data(count: self.bytesPerRow * height)
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return self.data.withUnsafeBytes { bytes -> String in
|
||||
let pixels = bytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
var result = ""
|
||||
|
||||
for y in 0 ..< self.height {
|
||||
if y != 0 {
|
||||
result.append("\n")
|
||||
}
|
||||
for x in 0 ..< self.width {
|
||||
if x != 0 {
|
||||
result.append(" ")
|
||||
}
|
||||
result.append(String(format: "%03d", Int(pixels[y * self.bytesPerRow + x])))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ImagePlane {
|
||||
@ -35,6 +58,14 @@ extension ImagePlane {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func subtract(other: DctCoefficientPlane) {
|
||||
self.data.withUnsafeMutableBytes { bytes in
|
||||
other.data.withUnsafeBytes { otherBytes in
|
||||
subtractArraysUInt8Int16(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), otherBytes.baseAddress!.assumingMemoryBound(to: Int16.self), bytes.baseAddress!.assumingMemoryBound(to: Int8.self), Int32(bytes.count))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ImageARGB {
|
||||
@ -59,7 +90,7 @@ final class ImageYUVA420 {
|
||||
}
|
||||
}
|
||||
|
||||
final class DctCoefficientPlane {
|
||||
final class DctCoefficientPlane: CustomStringConvertible {
|
||||
let width: Int
|
||||
let height: Int
|
||||
var data: Data
|
||||
@ -69,6 +100,46 @@ final class DctCoefficientPlane {
|
||||
self.height = height
|
||||
self.data = Data(count: width * 2 * height)
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return self.data.withUnsafeBytes { bytes -> String in
|
||||
let pixels = bytes.baseAddress!.assumingMemoryBound(to: Int16.self)
|
||||
|
||||
var result = ""
|
||||
|
||||
for y in 0 ..< self.height {
|
||||
if y != 0 {
|
||||
result.append("\n")
|
||||
}
|
||||
for x in 0 ..< self.width {
|
||||
if x != 0 {
|
||||
result.append(" ")
|
||||
}
|
||||
result.append(String(format: "%03d", Int(pixels[y * self.width + x])))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
func subtract(other: DctCoefficientPlane) {
|
||||
self.data.withUnsafeMutableBytes { bytes in
|
||||
other.data.withUnsafeBytes { otherBytes in
|
||||
subtractArraysInt16(otherBytes.baseAddress!.assumingMemoryBound(to: Int16.self), bytes.baseAddress!.assumingMemoryBound(to: Int16.self), bytes.baseAddress!.assumingMemoryBound(to: Int16.self), Int32(bytes.count / 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DctCoefficientPlane {
|
||||
func toFloatCoefficients(target: FloatCoefficientPlane) {
|
||||
self.data.withUnsafeBytes { bytes in
|
||||
target.data.withUnsafeMutableBytes { otherBytes in
|
||||
vDSP_vflt16(bytes.baseAddress!.assumingMemoryBound(to: Int16.self), 1, otherBytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, vDSP_Length(bytes.count / 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class DctCoefficientsYUVA420 {
|
||||
@ -85,6 +156,174 @@ final class DctCoefficientsYUVA420 {
|
||||
}
|
||||
}
|
||||
|
||||
final class FloatCoefficientPlane: CustomStringConvertible {
|
||||
let width: Int
|
||||
let height: Int
|
||||
var data: Data
|
||||
|
||||
init(width: Int, height: Int) {
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.data = Data(count: width * 4 * height)
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return self.data.withUnsafeBytes { bytes -> String in
|
||||
let pixels = bytes.baseAddress!.assumingMemoryBound(to: Float32.self)
|
||||
|
||||
var result = ""
|
||||
|
||||
for y in 0 ..< self.height {
|
||||
if y != 0 {
|
||||
result.append("\n")
|
||||
}
|
||||
for x in 0 ..< self.width {
|
||||
if x != 0 {
|
||||
result.append(" ")
|
||||
}
|
||||
result.append(String(format: "%03.02f", Double(pixels[y * self.width + x])))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class FloatCoefficientsYUVA420 {
|
||||
let yPlane: FloatCoefficientPlane
|
||||
let uPlane: FloatCoefficientPlane
|
||||
let vPlane: FloatCoefficientPlane
|
||||
let aPlane: FloatCoefficientPlane
|
||||
|
||||
init(width: Int, height: Int) {
|
||||
self.yPlane = FloatCoefficientPlane(width: width, height: height)
|
||||
self.uPlane = FloatCoefficientPlane(width: width / 2, height: height / 2)
|
||||
self.vPlane = FloatCoefficientPlane(width: width / 2, height: height / 2)
|
||||
self.aPlane = FloatCoefficientPlane(width: width, height: height)
|
||||
}
|
||||
|
||||
func copy(into other: FloatCoefficientsYUVA420) {
|
||||
self.yPlane.copy(into: other.yPlane)
|
||||
self.uPlane.copy(into: other.uPlane)
|
||||
self.vPlane.copy(into: other.vPlane)
|
||||
self.aPlane.copy(into: other.aPlane)
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatCoefficientPlane {
|
||||
func add(constant: Float32) {
|
||||
let buffer = malloc(4 * self.data.count)!
|
||||
|
||||
memset(buffer, Int32(bitPattern: constant.bitPattern), 4 * self.data.count)
|
||||
defer {
|
||||
free(buffer)
|
||||
}
|
||||
var constant = constant
|
||||
self.data.withUnsafeMutableBytes { bytes in
|
||||
vDSP_vfill(&constant, buffer.assumingMemoryBound(to: Float32.self), 1, vDSP_Length(bytes.count / 4))
|
||||
vDSP_vadd(bytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, buffer.assumingMemoryBound(to: Float32.self), 1, bytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, vDSP_Length(bytes.count / 4))
|
||||
}
|
||||
}
|
||||
|
||||
func add(other: FloatCoefficientPlane) {
|
||||
self.data.withUnsafeMutableBytes { bytes in
|
||||
other.data.withUnsafeBytes { otherBytes in
|
||||
vDSP_vadd(bytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, otherBytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, bytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, vDSP_Length(bytes.count / 4))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func subtract(other: FloatCoefficientPlane) {
|
||||
self.data.withUnsafeMutableBytes { bytes in
|
||||
other.data.withUnsafeBytes { otherBytes in
|
||||
vDSP_vsub(bytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, otherBytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, bytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, vDSP_Length(bytes.count / 4))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clamp() {
|
||||
self.data.withUnsafeMutableBytes { bytes in
|
||||
let pixels = bytes.baseAddress!.assumingMemoryBound(to: Float32.self)
|
||||
var low: Float32 = 0.0
|
||||
var high: Float32 = 255.0
|
||||
vDSP_vclip(pixels, 1, &low, &high, pixels, 1, vDSP_Length(bytes.count / 4))
|
||||
}
|
||||
}
|
||||
|
||||
func toDctCoefficients(target: DctCoefficientPlane) {
|
||||
self.data.withUnsafeBytes { bytes in
|
||||
target.data.withUnsafeMutableBytes { otherBytes in
|
||||
vDSP_vfix16(bytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, otherBytes.baseAddress!.assumingMemoryBound(to: Int16.self), 1, vDSP_Length(bytes.count / 4))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toUInt8(target: ImagePlane) {
|
||||
self.data.withUnsafeBytes { bytes in
|
||||
target.data.withUnsafeMutableBytes { otherBytes in
|
||||
vDSP_vfix8(bytes.baseAddress!.assumingMemoryBound(to: Float32.self), 1, otherBytes.baseAddress!.assumingMemoryBound(to: Int8.self), 1, vDSP_Length(bytes.count / 4))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func copy(into other: FloatCoefficientPlane) {
|
||||
assert(self.data.count == other.data.count)
|
||||
|
||||
self.data.withUnsafeBytes { bytes in
|
||||
other.data.withUnsafeMutableBytes { otherBytes in
|
||||
let _ = memcpy(otherBytes.baseAddress!, bytes.baseAddress!, bytes.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatCoefficientsYUVA420 {
|
||||
func add(constant: Float32) {
|
||||
self.yPlane.add(constant: constant)
|
||||
self.uPlane.add(constant: constant)
|
||||
self.vPlane.add(constant: constant)
|
||||
self.aPlane.add(constant: constant)
|
||||
}
|
||||
|
||||
func add(other: FloatCoefficientsYUVA420) {
|
||||
self.yPlane.add(other: other.yPlane)
|
||||
self.uPlane.add(other: other.uPlane)
|
||||
self.vPlane.add(other: other.vPlane)
|
||||
self.aPlane.add(other: other.aPlane)
|
||||
}
|
||||
|
||||
func subtract(other: FloatCoefficientsYUVA420) {
|
||||
self.yPlane.subtract(other: other.yPlane)
|
||||
self.uPlane.subtract(other: other.uPlane)
|
||||
self.vPlane.subtract(other: other.vPlane)
|
||||
self.aPlane.subtract(other: other.aPlane)
|
||||
}
|
||||
|
||||
func clamp() {
|
||||
self.yPlane.clamp()
|
||||
self.uPlane.clamp()
|
||||
self.vPlane.clamp()
|
||||
self.aPlane.clamp()
|
||||
}
|
||||
|
||||
func toDctCoefficients(target: DctCoefficientsYUVA420) {
|
||||
self.yPlane.toDctCoefficients(target: target.yPlane)
|
||||
self.uPlane.toDctCoefficients(target: target.uPlane)
|
||||
self.vPlane.toDctCoefficients(target: target.vPlane)
|
||||
self.aPlane.toDctCoefficients(target: target.aPlane)
|
||||
}
|
||||
|
||||
func toYUVA420(target: ImageYUVA420) {
|
||||
assert(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height)
|
||||
|
||||
self.yPlane.toUInt8(target: target.yPlane)
|
||||
self.uPlane.toUInt8(target: target.uPlane)
|
||||
self.vPlane.toUInt8(target: target.vPlane)
|
||||
self.aPlane.toUInt8(target: target.aPlane)
|
||||
}
|
||||
}
|
||||
|
||||
extension ImageARGB {
|
||||
func toYUVA420(target: ImageYUVA420) {
|
||||
precondition(self.argbPlane.width == target.yPlane.width && self.argbPlane.height == target.yPlane.height)
|
||||
@ -127,7 +366,7 @@ extension ImageYUVA420 {
|
||||
self.vPlane.data.withUnsafeBytes { vBuffer -> Void in
|
||||
self.aPlane.data.withUnsafeBytes { aBuffer -> Void in
|
||||
target.argbPlane.data.withUnsafeMutableBytes { argbBuffer -> Void in
|
||||
combineYUVAPlanesIntoARBB(
|
||||
combineYUVAPlanesIntoARGB(
|
||||
argbBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self),
|
||||
yBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self),
|
||||
uBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self),
|
||||
@ -158,31 +397,78 @@ final class DctData {
|
||||
let chromaTable: ImageDCTTable
|
||||
let chromaDct: ImageDCT
|
||||
|
||||
init?(lumaTable: Data, chromaTable: Data) {
|
||||
let deltaTable: ImageDCTTable
|
||||
let deltaDct: ImageDCT
|
||||
|
||||
init?(lumaTable: Data, chromaTable: Data, deltaTable: Data) {
|
||||
guard let lumaTableData = ImageDCTTable(data: lumaTable) else {
|
||||
return nil
|
||||
}
|
||||
guard let chromaTableData = ImageDCTTable(data: chromaTable) else {
|
||||
return nil
|
||||
}
|
||||
guard let deltaTableData = ImageDCTTable(data: deltaTable) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.lumaTable = lumaTableData
|
||||
self.lumaDct = ImageDCT(table: lumaTableData)
|
||||
|
||||
self.chromaTable = chromaTableData
|
||||
self.chromaDct = ImageDCT(table: chromaTableData)
|
||||
|
||||
self.deltaTable = deltaTableData
|
||||
self.deltaDct = ImageDCT(table: deltaTableData)
|
||||
}
|
||||
|
||||
init(generatingTablesAtQualityLuma lumaQuality: Int, chroma chromaQuality: Int) {
|
||||
self.lumaTable = ImageDCTTable(quality: lumaQuality, isChroma: false)
|
||||
init(generatingTablesAtQualityLuma lumaQuality: Int, chroma chromaQuality: Int, delta deltaQuality: Int) {
|
||||
self.lumaTable = ImageDCTTable(quality: lumaQuality, type: .luma)
|
||||
self.lumaDct = ImageDCT(table: self.lumaTable)
|
||||
|
||||
self.chromaTable = ImageDCTTable(quality: chromaQuality, isChroma: true)
|
||||
self.chromaTable = ImageDCTTable(quality: chromaQuality, type: .chroma)
|
||||
self.chromaDct = ImageDCT(table: self.chromaTable)
|
||||
|
||||
self.deltaTable = ImageDCTTable(quality: deltaQuality, type: .delta)
|
||||
self.deltaDct = ImageDCT(table: self.deltaTable)
|
||||
}
|
||||
}
|
||||
|
||||
extension ImageYUVA420 {
|
||||
func toCoefficients(target: FloatCoefficientsYUVA420) {
|
||||
precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height)
|
||||
|
||||
for i in 0 ..< 4 {
|
||||
let sourcePlane: ImagePlane
|
||||
let targetPlane: FloatCoefficientPlane
|
||||
switch i {
|
||||
case 0:
|
||||
sourcePlane = self.yPlane
|
||||
targetPlane = target.yPlane
|
||||
case 1:
|
||||
sourcePlane = self.uPlane
|
||||
targetPlane = target.uPlane
|
||||
case 2:
|
||||
sourcePlane = self.vPlane
|
||||
targetPlane = target.vPlane
|
||||
case 3:
|
||||
sourcePlane = self.aPlane
|
||||
targetPlane = target.aPlane
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
sourcePlane.data.withUnsafeBytes { sourceBytes in
|
||||
let sourcePixels = sourceBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
targetPlane.data.withUnsafeMutableBytes { bytes in
|
||||
let coefficients = bytes.baseAddress!.assumingMemoryBound(to: Float32.self)
|
||||
|
||||
vDSP_vfltu8(sourcePixels, 1, coefficients, 1, vDSP_Length(sourceBytes.count))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dct(dctData: DctData, target: DctCoefficientsYUVA420) {
|
||||
precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height)
|
||||
|
||||
@ -229,6 +515,13 @@ extension ImageYUVA420 {
|
||||
self.dct(dctData: dctData, target: results)
|
||||
return results
|
||||
}
|
||||
|
||||
func subtract(other: DctCoefficientsYUVA420) {
|
||||
self.yPlane.subtract(other: other.yPlane)
|
||||
self.uPlane.subtract(other: other.uPlane)
|
||||
self.vPlane.subtract(other: other.vPlane)
|
||||
self.aPlane.subtract(other: other.aPlane)
|
||||
}
|
||||
}
|
||||
|
||||
extension DctCoefficientsYUVA420 {
|
||||
@ -278,4 +571,92 @@ extension DctCoefficientsYUVA420 {
|
||||
self.idct(dctData: dctData, target: resultImage)
|
||||
return resultImage
|
||||
}
|
||||
|
||||
func dct(dctData: DctData, target: DctCoefficientsYUVA420) {
|
||||
precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height)
|
||||
|
||||
for i in 0 ..< 4 {
|
||||
let sourcePlane: DctCoefficientPlane
|
||||
let targetPlane: DctCoefficientPlane
|
||||
switch i {
|
||||
case 0:
|
||||
sourcePlane = self.yPlane
|
||||
targetPlane = target.yPlane
|
||||
case 1:
|
||||
sourcePlane = self.uPlane
|
||||
targetPlane = target.uPlane
|
||||
case 2:
|
||||
sourcePlane = self.vPlane
|
||||
targetPlane = target.vPlane
|
||||
case 3:
|
||||
sourcePlane = self.aPlane
|
||||
targetPlane = target.aPlane
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
sourcePlane.data.withUnsafeBytes { sourceBytes in
|
||||
let sourceCoefficients = sourceBytes.baseAddress!.assumingMemoryBound(to: Int16.self)
|
||||
|
||||
targetPlane.data.withUnsafeMutableBytes { bytes in
|
||||
let coefficients = bytes.baseAddress!.assumingMemoryBound(to: Int16.self)
|
||||
|
||||
//memcpy(coefficients, sourceCoefficients, sourceBytes.count)
|
||||
|
||||
dctData.deltaDct.forward4x4(sourceCoefficients, coefficients: coefficients, width: sourcePlane.width, height: sourcePlane.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func idct(dctData: DctData, target: DctCoefficientsYUVA420) {
|
||||
precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height)
|
||||
|
||||
for i in 0 ..< 4 {
|
||||
let sourcePlane: DctCoefficientPlane
|
||||
let targetPlane: DctCoefficientPlane
|
||||
switch i {
|
||||
case 0:
|
||||
sourcePlane = self.yPlane
|
||||
targetPlane = target.yPlane
|
||||
case 1:
|
||||
sourcePlane = self.uPlane
|
||||
targetPlane = target.uPlane
|
||||
case 2:
|
||||
sourcePlane = self.vPlane
|
||||
targetPlane = target.vPlane
|
||||
case 3:
|
||||
sourcePlane = self.aPlane
|
||||
targetPlane = target.aPlane
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
sourcePlane.data.withUnsafeBytes { sourceBytes in
|
||||
let sourceCoefficients = sourceBytes.baseAddress!.assumingMemoryBound(to: Int16.self)
|
||||
|
||||
targetPlane.data.withUnsafeMutableBytes { bytes in
|
||||
let coefficients = bytes.baseAddress!.assumingMemoryBound(to: Int16.self)
|
||||
|
||||
//memcpy(coefficients, sourceCoefficients, sourceBytes.count)
|
||||
|
||||
dctData.deltaDct.inverse4x4(sourceCoefficients, normalizedCoefficients: coefficients, width: sourcePlane.width, height: sourcePlane.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func subtract(other: DctCoefficientsYUVA420) {
|
||||
self.yPlane.subtract(other: other.yPlane)
|
||||
self.uPlane.subtract(other: other.uPlane)
|
||||
self.vPlane.subtract(other: other.vPlane)
|
||||
self.aPlane.subtract(other: other.aPlane)
|
||||
}
|
||||
|
||||
func toFloatCoefficients(target: FloatCoefficientsYUVA420) {
|
||||
self.yPlane.toFloatCoefficients(target: target.yPlane)
|
||||
self.uPlane.toFloatCoefficients(target: target.uPlane)
|
||||
self.vPlane.toFloatCoefficients(target: target.vPlane)
|
||||
self.aPlane.toFloatCoefficients(target: target.aPlane)
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,37 @@ import MultiAnimationRenderer
|
||||
import ShimmerEffect
|
||||
import TextFormat
|
||||
|
||||
public func animationCacheFetchFile(context: AccountContext, file: TelegramMediaFile, keyframeOnly: Bool) -> (AnimationCacheFetchOptions) -> Disposable {
|
||||
return { options in
|
||||
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
||||
|
||||
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
|
||||
guard let result = result else {
|
||||
return
|
||||
}
|
||||
|
||||
if file.isVideoEmoji || file.isVideoSticker {
|
||||
cacheVideoAnimation(path: result, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, firstFrameOnly: options.firstFrameOnly)
|
||||
} else if file.isAnimatedSticker {
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
|
||||
options.writer.finish()
|
||||
return
|
||||
}
|
||||
cacheLottieAnimation(data: data, width: Int(options.size.width), height: Int(options.size.height), keyframeOnly: keyframeOnly, writer: options.writer, firstFrameOnly: options.firstFrameOnly)
|
||||
} else {
|
||||
cacheStillSticker(path: result, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer)
|
||||
}
|
||||
})
|
||||
|
||||
let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: stickerPackFileReference(file), resource: file.resource).start()
|
||||
|
||||
return ActionDisposable {
|
||||
dataDisposable.dispose()
|
||||
fetchDisposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
||||
public static let queue = Queue()
|
||||
|
||||
@ -134,8 +165,12 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
||||
} else {
|
||||
let pointSize = self.pointSize
|
||||
let placeholderColor = self.placeholderColor
|
||||
self.loadDisposable = self.renderer.loadFirstFrame(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, completion: { [weak self] result in
|
||||
self.loadDisposable = self.renderer.loadFirstFrame(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: animationCacheFetchFile(context: self.context, file: file, keyframeOnly: true), completion: { [weak self] result, isFinal in
|
||||
if !result {
|
||||
if !isFinal {
|
||||
return
|
||||
}
|
||||
|
||||
MultiAnimationRendererImpl.firstFrameQueue.async {
|
||||
let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: pointSize, scale: min(2.0, UIScreenScale), imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor)
|
||||
|
||||
@ -167,40 +202,17 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
||||
|
||||
let context = self.context
|
||||
if file.isAnimatedSticker || file.isVideoEmoji {
|
||||
self.disposable = renderer.add(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: { size, writer in
|
||||
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
||||
let keyframeOnly = self.pixelSize.width >= 120.0
|
||||
|
||||
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
|
||||
guard let result = result else {
|
||||
return
|
||||
}
|
||||
|
||||
if file.isVideoEmoji {
|
||||
cacheVideoAnimation(path: result, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||
self.disposable = renderer.add(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: animationCacheFetchFile(context: context, file: file, keyframeOnly: keyframeOnly))
|
||||
} else {
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
|
||||
writer.finish()
|
||||
return
|
||||
}
|
||||
cacheLottieAnimation(data: data, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||
}
|
||||
})
|
||||
|
||||
let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: .customEmoji(media: file), resource: file.resource).start()
|
||||
|
||||
return ActionDisposable {
|
||||
dataDisposable.dispose()
|
||||
fetchDisposable.dispose()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.disposable = renderer.add(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: { size, writer in
|
||||
self.disposable = renderer.add(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: { options in
|
||||
let dataDisposable = context.account.postbox.mediaBox.resourceData(file.resource).start(next: { result in
|
||||
guard result.complete else {
|
||||
return
|
||||
}
|
||||
|
||||
cacheStillSticker(path: result.path, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||
cacheStillSticker(path: result.path, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer)
|
||||
})
|
||||
|
||||
let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: .customEmoji(media: file), resource: file.resource).start()
|
||||
|
@ -29,6 +29,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/VideoAnimationCache:VideoAnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/MultiVideoRenderer:MultiVideoRenderer",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
|
||||
"//submodules/SoftwareVideo:SoftwareVideo",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/PhotoResources:PhotoResources",
|
||||
@ -41,6 +42,7 @@ swift_library(
|
||||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||
"//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent",
|
||||
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||
"//submodules/MurMurHash32:MurMurHash32",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -24,6 +24,7 @@ import StickerPeekUI
|
||||
import UndoUI
|
||||
import AudioToolbox
|
||||
import SolidRoundedButtonComponent
|
||||
import EmojiTextAttachmentView
|
||||
|
||||
private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white)
|
||||
private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white)
|
||||
@ -1359,6 +1360,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
public let hasClear: Bool
|
||||
public let isExpandable: Bool
|
||||
public let displayPremiumBadges: Bool
|
||||
public let headerItem: EntityKeyboardGroupHeaderItem?
|
||||
public let items: [Item]
|
||||
|
||||
public init(
|
||||
@ -1373,6 +1375,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
hasClear: Bool,
|
||||
isExpandable: Bool,
|
||||
displayPremiumBadges: Bool,
|
||||
headerItem: EntityKeyboardGroupHeaderItem?,
|
||||
items: [Item]
|
||||
) {
|
||||
self.supergroupId = supergroupId
|
||||
@ -1386,6 +1389,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.hasClear = hasClear
|
||||
self.isExpandable = isExpandable
|
||||
self.displayPremiumBadges = displayPremiumBadges
|
||||
self.headerItem = headerItem
|
||||
self.items = items
|
||||
}
|
||||
|
||||
@ -1426,6 +1430,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if lhs.displayPremiumBadges != rhs.displayPremiumBadges {
|
||||
return false
|
||||
}
|
||||
if lhs.headerItem != rhs.headerItem {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
@ -1592,9 +1599,6 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
self.itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (self.nativeItemSize + minSpacing)))
|
||||
|
||||
//self.itemsPerRow * x + minSpacing * (x - 1) = itemHorizontalSpace
|
||||
//self.itemsPerRow * x + minSpacing * (self.itemsPerRow - 1) = itemHorizontalSpace
|
||||
//x = (itemHorizontalSpace - minSpacing * (self.itemsPerRow - 1)) / self.itemsPerRow
|
||||
let proposedItemSize = floor((itemHorizontalSpace - minSpacing * (CGFloat(self.itemsPerRow) - 1.0)) / CGFloat(self.itemsPerRow))
|
||||
|
||||
self.visibleItemSize = proposedItemSize < self.nativeItemSize ? proposedItemSize : self.nativeItemSize
|
||||
@ -1862,44 +1866,28 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: { size, writer in
|
||||
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
||||
|
||||
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
|
||||
guard let result = result else {
|
||||
return
|
||||
}
|
||||
|
||||
if file.isVideoEmoji || file.isVideoSticker {
|
||||
cacheVideoAnimation(path: result, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||
} else if file.isAnimatedSticker {
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
|
||||
writer.finish()
|
||||
return
|
||||
}
|
||||
cacheLottieAnimation(data: data, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||
} else {
|
||||
cacheStillSticker(path: result, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||
}
|
||||
})
|
||||
|
||||
let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: stickerPackFileReference(file), resource: file.resource).start()
|
||||
|
||||
return ActionDisposable {
|
||||
dataDisposable.dispose()
|
||||
fetchDisposable.dispose()
|
||||
}
|
||||
})
|
||||
strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, file: file, keyframeOnly: pixelSize.width >= 120.0))
|
||||
}
|
||||
|
||||
if attemptSynchronousLoad {
|
||||
if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize) {
|
||||
self.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
|
||||
self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, file: file, keyframeOnly: true), completion: { [weak self] success, isFinal in
|
||||
if !isFinal {
|
||||
if !success {
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
loadAnimation()
|
||||
} else {
|
||||
let _ = renderer.loadFirstFrame(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, completion: { [weak self] success in
|
||||
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Queue.mainQueue().async {
|
||||
loadAnimation()
|
||||
|
||||
if !success {
|
||||
@ -1908,11 +1896,46 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
} else {
|
||||
//self?.updateDisplayPlaceholder(displayPlaceholder: false)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
loadAnimation()
|
||||
}
|
||||
} else {
|
||||
self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, file: file, keyframeOnly: true), completion: { [weak self] success, isFinal in
|
||||
if !isFinal {
|
||||
if !success {
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Queue.mainQueue().async {
|
||||
loadAnimation()
|
||||
|
||||
if !success {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
} else {
|
||||
//self?.updateDisplayPlaceholder(displayPlaceholder: false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if let staticEmoji = staticEmoji {
|
||||
let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
|
||||
let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let preScaleFactor: CGFloat = 1.0
|
||||
|
@ -271,7 +271,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: component.emojiContent.context,
|
||||
file: emoji.file,
|
||||
item: .file(file: emoji.file),
|
||||
isFeatured: false,
|
||||
isPremiumLocked: false,
|
||||
animationCache: component.emojiContent.animationCache,
|
||||
@ -365,7 +365,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
isReorderable: !itemGroup.isFeatured,
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: stickerContent.context,
|
||||
file: file,
|
||||
item: itemGroup.headerItem ?? .file(file: file),
|
||||
isFeatured: itemGroup.isFeatured,
|
||||
isPremiumLocked: itemGroup.isPremiumLocked,
|
||||
animationCache: stickerContent.animationCache,
|
||||
@ -468,7 +468,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
isReorderable: !itemGroup.isFeatured,
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: component.emojiContent.context,
|
||||
file: file,
|
||||
item: itemGroup.headerItem ?? .file(file: file),
|
||||
isFeatured: itemGroup.isFeatured,
|
||||
isPremiumLocked: itemGroup.isPremiumLocked,
|
||||
animationCache: component.emojiContent.animationCache,
|
||||
|
@ -12,12 +12,67 @@ import MultiAnimationRenderer
|
||||
import AccountContext
|
||||
import MultilineTextComponent
|
||||
import LottieAnimationComponent
|
||||
import MurMurHash32
|
||||
|
||||
public enum EntityKeyboardGroupHeaderItem: Equatable {
|
||||
public enum ThumbnailType {
|
||||
case still
|
||||
case lottie
|
||||
case video
|
||||
}
|
||||
|
||||
case file(file: TelegramMediaFile)
|
||||
case packThumbnail(resource: MediaResourceReference, immediateThumbnailData: Data?, dimensions: CGSize, type: ThumbnailType)
|
||||
}
|
||||
|
||||
private func fileFromItem(_ item: EntityKeyboardGroupHeaderItem) -> TelegramMediaFile {
|
||||
let file: TelegramMediaFile
|
||||
switch item {
|
||||
case let .file(fileValue):
|
||||
file = fileValue
|
||||
case let .packThumbnail(resource, immediateThumbnailData, _, type):
|
||||
let mimeType: String
|
||||
let attributes: [TelegramMediaFileAttribute]
|
||||
|
||||
switch type {
|
||||
case .still:
|
||||
mimeType = "image/webp"
|
||||
attributes = [.FileName(fileName: "image.webp")]
|
||||
case .lottie:
|
||||
mimeType = "application/x-tgsticker"
|
||||
attributes = [
|
||||
.FileName(fileName: "sticker.tgs"),
|
||||
.Sticker(displayText: "", packReference: nil, maskData: nil)
|
||||
]
|
||||
case .video:
|
||||
mimeType = "video/webm"
|
||||
attributes = [
|
||||
.FileName(fileName: "sticker.webm"),
|
||||
.Sticker(displayText: "", packReference: nil, maskData: nil)
|
||||
]
|
||||
}
|
||||
|
||||
file = TelegramMediaFile(
|
||||
fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: Int64(murMurHashString32(resource.resource.id.stringRepresentation))),
|
||||
partialReference: nil,
|
||||
resource: resource.resource as! TelegramMediaResource,
|
||||
previewRepresentations: [],
|
||||
videoThumbnails: [],
|
||||
immediateThumbnailData: immediateThumbnailData,
|
||||
mimeType: mimeType,
|
||||
size: nil,
|
||||
attributes: attributes
|
||||
)
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
typealias EnvironmentType = EntityKeyboardTopPanelItemEnvironment
|
||||
|
||||
let context: AccountContext
|
||||
let file: TelegramMediaFile
|
||||
let item: EntityKeyboardGroupHeaderItem
|
||||
let isFeatured: Bool
|
||||
let isPremiumLocked: Bool
|
||||
let animationCache: AnimationCache
|
||||
@ -28,7 +83,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
file: TelegramMediaFile,
|
||||
item: EntityKeyboardGroupHeaderItem,
|
||||
isFeatured: Bool,
|
||||
isPremiumLocked: Bool,
|
||||
animationCache: AnimationCache,
|
||||
@ -38,7 +93,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
pressed: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
self.item = item
|
||||
self.isFeatured = isFeatured
|
||||
self.isPremiumLocked = isPremiumLocked
|
||||
self.animationCache = animationCache
|
||||
@ -52,7 +107,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.file.fileId != rhs.file.fileId {
|
||||
if lhs.item != rhs.item {
|
||||
return false
|
||||
}
|
||||
if lhs.isFeatured != rhs.isFeatured {
|
||||
@ -104,19 +159,27 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
|
||||
let itemEnvironment = environment[EntityKeyboardTopPanelItemEnvironment.self].value
|
||||
|
||||
let dimensions = component.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
let dimensions: CGSize
|
||||
switch component.item {
|
||||
case let .file(file):
|
||||
dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
case let .packThumbnail(_, _, dimensionsValue, _):
|
||||
dimensions = dimensionsValue
|
||||
}
|
||||
let displaySize = dimensions.aspectFitted(CGSize(width: 44.0, height: 44.0))
|
||||
|
||||
if self.itemLayer == nil {
|
||||
let file = fileFromItem(component.item)
|
||||
|
||||
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
||||
item: EmojiPagerContentComponent.Item(
|
||||
file: component.file,
|
||||
file: file,
|
||||
staticEmoji: nil,
|
||||
subgroupId: nil
|
||||
),
|
||||
context: component.context,
|
||||
attemptSynchronousLoad: false,
|
||||
file: component.file,
|
||||
file: file,
|
||||
staticEmoji: nil,
|
||||
cache: component.animationCache,
|
||||
renderer: component.animationRenderer,
|
||||
@ -204,7 +267,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
if self.placeholderView == nil, let component = self.component {
|
||||
let placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView(
|
||||
context: component.context,
|
||||
file: component.file,
|
||||
file: fileFromItem(component.item),
|
||||
shimmerView: nil,
|
||||
color: component.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08),
|
||||
size: CGSize(width: 28.0, height: 28.0)
|
||||
|
@ -6,7 +6,7 @@ import RLottieBinding
|
||||
import GZip
|
||||
import WebPBinding
|
||||
|
||||
public func cacheLottieAnimation(data: Data, width: Int, height: Int, writer: AnimationCacheItemWriter) {
|
||||
public func cacheLottieAnimation(data: Data, width: Int, height: Int, keyframeOnly: Bool, writer: AnimationCacheItemWriter, firstFrameOnly: Bool) {
|
||||
writer.queue.async {
|
||||
let decompressedData = TGGUnzipData(data, 1 * 1024 * 1024) ?? data
|
||||
guard let animation = LottieInstance(data: decompressedData, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
|
||||
@ -34,7 +34,11 @@ public func cacheLottieAnimation(data: Data, width: Int, height: Int, writer: An
|
||||
writer.add(with: { surface in
|
||||
animation.renderFrame(with: i, into: surface.argb, width: Int32(surface.width), height: Int32(surface.height), bytesPerRow: Int32(surface.bytesPerRow))
|
||||
return frameDuration
|
||||
}, proposedWidth: width, proposedHeight: height)
|
||||
}, proposedWidth: width, proposedHeight: height, insertKeyframe: i == 0 || keyframeOnly)
|
||||
|
||||
if firstFrameOnly {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
writer.finish()
|
||||
@ -53,7 +57,7 @@ public func cacheStillSticker(path: String, width: Int, height: Int, writer: Ani
|
||||
}
|
||||
memcpy(surface.argb, context.bytes, surface.height * surface.bytesPerRow)
|
||||
return 1.0
|
||||
}, proposedWidth: width, proposedHeight: height)
|
||||
}, proposedWidth: width, proposedHeight: height, insertKeyframe: true)
|
||||
}
|
||||
|
||||
writer.finish()
|
||||
|
@ -302,7 +302,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer {
|
||||
var slotIndex: Int
|
||||
private let preferredRowAlignment: Int
|
||||
|
||||
init(slotIndex: Int, preferredRowAlignment: Int, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable, stateUpdated: @escaping () -> Void) {
|
||||
init(slotIndex: Int, preferredRowAlignment: Int, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (AnimationCacheFetchOptions) -> Disposable, stateUpdated: @escaping () -> Void) {
|
||||
self.slotIndex = slotIndex
|
||||
self.preferredRowAlignment = preferredRowAlignment
|
||||
self.cache = cache
|
||||
@ -517,7 +517,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer {
|
||||
return nullAction
|
||||
}
|
||||
|
||||
func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable) -> Disposable? {
|
||||
func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (AnimationCacheFetchOptions) -> Disposable) -> Disposable? {
|
||||
if size != self.cellSize {
|
||||
return nil
|
||||
}
|
||||
@ -798,7 +798,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer {
|
||||
self.isPlaying = isPlaying
|
||||
}
|
||||
|
||||
public func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable) -> Disposable {
|
||||
public func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (AnimationCacheFetchOptions) -> Disposable) -> Disposable {
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
let alignedSize = CGSize(width: CGFloat(alignUp(size: Int(size.width), align: 16)), height: CGFloat(alignUp(size: Int(size.height), align: 16)))
|
||||
@ -829,8 +829,8 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer {
|
||||
return false
|
||||
}
|
||||
|
||||
public func loadFirstFrame(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, completion: @escaping (Bool) -> Void) -> Disposable {
|
||||
completion(false)
|
||||
public func loadFirstFrame(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (Bool, Bool) -> Void) -> Disposable {
|
||||
completion(false, true)
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ import AnimationCache
|
||||
import Accelerate
|
||||
|
||||
public protocol MultiAnimationRenderer: AnyObject {
|
||||
func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable) -> Disposable
|
||||
func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (AnimationCacheFetchOptions) -> Disposable) -> Disposable
|
||||
func loadFirstFrameSynchronously(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize) -> Bool
|
||||
func loadFirstFrame(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, completion: @escaping (Bool) -> Void) -> Disposable
|
||||
func loadFirstFrame(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (Bool, Bool) -> Void) -> Disposable
|
||||
}
|
||||
|
||||
private var nextRenderTargetId: Int64 = 1
|
||||
@ -240,7 +240,7 @@ private final class ItemAnimationContext {
|
||||
|
||||
let targets = Bag<Weak<MultiAnimationRenderTarget>>()
|
||||
|
||||
init(cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable, stateUpdated: @escaping () -> Void) {
|
||||
init(cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (AnimationCacheFetchOptions) -> Disposable, stateUpdated: @escaping () -> Void) {
|
||||
self.cache = cache
|
||||
self.stateUpdated = stateUpdated
|
||||
|
||||
@ -396,7 +396,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
self.stateUpdated = stateUpdated
|
||||
}
|
||||
|
||||
func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable) -> Disposable {
|
||||
func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (AnimationCacheFetchOptions) -> Disposable) -> Disposable {
|
||||
let itemKey = ItemKey(id: itemId, width: Int(size.width), height: Int(size.height))
|
||||
let itemContext: ItemAnimationContext
|
||||
if let current = self.itemContexts[itemKey] {
|
||||
@ -469,11 +469,14 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
func loadFirstFrame(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, completion: @escaping (Bool) -> Void) -> Disposable {
|
||||
return cache.getFirstFrame(queue: self.firstFrameQueue, sourceId: itemId, size: size, completion: { [weak target] item in
|
||||
guard let item = item else {
|
||||
func loadFirstFrame(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (Bool, Bool) -> Void) -> Disposable {
|
||||
var hadIntermediateUpdate = false
|
||||
return cache.getFirstFrame(queue: self.firstFrameQueue, sourceId: itemId, size: size, fetch: fetch, completion: { [weak target] item in
|
||||
guard let item = item.item else {
|
||||
let isFinal = item.isFinal
|
||||
hadIntermediateUpdate = true
|
||||
Queue.mainQueue().async {
|
||||
completion(false)
|
||||
completion(false, isFinal)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -487,19 +490,25 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
|
||||
Queue.mainQueue().async {
|
||||
guard let target = target else {
|
||||
completion(false)
|
||||
completion(false, true)
|
||||
return
|
||||
}
|
||||
if let loadedFrame = loadedFrame {
|
||||
target.contents = loadedFrame.image.cgImage
|
||||
if let cgImage = loadedFrame.image.cgImage {
|
||||
if hadIntermediateUpdate {
|
||||
target.transitionToContents(cgImage)
|
||||
} else {
|
||||
target.contents = cgImage
|
||||
}
|
||||
}
|
||||
|
||||
if let blurredRepresentationTarget = target.blurredRepresentationTarget {
|
||||
blurredRepresentationTarget.contents = loadedFrame.blurredRepresentation(color: target.blurredRepresentationBackgroundColor)?.cgImage
|
||||
}
|
||||
|
||||
completion(true)
|
||||
completion(true, true)
|
||||
} else {
|
||||
completion(false)
|
||||
completion(false, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -581,7 +590,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
public func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable) -> Disposable {
|
||||
public func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (AnimationCacheFetchOptions) -> Disposable) -> Disposable {
|
||||
let groupContext: GroupContext
|
||||
if let current = self.groupContext {
|
||||
groupContext = current
|
||||
@ -619,7 +628,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
return groupContext.loadFirstFrameSynchronously(target: target, cache: cache, itemId: itemId, size: size)
|
||||
}
|
||||
|
||||
public func loadFirstFrame(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, completion: @escaping (Bool) -> Void) -> Disposable {
|
||||
public func loadFirstFrame(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (Bool, Bool) -> Void) -> Disposable {
|
||||
let groupContext: GroupContext
|
||||
if let current = self.groupContext {
|
||||
groupContext = current
|
||||
@ -633,7 +642,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
self.groupContext = groupContext
|
||||
}
|
||||
|
||||
return groupContext.loadFirstFrame(target: target, cache: cache, itemId: itemId, size: size, completion: completion)
|
||||
return groupContext.loadFirstFrame(target: target, cache: cache, itemId: itemId, size: size, fetch: fetch, completion: completion)
|
||||
}
|
||||
|
||||
private func updateIsPlaying() {
|
||||
|
@ -18,7 +18,7 @@ private func roundUp(_ numToRound: Int, multiple: Int) -> Int {
|
||||
return numToRound + multiple - remainder
|
||||
}
|
||||
|
||||
public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: AnimationCacheItemWriter) {
|
||||
public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: AnimationCacheItemWriter, firstFrameOnly: Bool) {
|
||||
writer.queue.async {
|
||||
guard let frameSource = makeVideoStickerDirectFrameSource(queue: writer.queue, path: path, width: roundUp(width, multiple: 16), height: roundUp(height, multiple: 16), cachePathPrefix: nil, unpremultiplyAlpha: false) else {
|
||||
return
|
||||
@ -45,7 +45,11 @@ public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: A
|
||||
}
|
||||
}
|
||||
return frameDuration
|
||||
}, proposedWidth: frame.width, proposedHeight: frame.height)
|
||||
}, proposedWidth: frame.width, proposedHeight: frame.height, insertKeyframe: true)
|
||||
|
||||
if firstFrameOnly {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
@ -115,6 +115,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
var isPremiumLocked: Bool
|
||||
var isFeatured: Bool
|
||||
var isExpandable: Bool
|
||||
var headerItem: EntityKeyboardGroupHeaderItem?
|
||||
var items: [EmojiPagerContentComponent.Item]
|
||||
}
|
||||
var itemGroups: [ItemGroup] = []
|
||||
@ -162,7 +163,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Stickers_FrequentlyUsed, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Stickers_FrequentlyUsed, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -180,7 +181,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -213,13 +214,28 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
|
||||
var title = ""
|
||||
var headerItem: EntityKeyboardGroupHeaderItem?
|
||||
inner: for (id, info, _) in view.collectionInfos {
|
||||
if id == entry.index.collectionId, let info = info as? StickerPackCollectionInfo {
|
||||
title = info.title
|
||||
|
||||
if let thumbnail = info.thumbnail {
|
||||
let type: EntityKeyboardGroupHeaderItem.ThumbnailType
|
||||
if item.file.isAnimatedSticker {
|
||||
type = .lottie
|
||||
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
|
||||
type = .video
|
||||
} else {
|
||||
type = .still
|
||||
}
|
||||
|
||||
headerItem = .packThumbnail(resource: MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource), immediateThumbnailData: info.immediateThumbnailData, dimensions: thumbnail.dimensions.cgSize, type: type)
|
||||
}
|
||||
|
||||
break inner
|
||||
}
|
||||
}
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: false, isExpandable: false, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: false, isExpandable: false, headerItem: headerItem, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,7 +262,23 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: true, isExpandable: true, items: [resultItem]))
|
||||
|
||||
var headerItem: EntityKeyboardGroupHeaderItem?
|
||||
|
||||
if let thumbnail = featuredEmojiPack.info.thumbnail {
|
||||
let type: EntityKeyboardGroupHeaderItem.ThumbnailType
|
||||
if item.file.isAnimatedSticker {
|
||||
type = .lottie
|
||||
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
|
||||
type = .video
|
||||
} else {
|
||||
type = .still
|
||||
}
|
||||
|
||||
headerItem = .packThumbnail(resource: MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: featuredEmojiPack.info.id.id, accessHash: featuredEmojiPack.info.accessHash), resource: thumbnail.resource), immediateThumbnailData: featuredEmojiPack.info.immediateThumbnailData, dimensions: thumbnail.dimensions.cgSize, type: type)
|
||||
}
|
||||
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: true, isExpandable: true, headerItem: headerItem, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -277,6 +309,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
hasClear: hasClear,
|
||||
isExpandable: group.isExpandable,
|
||||
displayPremiumBadges: false,
|
||||
headerItem: group.headerItem,
|
||||
items: group.items
|
||||
)
|
||||
},
|
||||
@ -312,7 +345,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let emojiItems = emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, areCustomEmojiEnabled: areCustomEmojiEnabled)
|
||||
|
||||
let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks]
|
||||
let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.PremiumStickers, Namespaces.OrderedItemList.CloudPremiumStickers]
|
||||
let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudPremiumStickers]
|
||||
|
||||
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
|
||||
|
||||
@ -333,6 +366,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
var isPremiumLocked: Bool
|
||||
var isFeatured: Bool
|
||||
var displayPremiumBadges: Bool
|
||||
var headerItem: EntityKeyboardGroupHeaderItem?
|
||||
var items: [EmojiPagerContentComponent.Item]
|
||||
}
|
||||
var itemGroups: [ItemGroup] = []
|
||||
@ -340,15 +374,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
var savedStickers: OrderedItemListView?
|
||||
var recentStickers: OrderedItemListView?
|
||||
var premiumStickers: OrderedItemListView?
|
||||
var cloudPremiumStickers: OrderedItemListView?
|
||||
for orderedView in view.orderedItemListsViews {
|
||||
if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers {
|
||||
recentStickers = orderedView
|
||||
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers {
|
||||
savedStickers = orderedView
|
||||
} else if orderedView.collectionId == Namespaces.OrderedItemList.PremiumStickers {
|
||||
premiumStickers = orderedView
|
||||
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudPremiumStickers {
|
||||
cloudPremiumStickers = orderedView
|
||||
}
|
||||
@ -393,7 +424,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let trendingIsPremium = featuredStickersConfiguration?.isPremium ?? false
|
||||
let title = trendingIsPremium ? strings.Stickers_TrendingPremiumStickers : strings.StickerPacksSettings_FeaturedPacks
|
||||
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -418,7 +449,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleFavoriteStickers, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleFavoriteStickers, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -443,41 +474,45 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Stickers_FrequentlyUsed, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Stickers_FrequentlyUsed, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hasPremiumStickers = false
|
||||
var premiumStickers: [StickerPackItem] = []
|
||||
if hasPremium {
|
||||
if let premiumStickers = premiumStickers, !premiumStickers.items.isEmpty {
|
||||
hasPremiumStickers = true
|
||||
} else if let cloudPremiumStickers = cloudPremiumStickers, !cloudPremiumStickers.items.isEmpty {
|
||||
hasPremiumStickers = true
|
||||
for entry in view.entries {
|
||||
guard let item = entry.item as? StickerPackItem else {
|
||||
continue
|
||||
}
|
||||
|
||||
if item.file.isPremiumSticker {
|
||||
premiumStickers.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
if hasPremiumStickers {
|
||||
var premiumStickers = premiumStickers?.items ?? []
|
||||
if let cloudPremiumStickers = cloudPremiumStickers {
|
||||
premiumStickers.append(contentsOf: cloudPremiumStickers.items)
|
||||
if let cloudPremiumStickers = cloudPremiumStickers, !cloudPremiumStickers.items.isEmpty {
|
||||
premiumStickers.append(contentsOf: cloudPremiumStickers.items.compactMap { item -> StickerPackItem? in guard let item = item.contents.get(RecentMediaItem.self) else {
|
||||
return nil
|
||||
}
|
||||
return StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: item.media, indexKeys: [])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if !premiumStickers.isEmpty {
|
||||
var processedIds = Set<MediaId>()
|
||||
for item in premiumStickers {
|
||||
guard let item = item.contents.get(RecentMediaItem.self) else {
|
||||
if isPremiumDisabled && item.file.isPremiumSticker {
|
||||
continue
|
||||
}
|
||||
if isPremiumDisabled && item.media.isPremiumSticker {
|
||||
if processedIds.contains(item.file.fileId) {
|
||||
continue
|
||||
}
|
||||
if processedIds.contains(item.media.fileId) {
|
||||
continue
|
||||
}
|
||||
processedIds.insert(item.media.fileId)
|
||||
processedIds.insert(item.file.fileId)
|
||||
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
file: item.media,
|
||||
file: item.file,
|
||||
staticEmoji: nil,
|
||||
subgroupId: nil
|
||||
)
|
||||
@ -487,7 +522,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitlePremiumStickers, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitlePremiumStickers, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -508,13 +543,28 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
|
||||
var title = ""
|
||||
var headerItem: EntityKeyboardGroupHeaderItem?
|
||||
inner: for (id, info, _) in view.collectionInfos {
|
||||
if id == groupId, let info = info as? StickerPackCollectionInfo {
|
||||
title = info.title
|
||||
|
||||
if let thumbnail = info.thumbnail {
|
||||
let type: EntityKeyboardGroupHeaderItem.ThumbnailType
|
||||
if item.file.isAnimatedSticker {
|
||||
type = .lottie
|
||||
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
|
||||
type = .video
|
||||
} else {
|
||||
type = .still
|
||||
}
|
||||
|
||||
headerItem = .packThumbnail(resource: MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource), immediateThumbnailData: info.immediateThumbnailData, dimensions: thumbnail.dimensions.cgSize, type: type)
|
||||
}
|
||||
|
||||
break inner
|
||||
}
|
||||
}
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, headerItem: headerItem, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
|
||||
@ -542,8 +592,22 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
|
||||
let subtitle: String = strings.StickerPack_StickerCount(Int32(featuredStickerPack.info.count))
|
||||
var headerItem: EntityKeyboardGroupHeaderItem?
|
||||
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: featuredStickerPack.info.title, subtitle: subtitle, actionButtonTitle: strings.Stickers_Install, isPremiumLocked: isPremiumLocked, isFeatured: true, displayPremiumBadges: false, items: [resultItem]))
|
||||
if let thumbnail = featuredStickerPack.info.thumbnail {
|
||||
let type: EntityKeyboardGroupHeaderItem.ThumbnailType
|
||||
if item.file.isAnimatedSticker {
|
||||
type = .lottie
|
||||
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
|
||||
type = .video
|
||||
} else {
|
||||
type = .still
|
||||
}
|
||||
|
||||
headerItem = .packThumbnail(resource: MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), resource: thumbnail.resource), immediateThumbnailData: featuredStickerPack.info.immediateThumbnailData, dimensions: thumbnail.dimensions.cgSize, type: type)
|
||||
}
|
||||
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: featuredStickerPack.info.title, subtitle: subtitle, actionButtonTitle: strings.Stickers_Install, isPremiumLocked: isPremiumLocked, isFeatured: true, displayPremiumBadges: false, headerItem: headerItem, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -576,6 +640,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
hasClear: hasClear,
|
||||
isExpandable: false,
|
||||
displayPremiumBadges: group.displayPremiumBadges,
|
||||
headerItem: group.headerItem,
|
||||
items: group.items
|
||||
)
|
||||
},
|
||||
@ -1436,6 +1501,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
}
|
||||
|
||||
if self.currentInputData.gifs != nil {
|
||||
let hasRecentGifs = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs))
|
||||
|> map { savedGifs -> Bool in
|
||||
return !savedGifs.isEmpty
|
||||
@ -1456,6 +1522,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.inputDataDisposable?.dispose()
|
||||
|
@ -279,7 +279,7 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
|
||||
if view.lower == nil {
|
||||
var savedStickerIds = Set<Int64>()
|
||||
if let savedStickers = savedStickers, !savedStickers.items.isEmpty {
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FavoriteStickers.uppercased(), shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FavoriteStickers.uppercased(), shortName: "", thumbnail: nil, thumbnailFileId: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
for i in 0 ..< savedStickers.items.count {
|
||||
if let item = savedStickers.items[i].contents.get(SavedStickerItem.self) {
|
||||
savedStickerIds.insert(item.file.fileId.id)
|
||||
@ -300,7 +300,7 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
|
||||
}
|
||||
|
||||
if let recentStickers = recentStickers, !recentStickers.items.isEmpty {
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FrequentlyUsed.uppercased(), shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FrequentlyUsed.uppercased(), shortName: "", thumbnail: nil, thumbnailFileId: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
var addedCount = 0
|
||||
for i in 0 ..< recentStickers.items.count {
|
||||
if addedCount >= 20 {
|
||||
@ -334,7 +334,7 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
|
||||
}
|
||||
|
||||
if let peerSpecificPack = peerSpecificPack {
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_GroupStickers, shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_GroupStickers, shortName: "", thumbnail: nil, thumbnailFileId: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
|
||||
for i in 0 ..< peerSpecificPack.items.count {
|
||||
if let item = peerSpecificPack.items[i] as? StickerPackItem {
|
||||
@ -351,7 +351,7 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
|
||||
|
||||
if hasPremium && !isPremiumDisabled {
|
||||
var existingStickerIds = Set<Int64>()
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.premium.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_PremiumStickers.uppercased(), shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.premium.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_PremiumStickers.uppercased(), shortName: "", thumbnail: nil, thumbnailFileId: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
|
||||
if let premiumStickers = premiumStickers {
|
||||
for i in 0 ..< premiumStickers.items.count {
|
||||
|
Loading…
x
Reference in New Issue
Block a user