|
| 1 | + |
1 | 2 | //
|
2 | 3 | // ImageCompression.swift
|
3 | 4 | // Telegram
|
|
7 | 8 | //
|
8 | 9 |
|
9 | 10 | 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 |
91 | 12 |
|
92 | 13 | 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=")
|
93 | 14 | private let tinyThumbnailFooterPattern = Data(base64Encoded: "/9k=")
|
94 | 15 |
|
95 |
| -func decodeTinyThumbnail(data: Data) -> Data? { |
| 16 | +public func decodeTinyThumbnail(data: Data) -> Data? { |
96 | 17 | if data.count < 3 {
|
97 | 18 | return nil
|
98 | 19 | }
|
@@ -120,28 +41,40 @@ func decodeTinyThumbnail(data: Data) -> Data? {
|
120 | 41 | return resultData
|
121 | 42 | }
|
122 | 43 |
|
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 | + } |
130 | 51 | }
|
131 | 52 |
|
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 |
145 | 61 | }
|
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) |
147 | 80 | }
|
0 commit comments