Coverage Report

Created: 2026-05-16 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}