Coverage Report

Created: 2025-09-05 06:52

/src/serenity/Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.cpp
Line
Count
Source (jump to first uncovered line)
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/Function.h>
13
#include <LibGfx/Bitmap.h>
14
#include <LibGfx/CMYKBitmap.h>
15
16
namespace Gfx {
17
18
namespace {
19
20
enum Mode {
21
    RGB,
22
    CMYK,
23
};
24
25
// This is basically a BigEndianOutputBitStream, the only difference
26
// is that it appends 0x00 after each 0xFF when it writes bits.
27
class JPEGBigEndianOutputBitStream : public Stream {
28
public:
29
    explicit JPEGBigEndianOutputBitStream(Stream& stream)
30
0
        : m_stream(stream)
31
0
    {
32
0
    }
33
34
    virtual ErrorOr<Bytes> read_some(Bytes) override
35
0
    {
36
0
        return Error::from_errno(EBADF);
37
0
    }
38
39
    virtual ErrorOr<size_t> write_some(ReadonlyBytes bytes) override
40
0
    {
41
0
        VERIFY(m_bit_offset == 0);
42
0
        return m_stream.write_some(bytes);
43
0
    }
44
45
    template<Unsigned T>
46
    ErrorOr<void> write_bits(T value, size_t bit_count)
47
0
    {
48
0
        VERIFY(m_bit_offset <= 7);
49
50
0
        while (bit_count > 0) {
51
0
            u8 const next_bit = (value >> (bit_count - 1)) & 1;
52
0
            bit_count--;
53
54
0
            m_current_byte <<= 1;
55
0
            m_current_byte |= next_bit;
56
0
            m_bit_offset++;
57
58
0
            if (m_bit_offset > 7) {
59
0
                TRY(m_stream.write_value(m_current_byte));
60
0
                if (m_current_byte == 0xFF)
61
0
                    TRY(m_stream.write_value<u8>(0));
62
63
0
                m_bit_offset = 0;
64
0
                m_current_byte = 0;
65
0
            }
66
0
        }
67
68
0
        return {};
69
0
    }
Unexecuted instantiation: JPEGWriter.cpp:_ZN3Gfx12_GLOBAL__N_128JPEGBigEndianOutputBitStream10write_bitsITkN2AK8Concepts8UnsignedEtEENS3_7ErrorOrIvNS3_5ErrorEEET_m
Unexecuted instantiation: JPEGWriter.cpp:_ZN3Gfx12_GLOBAL__N_128JPEGBigEndianOutputBitStream10write_bitsITkN2AK8Concepts8UnsignedEhEENS3_7ErrorOrIvNS3_5ErrorEEET_m
70
71
    virtual bool is_eof() const override
72
0
    {
73
0
        return true;
74
0
    }
75
76
    virtual bool is_open() const override
77
0
    {
78
0
        return m_stream.is_open();
79
0
    }
80
81
    virtual void close() override
82
0
    {
83
0
    }
84
85
    ErrorOr<void> align_to_byte_boundary(u8 filler = 0x0)
86
0
    {
87
0
        if (m_bit_offset == 0)
88
0
            return {};
89
90
0
        TRY(write_bits(filler, 8 - m_bit_offset));
91
0
        VERIFY(m_bit_offset == 0);
92
0
        return {};
93
0
    }
94
95
private:
96
    Stream& m_stream;
97
    u8 m_current_byte { 0 };
98
    size_t m_bit_offset { 0 };
99
};
100
101
class JPEGEncodingContext {
102
public:
103
    JPEGEncodingContext(JPEGBigEndianOutputBitStream output_stream)
104
0
        : m_bit_stream(move(output_stream))
105
0
    {
106
0
    }
107
108
    ErrorOr<void> initialize_mcu(Bitmap const& bitmap)
109
0
    {
110
0
        u64 const horizontal_macroblocks = ceil_div(bitmap.width(), 8);
111
0
        u64 const vertical_macroblocks = ceil_div(bitmap.height(), 8);
112
0
        TRY(m_macroblocks.try_resize(horizontal_macroblocks * vertical_macroblocks));
113
114
0
        for (u16 y {}; y < bitmap.height(); ++y) {
115
0
            u16 const vertical_macroblock_index = y / 8;
116
0
            u16 const vertical_pixel_offset = y - vertical_macroblock_index * 8;
117
118
0
            for (u16 x {}; x < bitmap.width(); ++x) {
119
0
                u16 const horizontal_macroblock_index = x / 8;
120
0
                u16 const horizontal_pixel_offset = x - horizontal_macroblock_index * 8;
121
122
0
                auto& macroblock = m_macroblocks[vertical_macroblock_index * horizontal_macroblocks + horizontal_macroblock_index];
123
0
                auto const pixel_offset = vertical_pixel_offset * 8 + horizontal_pixel_offset;
124
125
0
                auto const original_pixel = bitmap.get_pixel(x, y);
126
127
                // Conversion from YCbCr to RGB isn't specified in the first JPEG specification but in the JFIF extension:
128
                // See: https://www.itu.int/rec/dologin_pub.asp?lang=f&id=T-REC-T.871-201105-I!!PDF-E&type=items
129
                // 7 - Conversion to and from RGB
130
0
                auto const y_ = clamp(0.299 * original_pixel.red() + 0.587 * original_pixel.green() + 0.114 * original_pixel.blue(), 0, 255);
131
0
                auto const cb = clamp(-0.1687 * original_pixel.red() - 0.3313 * original_pixel.green() + 0.5 * original_pixel.blue() + 128, 0, 255);
132
0
                auto const cr = clamp(0.5 * original_pixel.red() - 0.4187 * original_pixel.green() - 0.0813 * original_pixel.blue() + 128, 0, 255);
133
134
                // A.3.1 - Level shift
135
0
                macroblock.r[pixel_offset] = y_ - 128;
136
0
                macroblock.g[pixel_offset] = cb - 128;
137
0
                macroblock.b[pixel_offset] = cr - 128;
138
0
            }
139
0
        }
140
141
0
        return {};
142
0
    }
143
144
    ErrorOr<void> initialize_mcu(CMYKBitmap const& bitmap)
145
0
    {
146
0
        u64 const horizontal_macroblocks = ceil_div(bitmap.size().width(), 8);
147
0
        u64 const vertical_macroblocks = ceil_div(bitmap.size().height(), 8);
148
0
        TRY(m_macroblocks.try_resize(horizontal_macroblocks * vertical_macroblocks));
149
150
0
        for (u16 y {}; y < bitmap.size().height(); ++y) {
151
0
            u16 const vertical_macroblock_index = y / 8;
152
0
            u16 const vertical_pixel_offset = y - vertical_macroblock_index * 8;
153
154
0
            for (u16 x {}; x < bitmap.size().width(); ++x) {
155
0
                u16 const horizontal_macroblock_index = x / 8;
156
0
                u16 const horizontal_pixel_offset = x - horizontal_macroblock_index * 8;
157
158
0
                auto& macroblock = m_macroblocks[vertical_macroblock_index * horizontal_macroblocks + horizontal_macroblock_index];
159
0
                auto const pixel_offset = vertical_pixel_offset * 8 + horizontal_pixel_offset;
160
161
0
                auto const original_pixel = bitmap.scanline(y)[x];
162
163
                // To get YCCK, the CMY part is converted to RGB (ignoring the K component), and then the RGB is converted to YCbCr.
164
                // r is `255 - c` (and similar for g/m b/y), but with the Adobe YCCK color transform marker, the CMY
165
                // channels are stored inverted, which cancels out: 255 - (255 - x) == x.
166
                // K is stored as-is (meaning it's inverted once for the color transform).
167
0
                u8 r = original_pixel.c;
168
0
                u8 g = original_pixel.m;
169
0
                u8 b = original_pixel.y;
170
0
                u8 k = 255 - original_pixel.k;
171
172
                // See: https://www.itu.int/rec/dologin_pub.asp?lang=f&id=T-REC-T.871-201105-I!!PDF-E&type=items
173
                // 7 - Conversion to and from RGB
174
0
                auto const y_ = clamp(0.299 * r + 0.587 * g + 0.114 * b, 0, 255);
175
0
                auto const cb = clamp(-0.1687 * r - 0.3313 * g + 0.5 * b + 128, 0, 255);
176
0
                auto const cr = clamp(0.5 * r - 0.4187 * g - 0.0813 * b + 128, 0, 255);
177
178
                // A.3.1 - Level shift
179
0
                macroblock.r[pixel_offset] = y_ - 128;
180
0
                macroblock.g[pixel_offset] = cb - 128;
181
0
                macroblock.b[pixel_offset] = cr - 128;
182
0
                macroblock.k[pixel_offset] = k - 128;
183
0
            }
184
0
        }
185
186
0
        return {};
187
0
    }
188
189
    static Array<double, 64> create_cosine_lookup_table()
190
0
    {
191
0
        static constexpr double pi_over_16 = AK::Pi<double> / 16;
192
193
0
        Array<double, 64> table;
194
195
0
        for (u8 u = 0; u < 8; ++u) {
196
0
            for (u8 x = 0; x < 8; ++x)
197
0
                table[u * 8 + x] = cos((2 * x + 1) * u * pi_over_16);
198
0
        }
199
200
0
        return table;
201
0
    }
202
203
    void fdct_and_quantization(Mode mode)
204
0
    {
205
0
        static auto cosine_table = create_cosine_lookup_table();
206
207
0
        for (auto& macroblock : m_macroblocks) {
208
0
            constexpr double inverse_sqrt_2 = M_SQRT1_2;
209
210
0
            auto const convert_one_component = [&](i16 component[], QuantizationTable const& table) {
211
0
                Array<i16, 64> result {};
212
213
0
                auto const sum_xy = [&](u8 u, u8 v) {
214
0
                    double sum {};
215
0
                    for (u8 y {}; y < 8; ++y) {
216
0
                        for (u8 x {}; x < 8; ++x)
217
0
                            sum += component[y * 8 + x] * cosine_table[u * 8 + x] * cosine_table[v * 8 + y];
218
0
                    }
219
0
                    return sum;
220
0
                };
221
222
0
                for (u8 v {}; v < 8; ++v) {
223
0
                    double const cv = v == 0 ? inverse_sqrt_2 : 1;
224
0
                    for (u8 u {}; u < 8; ++u) {
225
0
                        auto const table_index = v * 8 + u;
226
227
0
                        double const cu = u == 0 ? inverse_sqrt_2 : 1;
228
229
                        // A.3.3 - FDCT and IDCT
230
0
                        double const fdct = cu * cv * sum_xy(u, v) / 4;
231
232
                        // A.3.4 - DCT coefficient quantization
233
0
                        i16 const quantized = round(fdct / table.table[table_index]);
234
235
0
                        result[table_index] = quantized;
236
0
                    }
237
0
                }
238
239
0
                for (u8 i {}; i < result.size(); ++i)
240
0
                    component[i] = result[i];
241
0
            };
242
243
0
            convert_one_component(macroblock.y, m_luminance_quantization_table);
244
0
            convert_one_component(macroblock.cb, m_chrominance_quantization_table);
245
0
            convert_one_component(macroblock.cr, m_chrominance_quantization_table);
246
0
            if (mode == Mode::CMYK)
247
0
                convert_one_component(macroblock.k, m_luminance_quantization_table);
248
0
        }
249
0
    }
250
251
    ErrorOr<void> write_huffman_stream(Mode mode)
252
0
    {
253
0
        for (auto& macroblock : m_macroblocks) {
254
0
            TRY(encode_dc(dc_luminance_huffman_table, macroblock.y, 0));
255
0
            TRY(encode_ac(ac_luminance_huffman_table, macroblock.y));
256
257
0
            TRY(encode_dc(dc_chrominance_huffman_table, macroblock.cb, 1));
258
0
            TRY(encode_ac(ac_chrominance_huffman_table, macroblock.cb));
259
260
0
            TRY(encode_dc(dc_chrominance_huffman_table, macroblock.cr, 2));
261
0
            TRY(encode_ac(ac_chrominance_huffman_table, macroblock.cr));
262
263
0
            if (mode == Mode::CMYK) {
264
0
                TRY(encode_dc(dc_luminance_huffman_table, macroblock.k, 3));
265
0
                TRY(encode_ac(ac_luminance_huffman_table, macroblock.k));
266
0
            }
267
0
        }
268
269
0
        TRY(m_bit_stream.align_to_byte_boundary(0xFF));
270
271
0
        return {};
272
0
    }
273
274
    void set_luminance_quantization_table(QuantizationTable const& table, int quality)
275
0
    {
276
0
        set_quantization_table(m_luminance_quantization_table, table, quality);
277
0
    }
278
279
    void set_chrominance_quantization_table(QuantizationTable const& table, int quality)
280
0
    {
281
0
        set_quantization_table(m_chrominance_quantization_table, table, quality);
282
0
    }
283
284
    QuantizationTable const& luminance_quantization_table() const
285
0
    {
286
0
        return m_luminance_quantization_table;
287
0
    }
288
289
    QuantizationTable const& chrominance_quantization_table() const
290
0
    {
291
0
        return m_chrominance_quantization_table;
292
0
    }
293
294
    OutputHuffmanTable dc_luminance_huffman_table;
295
    OutputHuffmanTable dc_chrominance_huffman_table;
296
297
    OutputHuffmanTable ac_luminance_huffman_table;
298
    OutputHuffmanTable ac_chrominance_huffman_table;
299
300
private:
301
    static void set_quantization_table(QuantizationTable& destination, QuantizationTable const& source, int quality)
302
0
    {
303
        // In order to be compatible with libjpeg-turbo, we use the same coefficients as them.
304
305
0
        quality = clamp(quality, 1, 100);
306
307
0
        if (quality < 50)
308
0
            quality = 5000 / quality;
309
0
        else
310
0
            quality = 200 - quality * 2;
311
312
0
        destination = source;
313
0
        for (u8 i {}; i < 64; ++i) {
314
0
            auto const shifted_value = (destination.table[i] * quality + 50) / 100;
315
0
            destination.table[i] = clamp(shifted_value, 1, 255);
316
0
        }
317
0
    }
318
319
    ErrorOr<void> write_symbol(OutputHuffmanTable::Symbol symbol)
320
0
    {
321
0
        return m_bit_stream.write_bits(symbol.word, symbol.code_length);
322
0
    }
323
324
    ErrorOr<void> encode_dc(OutputHuffmanTable const& dc_table, i16 const component[], u8 component_id)
325
0
    {
326
        // F.1.2.1.3 - Huffman encoding procedures for DC coefficients
327
0
        auto diff = component[0] - m_last_dc_values[component_id];
328
0
        m_last_dc_values[component_id] = component[0];
329
330
0
        auto const size = csize(diff);
331
0
        TRY(write_symbol(dc_table.from_input_byte(size)));
332
333
0
        if (diff < 0)
334
0
            diff -= 1;
335
336
0
        TRY(m_bit_stream.write_bits<u16>(diff, size));
337
0
        return {};
338
0
    }
339
340
    ErrorOr<void> encode_ac(OutputHuffmanTable const& ac_table, i16 const component[])
341
0
    {
342
0
        {
343
            // F.2 - Procedure for sequential encoding of AC coefficients with Huffman coding
344
0
            u32 k {};
345
0
            u32 r {};
346
347
0
            while (k < 63) {
348
0
                k++;
349
350
0
                auto coefficient = component[zigzag_map[k]];
351
0
                if (coefficient == 0) {
352
0
                    if (k == 63) {
353
0
                        TRY(write_symbol(ac_table.from_input_byte(0x00)));
354
0
                        break;
355
0
                    }
356
0
                    r += 1;
357
0
                    continue;
358
0
                }
359
360
0
                while (r > 15) {
361
0
                    TRY(write_symbol(ac_table.from_input_byte(0xF0)));
362
0
                    r -= 16;
363
0
                }
364
365
0
                {
366
                    // F.3 - Sequential encoding of a non-zero AC coefficient
367
0
                    auto const ssss = csize(coefficient);
368
0
                    auto const rs = (r << 4) + ssss;
369
0
                    TRY(write_symbol(ac_table.from_input_byte(rs)));
370
371
0
                    if (coefficient < 0)
372
0
                        coefficient -= 1;
373
374
0
                    TRY(m_bit_stream.write_bits<u16>(coefficient, ssss));
375
0
                }
376
377
0
                r = 0;
378
0
            }
379
0
        }
380
0
        return {};
381
0
    }
382
383
    static u8 csize(i16 coefficient)
384
0
    {
385
0
        VERIFY(coefficient >= -2047 && coefficient <= 2047);
386
387
0
        if (coefficient == 0)
388
0
            return 0;
389
390
0
        return floor(log2(abs(coefficient))) + 1;
391
0
    }
392
393
    QuantizationTable m_luminance_quantization_table {};
394
    QuantizationTable m_chrominance_quantization_table {};
395
396
    Vector<Macroblock> m_macroblocks {};
397
    Array<i16, 4> m_last_dc_values {};
398
399
    JPEGBigEndianOutputBitStream m_bit_stream;
400
};
401
402
ErrorOr<void> add_start_of_image(Stream& stream)
403
0
{
404
0
    TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOI));
405
0
    return {};
406
0
}
407
408
ErrorOr<void> add_end_of_image(Stream& stream)
409
0
{
410
0
    TRY(stream.write_value<BigEndian<Marker>>(JPEG_EOI));
411
0
    return {};
412
0
}
413
414
ErrorOr<void> add_icc_data(Stream& stream, ReadonlyBytes icc_data)
415
0
{
416
    // https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf, JFIF section
417
0
    constexpr StringView icc_chunk_name = "ICC_PROFILE\0"sv;
418
419
    // One JPEG chunk is at most 65535 bytes long, which includes the size of the 2-byte
420
    // "length" field. This leaves 65533 bytes for the actual data. One ICC chunk needs
421
    // 12 bytes for the "ICC_PROFILE\0" app id and then one byte each for the current
422
    // sequence number and the number of ICC chunks. This leaves 65519 bytes for the
423
    // ICC data.
424
0
    constexpr size_t icc_chunk_header_size = 2 + icc_chunk_name.length() + 1 + 1;
425
0
    constexpr size_t max_chunk_size = 65535 - icc_chunk_header_size;
426
0
    static_assert(max_chunk_size == 65519);
427
428
0
    constexpr size_t max_number_of_icc_chunks = 255; // Chunk IDs are stored in an u8 and start at 1.
429
0
    constexpr size_t max_icc_data_size = max_chunk_size * max_number_of_icc_chunks;
430
431
    // "The 1-byte chunk count limits the size of embeddable profiles to 16 707 345 bytes.""
432
0
    static_assert(max_icc_data_size == 16'707'345);
433
434
0
    if (icc_data.size() > max_icc_data_size)
435
0
        return Error::from_string_view("JPEGWriter: icc data too large for jpeg format"sv);
436
437
0
    size_t const number_of_icc_chunks = AK::ceil_div(icc_data.size(), max_chunk_size);
438
0
    for (size_t chunk_id = 1; chunk_id <= number_of_icc_chunks; ++chunk_id) {
439
0
        size_t const chunk_size = min(icc_data.size(), max_chunk_size);
440
441
0
        TRY(stream.write_value<BigEndian<Marker>>(JPEG_APPN2));
442
0
        TRY(stream.write_value<BigEndian<u16>>(icc_chunk_header_size + chunk_size));
443
0
        TRY(stream.write_until_depleted(icc_chunk_name.bytes()));
444
0
        TRY(stream.write_value<u8>(chunk_id));
445
0
        TRY(stream.write_value<u8>(number_of_icc_chunks));
446
0
        TRY(stream.write_until_depleted(icc_data.slice(0, chunk_size)));
447
0
        icc_data = icc_data.slice(chunk_size);
448
0
    }
449
0
    VERIFY(icc_data.is_empty());
450
0
    return {};
451
0
}
452
453
ErrorOr<void> add_frame_header(Stream& stream, JPEGEncodingContext const& context, IntSize size, Mode mode)
454
0
{
455
    // B.2.2 - Frame header syntax
456
0
    TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOF0));
457
458
0
    u16 const Nf = mode == Mode::CMYK ? 4 : 3;
459
460
    // Lf = 8 + 3 × Nf
461
0
    TRY(stream.write_value<BigEndian<u16>>(8 + 3 * Nf));
462
463
    // P
464
0
    TRY(stream.write_value<u8>(8));
465
466
    // Y
467
0
    TRY(stream.write_value<BigEndian<u16>>(size.height()));
468
469
    // X
470
0
    TRY(stream.write_value<BigEndian<u16>>(size.width()));
471
472
    // Nf
473
0
    TRY(stream.write_value<u8>(Nf));
474
475
    // Encode Nf components
476
0
    for (u8 i {}; i < Nf; ++i) {
477
        // Ci
478
0
        TRY(stream.write_value<u8>(i + 1));
479
480
        // Hi and Vi
481
0
        TRY(stream.write_value<u8>((1 << 4) | 1));
482
483
        // Tqi
484
0
        TRY(stream.write_value<u8>((i == 0 || i == 3 ? context.luminance_quantization_table() : context.chrominance_quantization_table()).id));
485
0
    }
486
487
0
    return {};
488
0
}
489
490
ErrorOr<void> add_ycck_color_transform_header(Stream& stream)
491
0
{
492
    // T-REC-T.872-201206-I!!PDF-E.pdf, 6.5.3 APP14 marker segment for colour encoding
493
0
    TRY(stream.write_value<BigEndian<Marker>>(JPEG_APPN14));
494
0
    TRY(stream.write_value<BigEndian<u16>>(14));
495
496
0
    TRY(stream.write_until_depleted("Adobe\0"sv.bytes()));
497
498
    // These values are ignored.
499
0
    TRY(stream.write_value<u8>(0x64));
500
0
    TRY(stream.write_value<BigEndian<u16>>(0x0000));
501
0
    TRY(stream.write_value<BigEndian<u16>>(0x0000));
502
503
    // YCCK
504
0
    TRY(stream.write_value<u8>(0x2));
505
0
    return {};
506
0
}
507
508
ErrorOr<void> add_quantization_table(Stream& stream, QuantizationTable const& table)
509
0
{
510
    // B.2.4.1 - Quantization table-specification syntax
511
0
    TRY(stream.write_value<BigEndian<Marker>>(JPEG_DQT));
512
513
    // Lq = 2 + 1 * 65
514
0
    TRY(stream.write_value<BigEndian<u16>>(2 + 65));
515
516
    // Pq and Tq
517
0
    TRY(stream.write_value<u8>((0 << 4) | table.id));
518
519
0
    for (u8 i = 0; i < 64; ++i)
520
0
        TRY(stream.write_value<u8>(table.table[zigzag_map[i]]));
521
522
0
    return {};
523
0
}
524
525
ErrorOr<Vector<Vector<u8>, 16>> sort_symbols_per_size(OutputHuffmanTable const& table)
526
0
{
527
    // JPEG only allows symbol with a size less than or equal to 16.
528
0
    Vector<Vector<u8>, 16> output {};
529
0
    TRY(output.try_resize(16));
530
531
0
    for (auto const& symbol : table.table)
532
0
        TRY(output[symbol.code_length - 1].try_append(symbol.input_byte));
533
534
0
    return output;
535
0
}
536
537
ErrorOr<void> add_huffman_table(Stream& stream, OutputHuffmanTable const& table)
538
0
{
539
    // B.2.4.2 - Huffman table-specification syntax
540
0
    TRY(stream.write_value<BigEndian<Marker>>(JPEG_DHT));
541
542
    // Lh
543
0
    TRY(stream.write_value<BigEndian<u16>>(2 + 17 + table.table.size()));
544
545
    // Tc and Th
546
0
    TRY(stream.write_value<u8>(table.id));
547
548
0
    auto const vectorized_table = TRY(sort_symbols_per_size(table));
549
0
    for (auto const& symbol_vector : vectorized_table)
550
0
        TRY(stream.write_value<u8>(symbol_vector.size()));
551
552
0
    for (auto const& symbol_vector : vectorized_table) {
553
0
        for (auto symbol : symbol_vector)
554
0
            TRY(stream.write_value<u8>(symbol));
555
0
    }
556
557
0
    return {};
558
0
}
559
560
ErrorOr<void> add_scan_header(Stream& stream, Mode mode)
561
0
{
562
    // B.2.3 - Scan header syntax
563
0
    TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOS));
564
565
0
    u16 const Ns = mode == Mode::CMYK ? 4 : 3;
566
567
    // Ls - 6 + 2 × Ns
568
0
    TRY(stream.write_value<BigEndian<u16>>(6 + 2 * Ns));
569
570
    // Ns
571
0
    TRY(stream.write_value<u8>(Ns));
572
573
    // Encode Ns components
574
0
    for (u8 i {}; i < Ns; ++i) {
575
        // Csj
576
0
        TRY(stream.write_value<u8>(i + 1));
577
578
        // Tdj and Taj
579
        // We're using 0 for luminance and 1 for chrominance
580
0
        u8 const huffman_identifier = i == 0 || i == 3 ? 0 : 1;
581
0
        TRY(stream.write_value<u8>((huffman_identifier << 4) | huffman_identifier));
582
0
    }
583
584
    // Ss
585
0
    TRY(stream.write_value<u8>(0));
586
587
    // Se
588
0
    TRY(stream.write_value<u8>(63));
589
590
    // Ah and Al
591
0
    TRY(stream.write_value<u8>((0 << 4) | 0));
592
593
0
    return {};
594
0
}
595
596
ErrorOr<void> add_headers(Stream& stream, JPEGEncodingContext& context, JPEGWriter::Options const& options, IntSize size, Mode mode)
597
0
{
598
0
    context.set_luminance_quantization_table(s_default_luminance_quantization_table, options.quality);
599
0
    context.set_chrominance_quantization_table(s_default_chrominance_quantization_table, options.quality);
600
601
0
    context.dc_luminance_huffman_table = s_default_dc_luminance_huffman_table;
602
0
    context.dc_chrominance_huffman_table = s_default_dc_chrominance_huffman_table;
603
604
0
    context.ac_luminance_huffman_table = s_default_ac_luminance_huffman_table;
605
0
    context.ac_chrominance_huffman_table = s_default_ac_chrominance_huffman_table;
606
607
0
    TRY(add_start_of_image(stream));
608
609
0
    if (options.icc_data.has_value())
610
0
        TRY(add_icc_data(stream, options.icc_data.value()));
611
612
0
    if (mode == Mode::CMYK)
613
0
        TRY(add_ycck_color_transform_header(stream));
614
0
    TRY(add_frame_header(stream, context, size, mode));
615
616
0
    TRY(add_quantization_table(stream, context.luminance_quantization_table()));
617
0
    TRY(add_quantization_table(stream, context.chrominance_quantization_table()));
618
619
0
    TRY(add_huffman_table(stream, context.dc_luminance_huffman_table));
620
0
    TRY(add_huffman_table(stream, context.dc_chrominance_huffman_table));
621
0
    TRY(add_huffman_table(stream, context.ac_luminance_huffman_table));
622
0
    TRY(add_huffman_table(stream, context.ac_chrominance_huffman_table));
623
624
0
    TRY(add_scan_header(stream, mode));
625
0
    return {};
626
0
}
627
628
ErrorOr<void> add_image(Stream& stream, JPEGEncodingContext& context, Mode mode)
629
0
{
630
0
    context.fdct_and_quantization(mode);
631
0
    TRY(context.write_huffman_stream(mode));
632
0
    TRY(add_end_of_image(stream));
633
0
    return {};
634
0
}
635
636
}
637
638
ErrorOr<void> JPEGWriter::encode(Stream& stream, Bitmap const& bitmap, Options const& options)
639
0
{
640
0
    JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } };
641
0
    TRY(add_headers(stream, context, options, bitmap.size(), Mode::RGB));
642
0
    TRY(context.initialize_mcu(bitmap));
643
0
    TRY(add_image(stream, context, Mode::RGB));
644
0
    return {};
645
0
}
646
647
ErrorOr<void> JPEGWriter::encode(Stream& stream, CMYKBitmap const& bitmap, Options const& options)
648
0
{
649
0
    JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } };
650
0
    TRY(add_headers(stream, context, options, bitmap.size(), Mode::CMYK));
651
0
    TRY(context.initialize_mcu(bitmap));
652
0
    TRY(add_image(stream, context, Mode::CMYK));
653
0
    return {};
654
0
}
655
656
}