/src/serenity/Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include "JPEGWriter.h" |
8 | | #include "JPEGShared.h" |
9 | | #include "JPEGWriterTables.h" |
10 | | #include <AK/BitStream.h> |
11 | | #include <AK/Endian.h> |
12 | | #include <AK/Enumerate.h> |
13 | | #include <AK/Function.h> |
14 | | #include <LibGfx/Bitmap.h> |
15 | | #include <LibGfx/CMYKBitmap.h> |
16 | | |
17 | | namespace Gfx { |
18 | | |
19 | | namespace { |
20 | | |
21 | | enum class Mode { |
22 | | RGB, |
23 | | CMYK, |
24 | | }; |
25 | | |
26 | | // This is basically a BigEndianOutputBitStream, the only difference |
27 | | // is that it appends 0x00 after each 0xFF when it writes bits. |
28 | | class JPEGBigEndianOutputBitStream : public Stream { |
29 | | public: |
30 | | explicit JPEGBigEndianOutputBitStream(Stream& stream) |
31 | 0 | : m_stream(stream) |
32 | 0 | { |
33 | 0 | } |
34 | | |
35 | | virtual ErrorOr<Bytes> read_some(Bytes) override |
36 | 0 | { |
37 | 0 | return Error::from_errno(EBADF); |
38 | 0 | } |
39 | | |
40 | | virtual ErrorOr<size_t> write_some(ReadonlyBytes bytes) override |
41 | 0 | { |
42 | 0 | VERIFY(m_bit_offset == 0); |
43 | 0 | return m_stream.write_some(bytes); |
44 | 0 | } |
45 | | |
46 | | template<UnsignedIntegral T> |
47 | | ErrorOr<void> write_bits(T value, size_t bit_count) |
48 | 0 | { |
49 | 0 | VERIFY(m_bit_offset <= 7); |
50 | | |
51 | 0 | while (bit_count > 0) { |
52 | 0 | u8 const next_bit = (value >> (bit_count - 1)) & 1; |
53 | 0 | bit_count--; |
54 | |
|
55 | 0 | m_current_byte <<= 1; |
56 | 0 | m_current_byte |= next_bit; |
57 | 0 | m_bit_offset++; |
58 | |
|
59 | 0 | if (m_bit_offset > 7) { |
60 | 0 | TRY(m_stream.write_value(m_current_byte)); |
61 | 0 | if (m_current_byte == 0xFF) |
62 | 0 | TRY(m_stream.write_value<u8>(0)); |
63 | |
|
64 | 0 | m_bit_offset = 0; |
65 | 0 | m_current_byte = 0; |
66 | 0 | } |
67 | 0 | } |
68 | |
|
69 | 0 | return {}; |
70 | 0 | } Unexecuted instantiation: JPEGWriter.cpp:_ZN3Gfx12_GLOBAL__N_128JPEGBigEndianOutputBitStream10write_bitsITkN2AK8Concepts16UnsignedIntegralEtEENS3_7ErrorOrIvNS3_5ErrorEEET_m Unexecuted instantiation: JPEGWriter.cpp:_ZN3Gfx12_GLOBAL__N_128JPEGBigEndianOutputBitStream10write_bitsITkN2AK8Concepts16UnsignedIntegralEhEENS3_7ErrorOrIvNS3_5ErrorEEET_m |
71 | | |
72 | | virtual bool is_eof() const override |
73 | 0 | { |
74 | 0 | return true; |
75 | 0 | } |
76 | | |
77 | | virtual bool is_open() const override |
78 | 0 | { |
79 | 0 | return m_stream.is_open(); |
80 | 0 | } |
81 | | |
82 | | virtual void close() override |
83 | 0 | { |
84 | 0 | } |
85 | | |
86 | | ErrorOr<void> align_to_byte_boundary(u8 filler = 0x0) |
87 | 0 | { |
88 | 0 | if (m_bit_offset == 0) |
89 | 0 | return {}; |
90 | | |
91 | 0 | TRY(write_bits(filler, 8 - m_bit_offset)); |
92 | 0 | VERIFY(m_bit_offset == 0); |
93 | 0 | return {}; |
94 | 0 | } |
95 | | |
96 | | private: |
97 | | Stream& m_stream; |
98 | | u8 m_current_byte { 0 }; |
99 | | size_t m_bit_offset { 0 }; |
100 | | }; |
101 | | |
102 | | void interpolate(f32* component, f32 max_value, i8 start, i8 stop) |
103 | 0 | { |
104 | | // We're creating a uniform (ɑ = 1) Catmull–Rom curve for the missing points. |
105 | | // That means that tᵢ₊₁ = tᵢ + 1. |
106 | | // Note that component[start] should be interpolated but component[stop] should not. |
107 | | |
108 | | // p1 and p2 are set to the ceil value. |
109 | | // p0 is set to the last non-max value if possible, otherwise the value of p3 for symmetry. |
110 | | // The same logic is applied to p3. |
111 | 0 | f32 const p0 = start == 0 ? component[zigzag_map[stop]] : component[zigzag_map[start - 1]]; |
112 | 0 | f32 const p1 = max_value; |
113 | 0 | f32 const p2 = max_value; |
114 | 0 | f32 const p3 = stop > 63 ? p0 : component[zigzag_map[stop]]; |
115 | |
|
116 | 0 | f32 const t0 = 0.0f; |
117 | 0 | f32 const t1 = 1; |
118 | 0 | f32 const t2 = 2; |
119 | 0 | f32 const t3 = 3; |
120 | |
|
121 | 0 | f32 const step = 1. / (stop - start + 1); |
122 | 0 | f32 t = t1; |
123 | 0 | for (i8 i = start; i < stop; ++i) { |
124 | 0 | t += step; |
125 | 0 | f32 const A1 = p0 * (t1 - t) / (t1 - t0) + p1 * (t - t0) / (t1 - t0); |
126 | 0 | f32 const A2 = p1 * (t2 - t) / (t2 - t1) + p2 * (t - t1) / (t2 - t1); |
127 | 0 | f32 const A3 = p2 * (t3 - t) / (t3 - t2) + p3 * (t - t2) / (t3 - t2); |
128 | 0 | f32 const B1 = A1 * (t2 - t) / (t2 - t0) + A2 * (t - t0) / (t2 - t0); |
129 | 0 | f32 const B2 = A2 * (t3 - t) / (t3 - t1) + A3 * (t - t1) / (t3 - t1); |
130 | 0 | f32 const C = B1 * (t2 - t) / (t2 - t1) + B2 * (t - t1) / (t2 - t1); |
131 | |
|
132 | 0 | component[zigzag_map[i]] = C; |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | | class JPEGEncodingContext { |
137 | | public: |
138 | | JPEGEncodingContext(JPEGBigEndianOutputBitStream output_stream) |
139 | 0 | : m_bit_stream(move(output_stream)) |
140 | 0 | { |
141 | 0 | } |
142 | | |
143 | | ErrorOr<void> initialize_mcu(Bitmap const& bitmap) |
144 | 0 | { |
145 | 0 | u64 const horizontal_macroblocks = ceil_div(bitmap.width(), 8); |
146 | 0 | u64 const vertical_macroblocks = ceil_div(bitmap.height(), 8); |
147 | 0 | TRY(m_macroblocks.try_resize(horizontal_macroblocks * vertical_macroblocks)); |
148 | |
|
149 | 0 | for (u16 y {}; y < bitmap.height(); ++y) { |
150 | 0 | u16 const vertical_macroblock_index = y / 8; |
151 | 0 | u16 const vertical_pixel_offset = y - vertical_macroblock_index * 8; |
152 | |
|
153 | 0 | for (u16 x {}; x < bitmap.width(); ++x) { |
154 | 0 | u16 const horizontal_macroblock_index = x / 8; |
155 | 0 | u16 const horizontal_pixel_offset = x - horizontal_macroblock_index * 8; |
156 | |
|
157 | 0 | auto& macroblock = m_macroblocks[vertical_macroblock_index * horizontal_macroblocks + horizontal_macroblock_index]; |
158 | 0 | auto const pixel_offset = vertical_pixel_offset * 8 + horizontal_pixel_offset; |
159 | |
|
160 | 0 | auto const original_pixel = bitmap.get_pixel(x, y); |
161 | 0 | macroblock.r[pixel_offset] = original_pixel.red(); |
162 | 0 | macroblock.g[pixel_offset] = original_pixel.green(); |
163 | 0 | macroblock.b[pixel_offset] = original_pixel.blue(); |
164 | 0 | } |
165 | 0 | } |
166 | |
|
167 | 0 | return {}; |
168 | 0 | } |
169 | | |
170 | | ErrorOr<void> initialize_mcu(CMYKBitmap const& bitmap) |
171 | 0 | { |
172 | 0 | u64 const horizontal_macroblocks = ceil_div(bitmap.size().width(), 8); |
173 | 0 | u64 const vertical_macroblocks = ceil_div(bitmap.size().height(), 8); |
174 | 0 | TRY(m_macroblocks.try_resize(horizontal_macroblocks * vertical_macroblocks)); |
175 | |
|
176 | 0 | for (u16 y {}; y < bitmap.size().height(); ++y) { |
177 | 0 | u16 const vertical_macroblock_index = y / 8; |
178 | 0 | u16 const vertical_pixel_offset = y - vertical_macroblock_index * 8; |
179 | |
|
180 | 0 | for (u16 x {}; x < bitmap.size().width(); ++x) { |
181 | 0 | u16 const horizontal_macroblock_index = x / 8; |
182 | 0 | u16 const horizontal_pixel_offset = x - horizontal_macroblock_index * 8; |
183 | |
|
184 | 0 | auto& macroblock = m_macroblocks[vertical_macroblock_index * horizontal_macroblocks + horizontal_macroblock_index]; |
185 | 0 | auto const pixel_offset = vertical_pixel_offset * 8 + horizontal_pixel_offset; |
186 | |
|
187 | 0 | auto const original_pixel = bitmap.scanline(y)[x]; |
188 | |
|
189 | 0 | macroblock.r[pixel_offset] = original_pixel.c; |
190 | 0 | macroblock.g[pixel_offset] = original_pixel.m; |
191 | 0 | macroblock.b[pixel_offset] = original_pixel.y; |
192 | 0 | macroblock.k[pixel_offset] = original_pixel.k; |
193 | 0 | } |
194 | 0 | } |
195 | |
|
196 | 0 | return {}; |
197 | 0 | } |
198 | | |
199 | | static Array<f32, 64> create_cosine_lookup_table() |
200 | 0 | { |
201 | 0 | static constexpr f32 pi_over_16 = AK::Pi<f32> / 16; |
202 | |
|
203 | 0 | Array<f32, 64> table; |
204 | |
|
205 | 0 | for (u8 u = 0; u < 8; ++u) { |
206 | 0 | for (u8 x = 0; x < 8; ++x) |
207 | 0 | table[u * 8 + x] = cos((2 * x + 1) * u * pi_over_16); |
208 | 0 | } |
209 | |
|
210 | 0 | return table; |
211 | 0 | } |
212 | | |
213 | | void convert_to_ycbcr(Mode mode) |
214 | 0 | { |
215 | 0 | for (auto& macroblock : m_macroblocks) { |
216 | 0 | for (u8 i = 0; i < 64; ++i) { |
217 | 0 | auto r = macroblock.r[i]; |
218 | 0 | auto g = macroblock.g[i]; |
219 | 0 | auto b = macroblock.b[i]; |
220 | | |
221 | | // Conversion from YCbCr to RGB isn't specified in the first JPEG specification but in the JFIF extension: |
222 | | // See: https://www.itu.int/rec/dologin_pub.asp?lang=f&id=T-REC-T.871-201105-I!!PDF-E&type=items |
223 | | // 7 - Conversion to and from RGB |
224 | 0 | auto const y_ = 0.299f * r + 0.587f * g + 0.114f * b; |
225 | 0 | auto const cb = -0.1687f * r - 0.3313f * g + 0.5f * b + 128; |
226 | 0 | auto const cr = 0.5f * r - 0.4187f * g - 0.0813f * b + 128; |
227 | | |
228 | | // A.3.1 - Level shift |
229 | 0 | macroblock.y[i] = y_ - 128; |
230 | 0 | macroblock.cb[i] = cb - 128; |
231 | 0 | macroblock.cr[i] = cr - 128; |
232 | |
|
233 | 0 | if (mode == Mode::CMYK) { |
234 | | // To get YCCK, the CMY part is converted to RGB (ignoring the K component), and then the RGB is converted to YCbCr. |
235 | | // r is `255 - c` (and similar for g/m b/y), but with the Adobe YCCK color transform marker, the CMY |
236 | | // channels are stored inverted, which cancels out: 255 - (255 - x) == x. |
237 | | // K is stored as-is (meaning it's inverted once for the color transform). |
238 | 0 | macroblock.k[i] = 255 - macroblock.k[i]; |
239 | | // A.3.1 - Level shift |
240 | 0 | macroblock.k[i] -= 128; |
241 | 0 | } |
242 | 0 | } |
243 | 0 | } |
244 | 0 | } |
245 | | |
246 | | void fdct_and_quantization(Mode mode) |
247 | 0 | { |
248 | 0 | static auto cosine_table = create_cosine_lookup_table(); |
249 | |
|
250 | 0 | for (auto& macroblock : m_macroblocks) { |
251 | 0 | constexpr double inverse_sqrt_2 = M_SQRT1_2; |
252 | |
|
253 | 0 | auto const convert_one_component = [&](f32 component[], QuantizationTable const& table) { |
254 | 0 | Array<f32, 64> result {}; |
255 | |
|
256 | 0 | auto const sum_xy = [&](u8 u, u8 v) { |
257 | 0 | f32 sum {}; |
258 | 0 | for (u8 y {}; y < 8; ++y) { |
259 | 0 | for (u8 x {}; x < 8; ++x) |
260 | 0 | sum += component[y * 8 + x] * cosine_table[u * 8 + x] * cosine_table[v * 8 + y]; |
261 | 0 | } |
262 | 0 | return sum; |
263 | 0 | }; |
264 | |
|
265 | 0 | for (u8 v {}; v < 8; ++v) { |
266 | 0 | f32 const cv = v == 0 ? inverse_sqrt_2 : 1; |
267 | 0 | for (u8 u {}; u < 8; ++u) { |
268 | 0 | auto const table_index = v * 8 + u; |
269 | |
|
270 | 0 | f32 const cu = u == 0 ? inverse_sqrt_2 : 1; |
271 | | |
272 | | // A.3.3 - FDCT and IDCT |
273 | 0 | f32 const fdct = cu * cv * sum_xy(u, v) / 4; |
274 | | |
275 | | // A.3.4 - DCT coefficient quantization |
276 | 0 | f32 const quantized = fdct / table.table[table_index]; |
277 | |
|
278 | 0 | result[table_index] = quantized; |
279 | 0 | } |
280 | 0 | } |
281 | |
|
282 | 0 | for (u8 i {}; i < result.size(); ++i) |
283 | 0 | component[i] = result[i]; |
284 | 0 | }; |
285 | |
|
286 | 0 | convert_one_component(macroblock.y, m_luminance_quantization_table); |
287 | 0 | convert_one_component(macroblock.cb, m_chrominance_quantization_table); |
288 | 0 | convert_one_component(macroblock.cr, m_chrominance_quantization_table); |
289 | 0 | if (mode == Mode::CMYK) |
290 | 0 | convert_one_component(macroblock.k, m_luminance_quantization_table); |
291 | 0 | } |
292 | 0 | } |
293 | | |
294 | | void apply_deringing() |
295 | 0 | { |
296 | | // The method used here is described at: https://kornel.ski/deringing/. |
297 | |
|
298 | 0 | for (auto& macroblock : m_macroblocks) { |
299 | 0 | for (auto component : { macroblock.r, macroblock.g, macroblock.b }) { |
300 | 0 | static constexpr auto maximum_value = NumericLimits<u8>::max(); |
301 | 0 | Optional<u8> start; |
302 | 0 | u8 i = 0; |
303 | 0 | for (; i < 64; ++i) { |
304 | 0 | if (component[zigzag_map[i]] == maximum_value) { |
305 | 0 | if (!start.has_value()) |
306 | 0 | start = i; |
307 | 0 | else |
308 | 0 | continue; |
309 | 0 | } else { |
310 | 0 | if (start.has_value() && i - *start > 2) { |
311 | 0 | interpolate(component, maximum_value, *start, i); |
312 | 0 | } |
313 | 0 | start.clear(); |
314 | 0 | } |
315 | 0 | } |
316 | |
|
317 | 0 | if (start != 0 && component[zigzag_map[63]] == maximum_value) |
318 | 0 | interpolate(component, maximum_value, *start, 64); |
319 | 0 | } |
320 | 0 | } |
321 | 0 | } |
322 | | |
323 | | ErrorOr<void> huffman_encode_macroblocks(Mode mode) |
324 | 0 | { |
325 | 0 | for (auto& float_macroblock : m_macroblocks) { |
326 | 0 | auto macroblock = float_macroblock.as_i16(); |
327 | |
|
328 | 0 | for (auto [i, component] : enumerate(to_array({ macroblock.y, macroblock.cb, macroblock.cr, macroblock.k }))) { |
329 | 0 | if (mode == Mode::CMYK || i < 3) { |
330 | 0 | TRY(encode_dc(component, i)); |
331 | 0 | TRY(encode_ac(component, i)); |
332 | 0 | } |
333 | 0 | } |
334 | 0 | } |
335 | 0 | m_macroblocks.clear(); |
336 | |
|
337 | 0 | TRY(find_optimal_huffman_tables()); |
338 | 0 | return {}; |
339 | 0 | } |
340 | | |
341 | | ErrorOr<void> write_huffman_stream() |
342 | 0 | { |
343 | 0 | TRY(write_symbols_to_stream()); |
344 | 0 | TRY(m_bit_stream.align_to_byte_boundary(0xFF)); |
345 | 0 | return {}; |
346 | 0 | } |
347 | | |
348 | | void set_quantization_tables(int quality) |
349 | 0 | { |
350 | 0 | set_quantization_table(m_luminance_quantization_table, s_default_luminance_quantization_table, quality); |
351 | 0 | set_quantization_table(m_chrominance_quantization_table, s_default_chrominance_quantization_table, quality); |
352 | 0 | } |
353 | | |
354 | | QuantizationTable const& luminance_quantization_table() const |
355 | 0 | { |
356 | 0 | return m_luminance_quantization_table; |
357 | 0 | } |
358 | | |
359 | | QuantizationTable const& chrominance_quantization_table() const |
360 | 0 | { |
361 | 0 | return m_chrominance_quantization_table; |
362 | 0 | } |
363 | | |
364 | | OutputHuffmanTable dc_luminance_huffman_table; |
365 | | OutputHuffmanTable dc_chrominance_huffman_table; |
366 | | |
367 | | OutputHuffmanTable ac_luminance_huffman_table; |
368 | | OutputHuffmanTable ac_chrominance_huffman_table; |
369 | | |
370 | | private: |
371 | | struct RawBits { |
372 | | u16 bits {}; |
373 | | u8 length {}; |
374 | | }; |
375 | | struct Symbol { |
376 | | u8 byte {}; |
377 | | u8 component_id {}; |
378 | | bool is_dc {}; |
379 | | }; |
380 | | using SymbolOrRawBits = Variant<Symbol, RawBits>; |
381 | | |
382 | | static void set_quantization_table(QuantizationTable& destination, QuantizationTable const& source, int quality) |
383 | 0 | { |
384 | | // In order to be compatible with libjpeg-turbo, we use the same coefficients as them. |
385 | |
|
386 | 0 | quality = clamp(quality, 1, 100); |
387 | |
|
388 | 0 | if (quality < 50) |
389 | 0 | quality = 5000 / quality; |
390 | 0 | else |
391 | 0 | quality = 200 - quality * 2; |
392 | |
|
393 | 0 | destination = source; |
394 | 0 | for (u8 i {}; i < 64; ++i) { |
395 | 0 | auto const shifted_value = (destination.table[i] * quality + 50) / 100; |
396 | 0 | destination.table[i] = clamp(shifted_value, 1, 255); |
397 | 0 | } |
398 | 0 | } |
399 | | |
400 | | ErrorOr<void> write_symbol(OutputHuffmanTable::Symbol symbol) |
401 | 0 | { |
402 | 0 | return m_bit_stream.write_bits(symbol.word, symbol.code_length); |
403 | 0 | } |
404 | | |
405 | | ErrorOr<void> append_symbol(Symbol symbol) |
406 | 0 | { |
407 | 0 | auto& stat_table = [&]() -> auto& { |
408 | 0 | if (symbol.component_id == 0 || symbol.component_id == 3) { |
409 | 0 | return symbol.is_dc ? m_symbol_stats[0] : m_symbol_stats[1]; |
410 | 0 | } |
411 | 0 | return symbol.is_dc ? m_symbol_stats[2] : m_symbol_stats[3]; |
412 | 0 | }(); |
413 | 0 | stat_table[symbol.byte] += 1; |
414 | 0 | TRY(m_symbols_and_bits.try_append(symbol)); |
415 | 0 | return {}; |
416 | 0 | } |
417 | | |
418 | | ErrorOr<void> encode_dc(i16 const component[], u8 component_id) |
419 | 0 | { |
420 | | // F.1.2.1.3 - Huffman encoding procedures for DC coefficients |
421 | 0 | auto diff = component[0] - m_last_dc_values[component_id]; |
422 | 0 | m_last_dc_values[component_id] = component[0]; |
423 | |
|
424 | 0 | auto const size = csize(diff); |
425 | 0 | TRY(append_symbol({ .byte = size, .component_id = component_id, .is_dc = true })); |
426 | |
|
427 | 0 | if (diff < 0) |
428 | 0 | diff -= 1; |
429 | |
|
430 | 0 | TRY(m_symbols_and_bits.try_append(RawBits(diff, size))); |
431 | 0 | return {}; |
432 | 0 | } |
433 | | |
434 | | ErrorOr<void> encode_ac(i16 const component[], u8 component_id) |
435 | 0 | { |
436 | | // F.2 - Procedure for sequential encoding of AC coefficients with Huffman coding |
437 | 0 | u32 k {}; |
438 | 0 | u32 r {}; |
439 | |
|
440 | 0 | while (k < 63) { |
441 | 0 | k++; |
442 | |
|
443 | 0 | auto coefficient = component[zigzag_map[k]]; |
444 | 0 | if (coefficient == 0) { |
445 | 0 | if (k == 63) { |
446 | 0 | TRY(append_symbol({ .byte = 0x00, .component_id = component_id, .is_dc = false })); |
447 | 0 | break; |
448 | 0 | } |
449 | 0 | r += 1; |
450 | 0 | continue; |
451 | 0 | } |
452 | | |
453 | 0 | while (r > 15) { |
454 | 0 | TRY(append_symbol({ .byte = 0xF0, .component_id = component_id, .is_dc = false })); |
455 | 0 | r -= 16; |
456 | 0 | } |
457 | |
|
458 | 0 | { |
459 | | // F.3 - Sequential encoding of a non-zero AC coefficient |
460 | 0 | auto const ssss = csize(coefficient); |
461 | 0 | u8 const rs = (r << 4) + ssss; |
462 | 0 | TRY(append_symbol({ .byte = rs, .component_id = component_id, .is_dc = false })); |
463 | |
|
464 | 0 | if (coefficient < 0) |
465 | 0 | coefficient -= 1; |
466 | |
|
467 | 0 | TRY(m_symbols_and_bits.try_append(RawBits(coefficient, ssss))); |
468 | 0 | } |
469 | |
|
470 | 0 | r = 0; |
471 | 0 | } |
472 | 0 | return {}; |
473 | 0 | } |
474 | | |
475 | | ErrorOr<void> write_symbols_to_stream() |
476 | 0 | { |
477 | 0 | for (auto const& symbol_or_bits : m_symbols_and_bits) { |
478 | 0 | if (symbol_or_bits.has<Symbol>()) { |
479 | 0 | auto symbol = symbol_or_bits.get<Symbol>(); |
480 | 0 | auto const& huffman_table = [&]() { |
481 | 0 | if (symbol.component_id == 0 || symbol.component_id == 3) |
482 | 0 | return symbol.is_dc ? dc_luminance_huffman_table : ac_luminance_huffman_table; |
483 | 0 | return symbol.is_dc ? dc_chrominance_huffman_table : ac_chrominance_huffman_table; |
484 | 0 | }(); |
485 | 0 | TRY(write_symbol(huffman_table.from_input_byte(symbol.byte))); |
486 | 0 | } else { |
487 | 0 | auto bits = symbol_or_bits.get<RawBits>(); |
488 | 0 | TRY(m_bit_stream.write_bits(bits.bits, bits.length)); |
489 | 0 | } |
490 | 0 | } |
491 | |
|
492 | 0 | return {}; |
493 | 0 | } |
494 | | |
495 | | static void find_smallest_frequencies(Array<u32, 257> const& frequencies, u16& v1, Optional<u16>& v2) |
496 | 0 | { |
497 | | // FIXME: A min-heap with a custom comparator should be able to do the trick. |
498 | | |
499 | | // "The procedure “Find V1 for least value of FREQ(V1) > 0” always selects the value |
500 | | // with the largest value of V1 when more than one V1 with the same frequency occurs. |
501 | | // The reserved code point is then guaranteed to be in the longest code word category." |
502 | |
|
503 | 0 | u16 index_min {}; |
504 | 0 | u16 second_index_min {}; |
505 | 0 | u32 freq_min = NumericLimits<u32>::max(); |
506 | 0 | u32 second_freq_min = NumericLimits<u32>::max(); |
507 | |
|
508 | 0 | for (auto [i, freq] : enumerate(frequencies)) { |
509 | 0 | if (freq == 0) |
510 | 0 | continue; |
511 | 0 | if (freq <= freq_min) { |
512 | 0 | second_index_min = index_min; |
513 | 0 | second_freq_min = freq_min; |
514 | 0 | index_min = i; |
515 | 0 | freq_min = freq; |
516 | 0 | } else if (freq <= second_freq_min) { |
517 | 0 | second_index_min = i; |
518 | 0 | second_freq_min = freq; |
519 | 0 | } |
520 | 0 | } |
521 | |
|
522 | 0 | v1 = index_min; |
523 | 0 | if (second_freq_min != NumericLimits<u32>::max()) |
524 | 0 | v2 = second_index_min; |
525 | 0 | else |
526 | 0 | v2.clear(); |
527 | 0 | } |
528 | | |
529 | | static Array<u8, 257> find_huffman_code_size(Array<u32, 257> frequencies) |
530 | 0 | { |
531 | | // "Before starting the procedure, the values of FREQ are collected for V = 0 to 255 |
532 | | // and the FREQ value for V = 256 is set to 1." |
533 | 0 | frequencies[256] = 1; |
534 | | |
535 | | // "the entries in CODESIZE are all set to 0" |
536 | 0 | Array<u8, 257> code_size {}; |
537 | | |
538 | | // "the indices in OTHERS are set to –1" |
539 | 0 | Array<i16, 257> others {}; |
540 | 0 | others.fill(-1); |
541 | | |
542 | | // Figure K.1 – Procedure to find Huffman code sizes |
543 | 0 | while (true) { |
544 | 0 | u16 v1 {}; |
545 | 0 | Optional<u16> maybe_v2 {}; |
546 | 0 | find_smallest_frequencies(frequencies, v1, maybe_v2); |
547 | 0 | if (!maybe_v2.has_value()) |
548 | 0 | break; |
549 | | |
550 | 0 | auto v2 = maybe_v2.value(); |
551 | |
|
552 | 0 | frequencies[v1] += frequencies[v2]; |
553 | 0 | frequencies[v2] = 0; |
554 | |
|
555 | 0 | increment_v1_code_size: |
556 | 0 | code_size[v1] += 1; |
557 | |
|
558 | 0 | if (others[v1] != -1) { |
559 | 0 | v1 = others[v1]; |
560 | 0 | goto increment_v1_code_size; |
561 | 0 | } |
562 | | |
563 | 0 | others[v1] = v2; |
564 | |
|
565 | 0 | increment_v2_code_size: |
566 | 0 | code_size[v2] += 1; |
567 | 0 | if (others[v2] != -1) { |
568 | 0 | v2 = others[v2]; |
569 | 0 | goto increment_v2_code_size; |
570 | 0 | } |
571 | 0 | } |
572 | | |
573 | 0 | return code_size; |
574 | 0 | } |
575 | | |
576 | | static void adjust_bits(Array<u8, 257>& bits) |
577 | 0 | { |
578 | | // Figure K.3 – Procedure for limiting code lengths to 16 bits |
579 | 0 | u16 i = 32; |
580 | 0 | while (true) { |
581 | 0 | if (bits[i] > 0) { |
582 | 0 | auto j = i - 1; |
583 | 0 | do { |
584 | 0 | j--; |
585 | 0 | } while (bits[j] == 0); |
586 | |
|
587 | 0 | bits[i] = bits[i] - 2; |
588 | 0 | bits[i - 1] = bits[i - 1] + 1; |
589 | 0 | bits[j + 1] = bits[j + 1] + 2; |
590 | 0 | bits[j] = bits[j] - 1; |
591 | 0 | } else { |
592 | 0 | i -= 1; |
593 | 0 | if (i != 16) |
594 | 0 | continue; |
595 | | |
596 | 0 | while (bits[i] == 0) |
597 | 0 | --i; |
598 | 0 | bits[i] -= 1; |
599 | 0 | break; |
600 | 0 | } |
601 | 0 | } |
602 | 0 | } |
603 | | |
604 | | static Array<u8, 257> count_bits(Array<u8, 257> const& code_size) |
605 | 0 | { |
606 | | // "The count for each size is contained in the list, BITS. The counts in BITS are zero |
607 | | // at the start of the procedure." |
608 | 0 | Array<u8, 257> bits {}; |
609 | | |
610 | | // Figure K.2 – Procedure to find the number of codes of each size |
611 | 0 | for (u16 i = 0; i < 257; ++i) { |
612 | 0 | if (code_size[i] == 0) |
613 | 0 | continue; |
614 | 0 | bits[code_size[i]] += 1; |
615 | 0 | } |
616 | 0 | adjust_bits(bits); |
617 | |
|
618 | 0 | return bits; |
619 | 0 | } |
620 | | |
621 | | static Vector<u8, 256> sort_input(Array<u8, 257> const& code_size) |
622 | 0 | { |
623 | | // "Figure K.4 – Sorting of input values according to code size" |
624 | 0 | Vector<u8, 256> huffval {}; |
625 | 0 | for (u8 i = 1; i <= 32; ++i) { |
626 | 0 | for (u16 j = 0; j <= 255; ++j) { |
627 | 0 | if (code_size[j] == i) |
628 | 0 | huffval.append(j); |
629 | 0 | } |
630 | 0 | } |
631 | 0 | return huffval; |
632 | 0 | } |
633 | | |
634 | | static ErrorOr<OutputHuffmanTable> compute_optimal_table(Array<u32, 257> const& distribution) |
635 | 0 | { |
636 | | // K.2 A procedure for generating the lists which specify a Huffman code table |
637 | |
|
638 | 0 | auto code_size = find_huffman_code_size(distribution); |
639 | |
|
640 | 0 | auto bits = count_bits(code_size); |
641 | | |
642 | | // "The input values are sorted according to code size" |
643 | 0 | auto huffval = sort_input(code_size); |
644 | | |
645 | | // "At this point, the list of code lengths (BITS) and the list of values |
646 | | // (HUFFVAL) can be used to generate the code tables." |
647 | |
|
648 | 0 | Vector<OutputHuffmanTable::Symbol, 16> symbols; |
649 | 0 | u16 code = 0; |
650 | 0 | u32 symbol_index = 0; |
651 | 0 | for (auto [encoded_size, number_of_codes] : enumerate(bits)) { |
652 | 0 | for (u8 i = 0; i < number_of_codes; i++) { |
653 | 0 | TRY(symbols.try_append({ .input_byte = huffval[symbol_index], .code_length = static_cast<u8>(encoded_size), .word = code })); |
654 | 0 | code++; |
655 | 0 | symbol_index++; |
656 | 0 | } |
657 | 0 | code <<= 1; |
658 | 0 | } |
659 | |
|
660 | 0 | return OutputHuffmanTable { move(symbols) }; |
661 | 0 | } |
662 | | |
663 | | ErrorOr<void> find_optimal_huffman_tables() |
664 | 0 | { |
665 | 0 | dc_luminance_huffman_table = TRY(compute_optimal_table(m_symbol_stats[0])); |
666 | 0 | dc_luminance_huffman_table.id = (0 << 4) | 0; |
667 | 0 | ac_luminance_huffman_table = TRY(compute_optimal_table(m_symbol_stats[1])); |
668 | 0 | ac_luminance_huffman_table.id = (1 << 4) | 0; |
669 | 0 | dc_chrominance_huffman_table = TRY(compute_optimal_table(m_symbol_stats[2])); |
670 | 0 | dc_chrominance_huffman_table.id = (0 << 4) | 1; |
671 | 0 | ac_chrominance_huffman_table = TRY(compute_optimal_table(m_symbol_stats[3])); |
672 | 0 | ac_chrominance_huffman_table.id = (1 << 4) | 1; |
673 | 0 | return {}; |
674 | 0 | } |
675 | | |
676 | | static u8 csize(i16 coefficient) |
677 | 0 | { |
678 | 0 | VERIFY(coefficient >= -2047 && coefficient <= 2047); |
679 | | |
680 | 0 | if (coefficient == 0) |
681 | 0 | return 0; |
682 | | |
683 | 0 | return floor(log2(abs(coefficient))) + 1; |
684 | 0 | } |
685 | | |
686 | | QuantizationTable m_luminance_quantization_table {}; |
687 | | QuantizationTable m_chrominance_quantization_table {}; |
688 | | |
689 | | Vector<FloatMacroblock> m_macroblocks {}; |
690 | | Array<i16, 4> m_last_dc_values {}; |
691 | | |
692 | | Array<Array<u32, 257>, 4> m_symbol_stats {}; |
693 | | Vector<SymbolOrRawBits> m_symbols_and_bits {}; |
694 | | |
695 | | JPEGBigEndianOutputBitStream m_bit_stream; |
696 | | }; |
697 | | |
698 | | ErrorOr<void> add_start_of_image(Stream& stream) |
699 | 0 | { |
700 | 0 | TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOI)); |
701 | 0 | return {}; |
702 | 0 | } |
703 | | |
704 | | ErrorOr<void> add_end_of_image(Stream& stream) |
705 | 0 | { |
706 | 0 | TRY(stream.write_value<BigEndian<Marker>>(JPEG_EOI)); |
707 | 0 | return {}; |
708 | 0 | } |
709 | | |
710 | | ErrorOr<void> add_icc_data(Stream& stream, ReadonlyBytes icc_data) |
711 | 0 | { |
712 | | // https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf, JFIF section |
713 | 0 | constexpr StringView icc_chunk_name = "ICC_PROFILE\0"sv; |
714 | | |
715 | | // One JPEG chunk is at most 65535 bytes long, which includes the size of the 2-byte |
716 | | // "length" field. This leaves 65533 bytes for the actual data. One ICC chunk needs |
717 | | // 12 bytes for the "ICC_PROFILE\0" app id and then one byte each for the current |
718 | | // sequence number and the number of ICC chunks. This leaves 65519 bytes for the |
719 | | // ICC data. |
720 | 0 | constexpr size_t icc_chunk_header_size = 2 + icc_chunk_name.length() + 1 + 1; |
721 | 0 | constexpr size_t max_chunk_size = 65535 - icc_chunk_header_size; |
722 | 0 | static_assert(max_chunk_size == 65519); |
723 | |
|
724 | 0 | constexpr size_t max_number_of_icc_chunks = 255; // Chunk IDs are stored in an u8 and start at 1. |
725 | 0 | constexpr size_t max_icc_data_size = max_chunk_size * max_number_of_icc_chunks; |
726 | | |
727 | | // "The 1-byte chunk count limits the size of embeddable profiles to 16 707 345 bytes."" |
728 | 0 | static_assert(max_icc_data_size == 16'707'345); |
729 | |
|
730 | 0 | if (icc_data.size() > max_icc_data_size) |
731 | 0 | return Error::from_string_view("JPEGWriter: icc data too large for jpeg format"sv); |
732 | | |
733 | 0 | size_t const number_of_icc_chunks = AK::ceil_div(icc_data.size(), max_chunk_size); |
734 | 0 | for (size_t chunk_id = 1; chunk_id <= number_of_icc_chunks; ++chunk_id) { |
735 | 0 | size_t const chunk_size = min(icc_data.size(), max_chunk_size); |
736 | |
|
737 | 0 | TRY(stream.write_value<BigEndian<Marker>>(JPEG_APPN2)); |
738 | 0 | TRY(stream.write_value<BigEndian<u16>>(icc_chunk_header_size + chunk_size)); |
739 | 0 | TRY(stream.write_until_depleted(icc_chunk_name.bytes())); |
740 | 0 | TRY(stream.write_value<u8>(chunk_id)); |
741 | 0 | TRY(stream.write_value<u8>(number_of_icc_chunks)); |
742 | 0 | TRY(stream.write_until_depleted(icc_data.slice(0, chunk_size))); |
743 | 0 | icc_data = icc_data.slice(chunk_size); |
744 | 0 | } |
745 | 0 | VERIFY(icc_data.is_empty()); |
746 | 0 | return {}; |
747 | 0 | } |
748 | | |
749 | | ErrorOr<void> add_frame_header(Stream& stream, JPEGEncodingContext const& context, IntSize size, Mode mode) |
750 | 0 | { |
751 | | // B.2.2 - Frame header syntax |
752 | 0 | TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOF0)); |
753 | |
|
754 | 0 | u16 const Nf = mode == Mode::CMYK ? 4 : 3; |
755 | | |
756 | | // Lf = 8 + 3 × Nf |
757 | 0 | TRY(stream.write_value<BigEndian<u16>>(8 + 3 * Nf)); |
758 | | |
759 | | // P |
760 | 0 | TRY(stream.write_value<u8>(8)); |
761 | | |
762 | | // Y |
763 | 0 | TRY(stream.write_value<BigEndian<u16>>(size.height())); |
764 | | |
765 | | // X |
766 | 0 | TRY(stream.write_value<BigEndian<u16>>(size.width())); |
767 | | |
768 | | // Nf |
769 | 0 | TRY(stream.write_value<u8>(Nf)); |
770 | | |
771 | | // Encode Nf components |
772 | 0 | for (u8 i {}; i < Nf; ++i) { |
773 | | // Ci |
774 | 0 | TRY(stream.write_value<u8>(i + 1)); |
775 | | |
776 | | // Hi and Vi |
777 | 0 | TRY(stream.write_value<u8>((1 << 4) | 1)); |
778 | | |
779 | | // Tqi |
780 | 0 | TRY(stream.write_value<u8>((i == 0 || i == 3 ? context.luminance_quantization_table() : context.chrominance_quantization_table()).id)); |
781 | 0 | } |
782 | |
|
783 | 0 | return {}; |
784 | 0 | } |
785 | | |
786 | | ErrorOr<void> add_ycck_color_transform_header(Stream& stream) |
787 | 0 | { |
788 | | // T-REC-T.872-201206-I!!PDF-E.pdf, 6.5.3 APP14 marker segment for colour encoding |
789 | 0 | TRY(stream.write_value<BigEndian<Marker>>(JPEG_APPN14)); |
790 | 0 | TRY(stream.write_value<BigEndian<u16>>(14)); |
791 | |
|
792 | 0 | TRY(stream.write_until_depleted("Adobe\0"sv.bytes())); |
793 | | |
794 | | // These values are ignored. |
795 | 0 | TRY(stream.write_value<u8>(0x64)); |
796 | 0 | TRY(stream.write_value<BigEndian<u16>>(0x0000)); |
797 | 0 | TRY(stream.write_value<BigEndian<u16>>(0x0000)); |
798 | | |
799 | | // YCCK |
800 | 0 | TRY(stream.write_value<u8>(0x2)); |
801 | 0 | return {}; |
802 | 0 | } |
803 | | |
804 | | ErrorOr<void> add_quantization_table(Stream& stream, QuantizationTable const& table) |
805 | 0 | { |
806 | | // B.2.4.1 - Quantization table-specification syntax |
807 | 0 | TRY(stream.write_value<BigEndian<Marker>>(JPEG_DQT)); |
808 | | |
809 | | // Lq = 2 + 1 * 65 |
810 | 0 | TRY(stream.write_value<BigEndian<u16>>(2 + 65)); |
811 | | |
812 | | // Pq and Tq |
813 | 0 | TRY(stream.write_value<u8>((0 << 4) | table.id)); |
814 | |
|
815 | 0 | for (u8 i = 0; i < 64; ++i) |
816 | 0 | TRY(stream.write_value<u8>(table.table[zigzag_map[i]])); |
817 | |
|
818 | 0 | return {}; |
819 | 0 | } |
820 | | |
821 | | ErrorOr<Vector<Vector<u8>, 16>> sort_symbols_per_size(OutputHuffmanTable const& table) |
822 | 0 | { |
823 | | // JPEG only allows symbol with a size less than or equal to 16. |
824 | 0 | Vector<Vector<u8>, 16> output {}; |
825 | 0 | TRY(output.try_resize(16)); |
826 | |
|
827 | 0 | for (auto const& symbol : table.table) |
828 | 0 | TRY(output[symbol.code_length - 1].try_append(symbol.input_byte)); |
829 | |
|
830 | 0 | return output; |
831 | 0 | } |
832 | | |
833 | | ErrorOr<void> add_huffman_table(Stream& stream, OutputHuffmanTable const& table) |
834 | 0 | { |
835 | | // B.2.4.2 - Huffman table-specification syntax |
836 | 0 | TRY(stream.write_value<BigEndian<Marker>>(JPEG_DHT)); |
837 | | |
838 | | // Lh |
839 | 0 | TRY(stream.write_value<BigEndian<u16>>(2 + 17 + table.table.size())); |
840 | | |
841 | | // Tc and Th |
842 | 0 | TRY(stream.write_value<u8>(table.id)); |
843 | |
|
844 | 0 | auto const vectorized_table = TRY(sort_symbols_per_size(table)); |
845 | 0 | for (auto const& symbol_vector : vectorized_table) |
846 | 0 | TRY(stream.write_value<u8>(symbol_vector.size())); |
847 | |
|
848 | 0 | for (auto const& symbol_vector : vectorized_table) { |
849 | 0 | for (auto symbol : symbol_vector) |
850 | 0 | TRY(stream.write_value<u8>(symbol)); |
851 | 0 | } |
852 | |
|
853 | 0 | return {}; |
854 | 0 | } |
855 | | |
856 | | ErrorOr<void> add_scan_header(Stream& stream, Mode mode) |
857 | 0 | { |
858 | | // B.2.3 - Scan header syntax |
859 | 0 | TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOS)); |
860 | |
|
861 | 0 | u16 const Ns = mode == Mode::CMYK ? 4 : 3; |
862 | | |
863 | | // Ls - 6 + 2 × Ns |
864 | 0 | TRY(stream.write_value<BigEndian<u16>>(6 + 2 * Ns)); |
865 | | |
866 | | // Ns |
867 | 0 | TRY(stream.write_value<u8>(Ns)); |
868 | | |
869 | | // Encode Ns components |
870 | 0 | for (u8 i {}; i < Ns; ++i) { |
871 | | // Csj |
872 | 0 | TRY(stream.write_value<u8>(i + 1)); |
873 | | |
874 | | // Tdj and Taj |
875 | | // We're using 0 for luminance and 1 for chrominance |
876 | 0 | u8 const huffman_identifier = i == 0 || i == 3 ? 0 : 1; |
877 | 0 | TRY(stream.write_value<u8>((huffman_identifier << 4) | huffman_identifier)); |
878 | 0 | } |
879 | | |
880 | | // Ss |
881 | 0 | TRY(stream.write_value<u8>(0)); |
882 | | |
883 | | // Se |
884 | 0 | TRY(stream.write_value<u8>(63)); |
885 | | |
886 | | // Ah and Al |
887 | 0 | TRY(stream.write_value<u8>((0 << 4) | 0)); |
888 | |
|
889 | 0 | return {}; |
890 | 0 | } |
891 | | |
892 | | ErrorOr<void> add_headers(Stream& stream, JPEGEncodingContext const& context, JPEGWriter::Options const& options, IntSize size, Mode mode) |
893 | 0 | { |
894 | 0 | TRY(add_start_of_image(stream)); |
895 | |
|
896 | 0 | if (options.icc_data.has_value()) |
897 | 0 | TRY(add_icc_data(stream, options.icc_data.value())); |
898 | |
|
899 | 0 | if (mode == Mode::CMYK) |
900 | 0 | TRY(add_ycck_color_transform_header(stream)); |
901 | 0 | TRY(add_frame_header(stream, context, size, mode)); |
902 | |
|
903 | 0 | TRY(add_quantization_table(stream, context.luminance_quantization_table())); |
904 | 0 | TRY(add_quantization_table(stream, context.chrominance_quantization_table())); |
905 | |
|
906 | 0 | TRY(add_huffman_table(stream, context.dc_luminance_huffman_table)); |
907 | 0 | TRY(add_huffman_table(stream, context.dc_chrominance_huffman_table)); |
908 | 0 | TRY(add_huffman_table(stream, context.ac_luminance_huffman_table)); |
909 | 0 | TRY(add_huffman_table(stream, context.ac_chrominance_huffman_table)); |
910 | |
|
911 | 0 | TRY(add_scan_header(stream, mode)); |
912 | 0 | return {}; |
913 | 0 | } |
914 | | |
915 | | ErrorOr<void> add_image(Stream& stream, JPEGEncodingContext& context, JPEGEncoderOptions const& options, IntSize size, Mode mode) |
916 | 0 | { |
917 | 0 | if (options.use_deringing == JPEGEncoderOptions::UseDeringing::Yes) |
918 | 0 | context.apply_deringing(); |
919 | 0 | context.set_quantization_tables(options.quality); |
920 | 0 | context.convert_to_ycbcr(mode); |
921 | 0 | context.fdct_and_quantization(mode); |
922 | 0 | TRY(context.huffman_encode_macroblocks(mode)); |
923 | 0 | TRY(add_headers(stream, context, options, size, mode)); |
924 | 0 | TRY(context.write_huffman_stream()); |
925 | 0 | TRY(add_end_of_image(stream)); |
926 | 0 | return {}; |
927 | 0 | } |
928 | | |
929 | | } |
930 | | |
931 | | ErrorOr<void> JPEGWriter::encode(Stream& stream, Bitmap const& bitmap, Options const& options) |
932 | 0 | { |
933 | 0 | JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } }; |
934 | 0 | TRY(context.initialize_mcu(bitmap)); |
935 | 0 | TRY(add_image(stream, context, options, bitmap.size(), Mode::RGB)); |
936 | 0 | return {}; |
937 | 0 | } |
938 | | |
939 | | ErrorOr<void> JPEGWriter::encode(Stream& stream, CMYKBitmap const& bitmap, Options const& options) |
940 | 0 | { |
941 | 0 | JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } }; |
942 | 0 | TRY(context.initialize_mcu(bitmap)); |
943 | 0 | TRY(add_image(stream, context, options, bitmap.size(), Mode::CMYK)); |
944 | 0 | return {}; |
945 | 0 | } |
946 | | |
947 | | } |