Merge branch 'experiments/animation-residual-coding'

This commit is contained in:
Ali 2022-07-29 18:34:56 +02:00
commit 228bc712e9
35 changed files with 2784 additions and 498 deletions

View File

@ -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
}

View 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",
],
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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
}
}

View 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()
}
}
}

View File

@ -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)
}
}
}

View File

@ -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 {

View File

@ -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:

View File

@ -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:

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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 }

View File

@ -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

View File

@ -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 */

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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",

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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()

View File

@ -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
}

View File

@ -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() {

View File

@ -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
}

View File

@ -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()

View File

@ -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 {