Skip to content

Commit e742ddc

Browse files
author
overtake
committed
- Bug fixes and minor improvements.
1 parent 1c858f1 commit e742ddc

File tree

23 files changed

+1322
-3813
lines changed

23 files changed

+1322
-3813
lines changed

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ fastlane/report.xml
33
xcuserdata
44
xcuserstate
55
.DS_Store
6+
core-xprojects/Mozjpeg/build

‎Telegram-Mac.xcworkspace/contents.xcworkspacedata

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Telegram-Mac/ImageCompression.swift

+36-103
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
//
23
// ImageCompression.swift
34
// Telegram
@@ -7,92 +8,12 @@
78
//
89

910
import Cocoa
10-
11-
import TelegramCore
12-
import SyncCore
13-
import Postbox
14-
import TGUIKit
15-
import MurMurHash32
16-
17-
public struct TinyThumbnailData: Equatable {
18-
let tablesDataHash: Int32
19-
let data: Data
20-
}
21-
22-
private let fixedTablesData = dataWithHexString("ffd8ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffd9")
23-
24-
private let fixedTablesDataHash: Int32 = murMurHash32Data(fixedTablesData)
25-
26-
private struct my_error_mgr {
27-
var pub = jpeg_error_mgr()
28-
}
29-
30-
func decompressTinyThumbnail(data: TinyThumbnailData) -> CGImage? {
31-
if data.tablesDataHash != fixedTablesDataHash {
32-
return nil
33-
}
34-
35-
var cinfo = jpeg_decompress_struct()
36-
var jerr = my_error_mgr()
37-
38-
cinfo.err = jpeg_std_error(&jerr.pub)
39-
//jerr.pub.error_exit = my_error_exit
40-
41-
/* Establish the setjmp return context for my_error_exit to use. */
42-
/*if (setjmp(jerr.setjmp_buffer)) {
43-
/* If we get here, the JPEG code has signaled an error.
44-
* We need to clean up the JPEG object, close the input file, and return.
45-
*/
46-
jpeg_destroy_decompress(&cinfo);
47-
fclose(infile);
48-
return 0;
49-
}*/
50-
51-
/* Now we can initialize the JPEG decompression object. */
52-
jpeg_CreateDecompress(&cinfo, JPEG_LIB_VERSION, MemoryLayout.size(ofValue: cinfo))
53-
54-
/* Step 2: specify data source (eg, a file) */
55-
56-
let fixedTablesDataLength = UInt(fixedTablesData.count)
57-
fixedTablesData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
58-
jpeg_mem_src(&cinfo, bytes, fixedTablesDataLength)
59-
jpeg_read_header(&cinfo, 0)
60-
}
61-
62-
let result = data.data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> CGImage? in
63-
jpeg_mem_src(&cinfo, bytes, fixedTablesDataLength)
64-
jpeg_read_header(&cinfo, 1)
65-
jpeg_start_decompress(&cinfo)
66-
let rowStride = Int(cinfo.output_width) * 3
67-
var tempBuffer = malloc(rowStride)!.assumingMemoryBound(to: UInt8.self)
68-
defer {
69-
free(tempBuffer)
70-
}
71-
let context = DrawingContext(size: CGSize(width: CGFloat(cinfo.output_width), height: CGFloat(cinfo.output_height)), scale: 1.0, clear: false)
72-
while cinfo.output_scanline < cinfo.output_height {
73-
let rowPointer = context.bytes.assumingMemoryBound(to: UInt8.self).advanced(by: Int(cinfo.output_scanline) * context.bytesPerRow)
74-
var row: JSAMPROW? = UnsafeMutablePointer(tempBuffer)
75-
jpeg_read_scanlines(&cinfo, &row, 1)
76-
for x in 0 ..< Int(cinfo.output_width) {
77-
rowPointer[x * 4 + 3] = 255
78-
for i in 0 ..< 3 {
79-
rowPointer[x * 4 + i] = tempBuffer[x * 3 + i]
80-
}
81-
}
82-
}
83-
return context.generateImage()
84-
}
85-
86-
jpeg_finish_decompress(&cinfo)
87-
jpeg_destroy_decompress(&cinfo)
88-
89-
return result
90-
}
11+
import Mozjpeg
9112

9213
private let tinyThumbnailHeaderPattern = Data(base64Encoded: "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////m8H////6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=")
9314
private let tinyThumbnailFooterPattern = Data(base64Encoded: "/9k=")
9415

95-
func decodeTinyThumbnail(data: Data) -> Data? {
16+
public func decodeTinyThumbnail(data: Data) -> Data? {
9617
if data.count < 3 {
9718
return nil
9819
}
@@ -120,28 +41,40 @@ func decodeTinyThumbnail(data: Data) -> Data? {
12041
return resultData
12142
}
12243

123-
func serializeTinyThumbnail(_ data: TinyThumbnailData) -> String {
124-
var result = "TTh1 \(data.data.count) bytes\n"
125-
result.append(String(data.tablesDataHash, radix: 16))
126-
result.append(data.data.base64EncodedString())
127-
let parsed = parseTinyThumbnail(result)
128-
assert(parsed == data)
129-
return result
44+
45+
46+
47+
public func extractImageExtraScans(_ data: Data) -> [Int] {
48+
return extractJPEGDataScans(data).map { item in
49+
return item.intValue
50+
}
13051
}
13152

132-
func parseTinyThumbnail(_ text: String) -> TinyThumbnailData? {
133-
if text.hasPrefix("TTh1") && text.count > 20 {
134-
guard let startIndex = text.range(of: "\n")?.upperBound else {
135-
return nil
136-
}
137-
let start = startIndex.encodedOffset
138-
guard let hash = Int32(String(text[text.index(text.startIndex, offsetBy: start) ..< text.index(text.startIndex, offsetBy: start + 8)]), radix: 16) else {
139-
return nil
140-
}
141-
guard let data = Data(base64Encoded: String(text[text.index(text.startIndex, offsetBy: start + 8)...])) else {
142-
return nil
143-
}
144-
return TinyThumbnailData(tablesDataHash: hash, data: data)
53+
public func compressImageToJPEG(_ cgImage: CGImage, quality: Float) -> Data? {
54+
if let result = compressJPEGData(cgImage) {
55+
return result
56+
}
57+
58+
let data = NSMutableData()
59+
guard let destination = CGImageDestinationCreateWithData(data as CFMutableData, "public.jpeg" as CFString, 1, nil) else {
60+
return nil
14561
}
146-
return nil
62+
63+
let options = NSMutableDictionary()
64+
options.setObject(quality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
65+
66+
CGImageDestinationAddImage(destination, cgImage, options as CFDictionary)
67+
CGImageDestinationFinalize(destination)
68+
69+
if data.length == 0 {
70+
return nil
71+
}
72+
73+
return data as Data
74+
}
75+
76+
77+
78+
public func compressImageMiniThumbnail(_ image: CGImage) -> Data? {
79+
return compressMiniThumbnail(image)
14780
}

‎Telegram-Mac/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
</dict>
3737
</array>
3838
<key>CFBundleVersion</key>
39-
<string>204748</string>
39+
<string>204754</string>
4040
<key>LSApplicationCategoryType</key>
4141
<string>public.app-category.social-networking</string>
4242
<key>LSFileQuarantineEnabled</key>

‎Telegram-Mac/MediaUtils.swift

+98-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ final class ImageRenderData {
3535
}
3636
}
3737

38+
private let progressiveRangeMap: [(Int, [Int])] = [
39+
(100, [0]),
40+
(400, [2]),
41+
(600, [4, 5]),
42+
(Int(Int32.max), [4, 5, 8, 9])
43+
]
3844

3945
func chatMessageFileStatus(account: Account, file: TelegramMediaFile, approximateSynchronousValue: Bool = false) -> Signal<MediaResourceStatus, NoError> {
4046
if let _ = file.resource as? LocalFileReferenceMediaResource {
@@ -59,9 +65,99 @@ func smallestImageRepresentation(_ representation:[TelegramMediaImageRepresentat
5965
return representation.first
6066
}
6167

68+
public func representationFetchRangeForDisplayAtSize(representation: TelegramMediaImageRepresentation, dimension: Int) -> Range<Int>? {
69+
if representation.progressiveSizes.count > 1 {
70+
var largestByteSize = Int(representation.progressiveSizes[0])
71+
for (maxDimension, byteSizes) in progressiveRangeMap {
72+
largestByteSize = Int(representation.progressiveSizes[byteSizes.last!])
73+
if maxDimension >= dimension {
74+
break
75+
}
76+
}
77+
return 0 ..< largestByteSize
78+
}
79+
return nil
80+
}
6281

63-
//
64-
func chatMessagePhotoDatas(postbox: Postbox, imageReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false, secureIdAccessContext: SecureIdAccessContext? = nil, peer: Peer? = nil) -> Signal<ImageRenderData, NoError> {
82+
func chatMessagePhotoDatas(postbox: Postbox, imageReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false, secureIdAccessContext: SecureIdAccessContext? = nil, peer: Peer? = nil, useMiniThumbnailIfAvailable: Bool = false) -> Signal<ImageRenderData, NoError> {
83+
84+
if let progressiveRepresentation = progressiveImageRepresentation(imageReference.media.representations), progressiveRepresentation.progressiveSizes.count == 10 {
85+
enum SizeSource {
86+
case miniThumbnail(data: Data)
87+
case image(size: Int)
88+
}
89+
90+
var sources: [SizeSource] = []
91+
if let miniThumbnail = imageReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) {
92+
sources.append(.miniThumbnail(data: miniThumbnail))
93+
}
94+
let thumbnailByteSize = Int(progressiveRepresentation.progressiveSizes[0])
95+
var largestByteSize = Int(progressiveRepresentation.progressiveSizes[0])
96+
for (maxDimension, byteSizes) in progressiveRangeMap {
97+
if Int(fullRepresentationSize.width) > 100 && maxDimension <= 100 {
98+
continue
99+
}
100+
sources.append(contentsOf: byteSizes.map { sizeIndex -> SizeSource in
101+
return .image(size: Int(progressiveRepresentation.progressiveSizes[sizeIndex]))
102+
})
103+
largestByteSize = Int(progressiveRepresentation.progressiveSizes[byteSizes.last!])
104+
if maxDimension >= Int(fullRepresentationSize.width) {
105+
break
106+
}
107+
}
108+
109+
return Signal { subscriber in
110+
let signals: [Signal<(SizeSource, Data?), NoError>] = sources.map { source -> Signal<(SizeSource, Data?), NoError> in
111+
switch source {
112+
case let .miniThumbnail(data):
113+
return .single((source, data))
114+
case let .image(size):
115+
return postbox.mediaBox.resourceData(progressiveRepresentation.resource, size: Int(progressiveRepresentation.progressiveSizes.last!), in: 0 ..< size, mode: .incremental, notifyAboutIncomplete: true, attemptSynchronously: synchronousLoad)
116+
|> map { (data, _) -> (SizeSource, Data?) in
117+
return (source, data)
118+
}
119+
}
120+
}
121+
122+
let dataDisposable = combineLatest(signals).start(next: { results in
123+
var foundData = false
124+
loop: for i in (0 ..< results.count).reversed() {
125+
let isLastSize = i == results.count - 1
126+
switch results[i].0 {
127+
case .image:
128+
if let data = results[i].1, data.count != 0 {
129+
subscriber.putNext(ImageRenderData(nil, data, isLastSize))
130+
foundData = true
131+
if isLastSize {
132+
subscriber.putCompletion()
133+
}
134+
break loop
135+
}
136+
case let .miniThumbnail(thumbnailData):
137+
subscriber.putNext(ImageRenderData(thumbnailData, nil, false))
138+
foundData = true
139+
break loop
140+
}
141+
}
142+
if !foundData {
143+
subscriber.putNext(ImageRenderData(nil, nil, false))
144+
}
145+
})
146+
var fetchDisposable: Disposable?
147+
if autoFetchFullSize {
148+
fetchDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: imageReference.resourceReference(progressiveRepresentation.resource), range: (0 ..< largestByteSize, .default), statsCategory: .image).start()
149+
} else if useMiniThumbnailIfAvailable {
150+
fetchDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: imageReference.resourceReference(progressiveRepresentation.resource), range: (0 ..< thumbnailByteSize, .default), statsCategory: .image).start()
151+
}
152+
153+
return ActionDisposable {
154+
dataDisposable.dispose()
155+
fetchDisposable?.dispose()
156+
}
157+
}
158+
}
159+
160+
65161
if let smallestRepresentation = smallestImageRepresentation(imageReference.media.representations), let largestRepresentation = imageReference.media.representationForDisplayAtSize(PixelDimensions(fullRepresentationSize)) {
66162

67163

‎Telegram-Mac/Telegram-Mac-Bridging-Header.h

-4
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@
2525
#ifndef SHARE
2626
#import "ffmpeg/include/libavcodec/avcodec.h"
2727
#import "ffmpeg/include/libavformat/avformat.h"
28-
#import "libjpeg-turbo/jpeglib.h"
29-
#import "libjpeg-turbo/jerror.h"
30-
#import "libjpeg-turbo/turbojpeg.h"
31-
#import "libjpeg-turbo/jmorecfg.h"
3228
#import "FFMpegRemuxer.h"
3329
#import "FFMpegGlobals.h"
3430
#import "FFMpegAVFormatContext.h"

0 commit comments

Comments
 (0)