diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index 30193191e3..ab73aeb256 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/Userland/Libraries/LibGfx/CMakeLists.txt @@ -39,6 +39,7 @@ set(SOURCES ImageFormats/ICOLoader.cpp ImageFormats/ImageDecoder.cpp ImageFormats/JPEGLoader.cpp + ImageFormats/JPEGWriter.cpp ImageFormats/PBMLoader.cpp ImageFormats/PGMLoader.cpp ImageFormats/PNGLoader.cpp diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.cpp b/Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.cpp new file mode 100644 index 0000000000..43114680e1 --- /dev/null +++ b/Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.cpp @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2023, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "JPEGWriter.h" +#include "JPEGShared.h" +#include "JPEGWriterTables.h" +#include +#include +#include +#include + +namespace Gfx { + +namespace { + +// This is basically a BigEndianOutputBitStream, the only difference +// is that it appends 0x00 after each 0xFF when it writes bits. +class JPEGBigEndianOutputBitStream : public Stream { +public: + explicit JPEGBigEndianOutputBitStream(Stream& stream) + : m_stream(stream) + { + } + + virtual ErrorOr read_some(Bytes) override + { + return Error::from_errno(EBADF); + } + + virtual ErrorOr write_some(ReadonlyBytes bytes) override + { + VERIFY(m_bit_offset == 0); + return m_stream.write_some(bytes); + } + + template + ErrorOr write_bits(T value, size_t bit_count) + { + VERIFY(m_bit_offset <= 7); + + while (bit_count > 0) { + u8 const next_bit = (value >> (bit_count - 1)) & 1; + bit_count--; + + m_current_byte <<= 1; + m_current_byte |= next_bit; + m_bit_offset++; + + if (m_bit_offset > 7) { + TRY(m_stream.write_value(m_current_byte)); + if (m_current_byte == 0xFF) + TRY(m_stream.write_value(0)); + + m_bit_offset = 0; + m_current_byte = 0; + } + } + + return {}; + } + + virtual bool is_eof() const override + { + return true; + } + + virtual bool is_open() const override + { + return m_stream.is_open(); + } + + virtual void close() override + { + } + + ErrorOr align_to_byte_boundary(u8 filler = 0x0) + { + if (m_bit_offset == 0) + return {}; + + TRY(write_bits(filler, 8 - m_bit_offset)); + VERIFY(m_bit_offset == 0); + return {}; + } + +private: + Stream& m_stream; + u8 m_current_byte { 0 }; + size_t m_bit_offset { 0 }; +}; + +class JPEGEncodingContext { +public: + JPEGEncodingContext(JPEGBigEndianOutputBitStream output_stream) + : m_bit_stream(move(output_stream)) + { + } + + ErrorOr initialize_mcu(Bitmap const& bitmap) + { + u64 const horizontal_macroblocks = bitmap.width() / 8 + (bitmap.width() % 8 == 0 ? 0 : 1); + m_vertical_macroblocks = bitmap.height() / 8 + (bitmap.height() % 8 == 0 ? 0 : 1); + + TRY(m_macroblocks.try_resize(horizontal_macroblocks * m_vertical_macroblocks)); + + for (u16 y {}; y < bitmap.height(); ++y) { + u16 const vertical_macroblock_index = y / 8; + u16 const vertical_pixel_offset = y - vertical_macroblock_index * 8; + + for (u16 x {}; x < bitmap.width(); ++x) { + u16 const horizontal_macroblock_index = x / 8; + u16 const horizontal_pixel_offset = x - horizontal_macroblock_index * 8; + + auto& macroblock = m_macroblocks[vertical_macroblock_index * horizontal_macroblocks + horizontal_macroblock_index]; + auto const pixel_offset = vertical_pixel_offset * 8 + horizontal_pixel_offset; + + auto const original_pixel = bitmap.get_pixel(x, y); + + // Conversion from YCbCr to RGB isn't specified in the first JPEG specification but in the JFIF extension: + // See: https://www.itu.int/rec/dologin_pub.asp?lang=f&id=T-REC-T.871-201105-I!!PDF-E&type=items + // 7 - Conversion to and from RGB + auto const y_ = clamp(0.299 * original_pixel.red() + 0.587 * original_pixel.green() + 0.114 * original_pixel.blue(), 0, 255); + auto const cb = clamp(-0.1687 * original_pixel.red() - 0.3313 * original_pixel.green() + 0.5 * original_pixel.blue() + 128, 0, 255); + auto const cr = clamp(0.5 * original_pixel.red() - 0.4187 * original_pixel.green() - 0.0813 * original_pixel.blue() + 128, 0, 255); + + // A.3.1 - Level shift + macroblock.r[pixel_offset] = y_ - 128; + macroblock.g[pixel_offset] = cb - 128; + macroblock.b[pixel_offset] = cr - 128; + } + } + + return {}; + } + + void fdct_and_quantization() + { + for (auto& macroblock : m_macroblocks) { + constexpr double pi_over_16 = AK::Pi / 16; + constexpr double inverse_sqrt_2 = M_SQRT1_2; + + auto const convert_one_component = [](i16 component[], QuantizationTable const& table) { + Array result {}; + + auto const sum_xy = [&component](u8 u, u8 v) { + double sum {}; + for (u8 x {}; x < 8; ++x) { + for (u8 y {}; y < 8; ++y) + sum += component[x * 8 + y] * cos((2 * x + 1) * u * pi_over_16) * cos((2 * y + 1) * v * pi_over_16); + } + return sum; + }; + + for (u8 u {}; u < 7; ++u) { + double const cu = u == 0 ? inverse_sqrt_2 : 1; + for (u8 v {}; v < 7; ++v) { + auto const table_index = u * 8 + v; + + double const cv = v == 0 ? inverse_sqrt_2 : 1; + + // A.3.3 - FDCT and IDCT + double const fdct = cu * cv * sum_xy(u, v) / 4; + + // A.3.4 - DCT coefficient quantization + i16 const quantized = round(fdct / table.table[table_index]); + + result[table_index] = quantized; + } + } + + for (u8 i {}; i < result.size(); ++i) + component[i] = result[i]; + }; + + convert_one_component(macroblock.y, m_luminance_quantization_table); + convert_one_component(macroblock.cb, m_chrominance_quantization_table); + convert_one_component(macroblock.cr, m_chrominance_quantization_table); + } + } + + ErrorOr write_huffman_stream() + { + for (auto& macroblock : m_macroblocks) { + TRY(encode_dc(dc_luminance_huffman_table, macroblock.y, 0)); + TRY(encode_ac(ac_luminance_huffman_table, macroblock.y)); + + TRY(encode_dc(dc_chrominance_huffman_table, macroblock.cb, 1)); + TRY(encode_ac(ac_chrominance_huffman_table, macroblock.cb)); + + TRY(encode_dc(dc_chrominance_huffman_table, macroblock.cr, 2)); + TRY(encode_ac(ac_chrominance_huffman_table, macroblock.cr)); + } + + TRY(m_bit_stream.align_to_byte_boundary(0xFF)); + + return {}; + } + + void set_luminance_quantization_table(QuantizationTable const& table, int quality) + { + set_quantization_table(m_luminance_quantization_table, table, quality); + } + + void set_chrominance_quantization_table(QuantizationTable const& table, int quality) + { + set_quantization_table(m_chrominance_quantization_table, table, quality); + } + + QuantizationTable const& luminance_quantization_table() const + { + return m_luminance_quantization_table; + } + + QuantizationTable const& chrominance_quantization_table() const + { + return m_chrominance_quantization_table; + } + + OutputHuffmanTable dc_luminance_huffman_table; + OutputHuffmanTable dc_chrominance_huffman_table; + + OutputHuffmanTable ac_luminance_huffman_table; + OutputHuffmanTable ac_chrominance_huffman_table; + +private: + static void set_quantization_table(QuantizationTable& destination, QuantizationTable const& source, int quality) + { + // In order to be compatible with libjpeg-turbo, we use the same coefficients as them. + + quality = clamp(quality, 1, 100); + + if (quality < 50) + quality = 5000 / quality; + else + quality = 200 - quality * 2; + + destination = source; + for (u8 i {}; i < 64; ++i) { + auto const shifted_value = (destination.table[i] * quality + 50) / 100; + destination.table[i] = clamp(shifted_value, 1, 255); + } + } + + ErrorOr write_symbol(OutputHuffmanTable::Symbol symbol) + { + return m_bit_stream.write_bits(symbol.word, symbol.code_length); + }; + + ErrorOr encode_dc(OutputHuffmanTable const& dc_table, i16 const component[], u8 component_id) + { + // F.1.2.1.3 - Huffman encoding procedures for DC coefficients + auto diff = component[0] - m_last_dc_values[component_id]; + m_last_dc_values[component_id] = component[0]; + + auto const size = csize(diff); + TRY(write_symbol(dc_table.from_input_byte(size))); + + if (diff < 0) + diff -= 1; + + TRY(m_bit_stream.write_bits(diff, size)); + return {}; + } + + ErrorOr encode_ac(OutputHuffmanTable const& ac_table, i16 const component[]) + { + { + // F.2 - Procedure for sequential encoding of AC coefficients with Huffman coding + u32 k {}; + u32 r {}; + + while (k < 63) { + k++; + + auto coefficient = component[zigzag_map[k]]; + if (coefficient == 0) { + if (k == 63) { + TRY(write_symbol(ac_table.from_input_byte(0x00))); + break; + } + r += 1; + continue; + } + + while (r > 15) { + TRY(write_symbol(ac_table.from_input_byte(0xF0))); + r -= 16; + } + + { + // F.3 - Sequential encoding of a non-zero AC coefficient + auto const ssss = csize(coefficient); + auto const rs = (r << 4) + ssss; + TRY(write_symbol(ac_table.from_input_byte(rs))); + + if (coefficient < 0) + coefficient -= 1; + + TRY(m_bit_stream.write_bits(coefficient, ssss)); + } + + r = 0; + } + } + return {}; + } + + static u8 csize(i16 coefficient) + { + VERIFY(coefficient >= -2047 && coefficient <= 2047); + return floor(log2(abs(coefficient))) + 1; + }; + + QuantizationTable m_luminance_quantization_table {}; + QuantizationTable m_chrominance_quantization_table {}; + + Vector m_macroblocks {}; + Array m_last_dc_values {}; + + u64 m_vertical_macroblocks {}; + + JPEGBigEndianOutputBitStream m_bit_stream; +}; + +ErrorOr add_start_of_image(Stream& stream) +{ + TRY(stream.write_value>(JPEG_SOI)); + return {}; +} + +ErrorOr add_end_of_image(Stream& stream) +{ + TRY(stream.write_value>(JPEG_EOI)); + return {}; +} + +ErrorOr add_frame_header(Stream& stream, JPEGEncodingContext const& context, Bitmap const& bitmap) +{ + // B.2.2 - Frame header syntax + TRY(stream.write_value>(JPEG_SOF0)); + + // Lf = 8 + 3 × Nf, we only support a single image per frame so Nf = 3 + TRY(stream.write_value>(17)); + + // P + TRY(stream.write_value(8)); + + // Y + TRY(stream.write_value>(bitmap.height())); + + // X + TRY(stream.write_value>(bitmap.width())); + + // Nf, as mentioned earlier, we only support Nf = 3 + TRY(stream.write_value(3)); + + // Encode 3 components + for (u8 i {}; i < 3; ++i) { + // Ci + TRY(stream.write_value(i + 1)); + + // Hi and Vi + TRY(stream.write_value((1 << 4) | 1)); + + // Tqi + TRY(stream.write_value((i == 0 ? context.luminance_quantization_table() : context.chrominance_quantization_table()).id)); + } + + return {}; +} + +ErrorOr add_quantization_table(Stream& stream, QuantizationTable const& table) +{ + // B.2.4.1 - Quantization table-specification syntax + TRY(stream.write_value>(JPEG_DQT)); + + // Lq = 2 + 1 * 65 + TRY(stream.write_value>(2 + 65)); + + // Pq and Tq + TRY(stream.write_value((0 << 4) | table.id)); + + for (auto coefficient : table.table) + TRY(stream.write_value(coefficient)); + + return {}; +} + +ErrorOr, 16>> sort_symbols_per_size(OutputHuffmanTable const& table) +{ + // JPEG only allows symbol with a size less than or equal to 16. + Vector, 16> output {}; + TRY(output.try_resize(16)); + + for (auto const& symbol : table.table) + TRY(output[symbol.code_length - 1].try_append(symbol.input_byte)); + + return output; +} + +ErrorOr add_huffman_table(Stream& stream, OutputHuffmanTable const& table) +{ + // B.2.4.2 - Huffman table-specification syntax + TRY(stream.write_value>(JPEG_DHT)); + + // Lh + TRY(stream.write_value>(2 + 17 + table.table.size())); + + // Tc and Th + TRY(stream.write_value(table.id)); + + auto const vectorized_table = TRY(sort_symbols_per_size(table)); + for (auto const& symbol_vector : vectorized_table) + TRY(stream.write_value(symbol_vector.size())); + + for (auto const& symbol_vector : vectorized_table) { + for (auto symbol : symbol_vector) + TRY(stream.write_value(symbol)); + } + + return {}; +} + +ErrorOr add_scan_header(Stream& stream) +{ + // B.2.3 - Scan header syntax + TRY(stream.write_value>(JPEG_SOS)); + + // Ls - 6 + 2 × Ns + TRY(stream.write_value>(6 + 2 * 3)); + + // Ns + TRY(stream.write_value(3)); + + // Encode 3 components + for (u8 i {}; i < 3; ++i) { + // Csj + TRY(stream.write_value(i + 1)); + + // Tdj and Taj + // We're using 0 for luminance and 1 for chrominance + u8 const huffman_identifier = i > 0 ? 1 : 0; + TRY(stream.write_value((huffman_identifier << 4) | huffman_identifier)); + } + + // Ss + TRY(stream.write_value(0)); + + // Se + TRY(stream.write_value(63)); + + // Ah and Al + TRY(stream.write_value((0 << 4) | 0)); + + return {}; +} + +} + +ErrorOr JPEGWriter::encode(Stream& stream, Bitmap const& bitmap) +{ + JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } }; + + // FIXME: Let's take the quality as an option instead of hardcoding it + // (there might also be a bug with quantization tables :^)). + context.set_luminance_quantization_table(s_default_luminance_quantization_table, 100); + context.set_chrominance_quantization_table(s_default_chrominance_quantization_table, 100); + + context.dc_luminance_huffman_table = s_default_dc_luminance_huffman_table; + context.dc_chrominance_huffman_table = s_default_dc_chrominance_huffman_table; + + context.ac_luminance_huffman_table = s_default_ac_luminance_huffman_table; + context.ac_chrominance_huffman_table = s_default_ac_chrominance_huffman_table; + + TRY(add_start_of_image(stream)); + + TRY(add_frame_header(stream, context, bitmap)); + + TRY(add_quantization_table(stream, context.luminance_quantization_table())); + TRY(add_quantization_table(stream, context.chrominance_quantization_table())); + + TRY(add_huffman_table(stream, context.dc_luminance_huffman_table)); + TRY(add_huffman_table(stream, context.dc_chrominance_huffman_table)); + TRY(add_huffman_table(stream, context.ac_luminance_huffman_table)); + TRY(add_huffman_table(stream, context.ac_chrominance_huffman_table)); + + TRY(add_scan_header(stream)); + + TRY(context.initialize_mcu(bitmap)); + context.fdct_and_quantization(); + + TRY(context.write_huffman_stream()); + + TRY(add_end_of_image(stream)); + return {}; +} + +} diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.h b/Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.h new file mode 100644 index 0000000000..96ea9eee65 --- /dev/null +++ b/Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Gfx { + +class JPEGWriter { +public: + static ErrorOr encode(Stream&, Bitmap const&); + +private: + JPEGWriter() = delete; +}; + +} diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEGWriterTables.h b/Userland/Libraries/LibGfx/ImageFormats/JPEGWriterTables.h new file mode 100644 index 0000000000..26a5c52f34 --- /dev/null +++ b/Userland/Libraries/LibGfx/ImageFormats/JPEGWriterTables.h @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2023, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Gfx { + +struct QuantizationTable { + Array table {}; + u8 id {}; +}; + +// K.1 - Quantization tables for luminance and chrominance components + +// clang-format off +constexpr static QuantizationTable s_default_luminance_quantization_table { + .table = { + 16, 11, 10, 16, 124, 140, 151, 161, + 12, 12, 14, 19, 126, 158, 160, 155, + 14, 13, 16, 24, 140, 157, 169, 156, + 14, 17, 22, 29, 151, 187, 180, 162, + 18, 22, 37, 56, 168, 109, 103, 177, + 24, 35, 55, 64, 181, 104, 113, 192, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 199, + }, + .id = 0, +}; + +constexpr static QuantizationTable s_default_chrominance_quantization_table { + .table = { + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + }, + .id = 1, +}; + +constexpr static QuantizationTable s_dummy_quantization_table { + .table = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + .id = 1, +}; + +// clang-format on + +struct OutputHuffmanTable { + struct Symbol { + u8 input_byte {}; + u8 code_length {}; + u16 word {}; + }; + + Symbol from_input_byte(u8 input_byte) const + { + for (auto symbol : table) { + if (symbol.input_byte == input_byte) + return symbol; + } + VERIFY_NOT_REACHED(); + } + + Vector table {}; + u8 id {}; +}; + +static OutputHuffmanTable s_default_dc_luminance_huffman_table { + .table = { + { 0, 2, 0b00 }, + { 1, 3, 0b010 }, + { 2, 3, 0b011 }, + { 3, 3, 0b100 }, + { 4, 3, 0b101 }, + { 5, 3, 0b110 }, + { 6, 4, 0b1110 }, + { 7, 5, 0b11110 }, + { 8, 6, 0b111110 }, + { 9, 7, 0b1111110 }, + { 10, 8, 0b11111110 }, + { 11, 9, 0b111111110 }, + }, + .id = (0 << 4) | 0, +}; + +static OutputHuffmanTable s_default_dc_chrominance_huffman_table { + .table = { + { 0, 2, 0b00 }, + { 1, 2, 0b01 }, + { 2, 2, 0b10 }, + { 3, 3, 0b110 }, + { 4, 4, 0b1110 }, + { 5, 5, 0b11110 }, + { 6, 6, 0b111110 }, + { 7, 7, 0b1111110 }, + { 8, 8, 0b11111110 }, + { 9, 9, 0b111111110 }, + { 10, 10, 0b1111111110 }, + { 11, 11, 0b11111111110 }, + }, + .id = (0 << 4) | 1, +}; + +static OutputHuffmanTable s_default_ac_luminance_huffman_table { + .table = { + { 0x01, 2, 0b00 }, + { 0x02, 2, 0b01 }, + { 0x03, 3, 0b100 }, + { 0x00, 4, 0b1010 }, + { 0x04, 4, 0b1011 }, + { 0x11, 4, 0b1100 }, + { 0x05, 5, 0b11010 }, + { 0x12, 5, 0b11011 }, + { 0x21, 5, 0b11100 }, + { 0x31, 6, 0b111010 }, + { 0x41, 6, 0b111011 }, + { 0x06, 7, 0b1111000 }, + { 0x13, 7, 0b1111001 }, + { 0x51, 7, 0b1111010 }, + { 0x61, 7, 0b1111011 }, + { 0x07, 8, 0b11111000 }, + { 0x22, 8, 0b11111001 }, + { 0x71, 8, 0b11111010 }, + { 0x14, 9, 0b111110110 }, + { 0x32, 9, 0b111110111 }, + { 0x81, 9, 0b111111000 }, + { 0x91, 9, 0b111111001 }, + { 0xA1, 9, 0b111111010 }, + { 0x08, 10, 0b1111110110 }, + { 0x23, 10, 0b1111110111 }, + { 0x42, 10, 0b1111111000 }, + { 0xB1, 10, 0b1111111001 }, + { 0xC1, 10, 0b1111111010 }, + { 0x15, 11, 0b11111110110 }, + { 0x52, 11, 0b11111110111 }, + { 0xD1, 11, 0b11111111000 }, + { 0xF0, 11, 0b11111111001 }, + { 0x24, 12, 0b111111110100 }, + { 0x33, 12, 0b111111110101 }, + { 0x62, 12, 0b111111110110 }, + { 0x72, 12, 0b111111110111 }, + { 0x82, 15, 0b111111111000000 }, + { 0x09, 16, 0b1111111110000010 }, + { 0x0A, 16, 0b1111111110000011 }, + { 0x16, 16, 0b1111111110000100 }, + { 0x17, 16, 0b1111111110000101 }, + { 0x18, 16, 0b1111111110000110 }, + { 0x19, 16, 0b1111111110000111 }, + { 0x1A, 16, 0b1111111110001000 }, + { 0x25, 16, 0b1111111110001001 }, + { 0x26, 16, 0b1111111110001010 }, + { 0x27, 16, 0b1111111110001011 }, + { 0x28, 16, 0b1111111110001100 }, + { 0x29, 16, 0b1111111110001101 }, + { 0x2A, 16, 0b1111111110001110 }, + { 0x34, 16, 0b1111111110001111 }, + { 0x35, 16, 0b1111111110010000 }, + { 0x36, 16, 0b1111111110010001 }, + { 0x37, 16, 0b1111111110010010 }, + { 0x38, 16, 0b1111111110010011 }, + { 0x39, 16, 0b1111111110010100 }, + { 0x3A, 16, 0b1111111110010101 }, + { 0x43, 16, 0b1111111110010110 }, + { 0x44, 16, 0b1111111110010111 }, + { 0x45, 16, 0b1111111110011000 }, + { 0x46, 16, 0b1111111110011001 }, + { 0x47, 16, 0b1111111110011010 }, + { 0x48, 16, 0b1111111110011011 }, + { 0x49, 16, 0b1111111110011100 }, + { 0x4A, 16, 0b1111111110011101 }, + { 0x53, 16, 0b1111111110011110 }, + { 0x54, 16, 0b1111111110011111 }, + { 0x55, 16, 0b1111111110100000 }, + { 0x56, 16, 0b1111111110100001 }, + { 0x57, 16, 0b1111111110100010 }, + { 0x58, 16, 0b1111111110100011 }, + { 0x59, 16, 0b1111111110100100 }, + { 0x5A, 16, 0b1111111110100101 }, + { 0x63, 16, 0b1111111110100110 }, + { 0x64, 16, 0b1111111110100111 }, + { 0x65, 16, 0b1111111110101000 }, + { 0x66, 16, 0b1111111110101001 }, + { 0x67, 16, 0b1111111110101010 }, + { 0x68, 16, 0b1111111110101011 }, + { 0x69, 16, 0b1111111110101100 }, + { 0x6A, 16, 0b1111111110101101 }, + { 0x73, 16, 0b1111111110101110 }, + { 0x74, 16, 0b1111111110101111 }, + { 0x75, 16, 0b1111111110110000 }, + { 0x76, 16, 0b1111111110110001 }, + { 0x77, 16, 0b1111111110110010 }, + { 0x78, 16, 0b1111111110110011 }, + { 0x79, 16, 0b1111111110110100 }, + { 0x7A, 16, 0b1111111110110101 }, + { 0x83, 16, 0b1111111110110110 }, + { 0x84, 16, 0b1111111110110111 }, + { 0x85, 16, 0b1111111110111000 }, + { 0x86, 16, 0b1111111110111001 }, + { 0x87, 16, 0b1111111110111010 }, + { 0x88, 16, 0b1111111110111011 }, + { 0x89, 16, 0b1111111110111100 }, + { 0x8A, 16, 0b1111111110111101 }, + { 0x92, 16, 0b1111111110111110 }, + { 0x93, 16, 0b1111111110111111 }, + { 0x94, 16, 0b1111111111000000 }, + { 0x95, 16, 0b1111111111000001 }, + { 0x96, 16, 0b1111111111000010 }, + { 0x97, 16, 0b1111111111000011 }, + { 0x98, 16, 0b1111111111000100 }, + { 0x99, 16, 0b1111111111000101 }, + { 0x9A, 16, 0b1111111111000110 }, + { 0xA2, 16, 0b1111111111000111 }, + { 0xA3, 16, 0b1111111111001000 }, + { 0xA4, 16, 0b1111111111001001 }, + { 0xA5, 16, 0b1111111111001010 }, + { 0xA6, 16, 0b1111111111001011 }, + { 0xA7, 16, 0b1111111111001100 }, + { 0xA8, 16, 0b1111111111001101 }, + { 0xA9, 16, 0b1111111111001110 }, + { 0xAA, 16, 0b1111111111001111 }, + { 0xB2, 16, 0b1111111111010000 }, + { 0xB3, 16, 0b1111111111010001 }, + { 0xB4, 16, 0b1111111111010010 }, + { 0xB5, 16, 0b1111111111010011 }, + { 0xB6, 16, 0b1111111111010100 }, + { 0xB7, 16, 0b1111111111010101 }, + { 0xB8, 16, 0b1111111111010110 }, + { 0xB9, 16, 0b1111111111010111 }, + { 0xBA, 16, 0b1111111111011000 }, + { 0xC2, 16, 0b1111111111011001 }, + { 0xC3, 16, 0b1111111111011010 }, + { 0xC4, 16, 0b1111111111011011 }, + { 0xC5, 16, 0b1111111111011100 }, + { 0xC6, 16, 0b1111111111011101 }, + { 0xC7, 16, 0b1111111111011110 }, + { 0xC8, 16, 0b1111111111011111 }, + { 0xC9, 16, 0b1111111111100000 }, + { 0xCA, 16, 0b1111111111100001 }, + { 0xD2, 16, 0b1111111111100010 }, + { 0xD3, 16, 0b1111111111100011 }, + { 0xD4, 16, 0b1111111111100100 }, + { 0xD5, 16, 0b1111111111100101 }, + { 0xD6, 16, 0b1111111111100110 }, + { 0xD7, 16, 0b1111111111100111 }, + { 0xD8, 16, 0b1111111111101000 }, + { 0xD9, 16, 0b1111111111101001 }, + { 0xDA, 16, 0b1111111111101010 }, + { 0xE1, 16, 0b1111111111101011 }, + { 0xE2, 16, 0b1111111111101100 }, + { 0xE3, 16, 0b1111111111101101 }, + { 0xE4, 16, 0b1111111111101110 }, + { 0xE5, 16, 0b1111111111101111 }, + { 0xE6, 16, 0b1111111111110000 }, + { 0xE7, 16, 0b1111111111110001 }, + { 0xE8, 16, 0b1111111111110010 }, + { 0xE9, 16, 0b1111111111110011 }, + { 0xEA, 16, 0b1111111111110100 }, + { 0xF1, 16, 0b1111111111110101 }, + { 0xF2, 16, 0b1111111111110110 }, + { 0xF3, 16, 0b1111111111110111 }, + { 0xF4, 16, 0b1111111111111000 }, + { 0xF5, 16, 0b1111111111111001 }, + { 0xF6, 16, 0b1111111111111010 }, + { 0xF7, 16, 0b1111111111111011 }, + { 0xF8, 16, 0b1111111111111100 }, + { 0xF9, 16, 0b1111111111111101 }, + { 0xFA, 16, 0b1111111111111110 }, + }, + .id = (1 << 4) | 0, +}; + +static OutputHuffmanTable s_default_ac_chrominance_huffman_table { + .table = { + { 0x00, 2, 0b00 }, + { 0x01, 2, 0b01 }, + { 0x02, 3, 0b100 }, + { 0x03, 4, 0b1010 }, + { 0x11, 4, 0b1011 }, + { 0x04, 5, 0b11000 }, + { 0x05, 5, 0b11001 }, + { 0x21, 5, 0b11010 }, + { 0x31, 5, 0b11011 }, + { 0x06, 6, 0b111000 }, + { 0x12, 6, 0b111001 }, + { 0x41, 6, 0b111010 }, + { 0x51, 6, 0b111011 }, + { 0x07, 7, 0b1111000 }, + { 0x61, 7, 0b1111001 }, + { 0x71, 7, 0b1111010 }, + { 0x13, 8, 0b11110110 }, + { 0x22, 8, 0b11110111 }, + { 0x32, 8, 0b11111000 }, + { 0x81, 8, 0b11111001 }, + { 0x08, 9, 0b111110100 }, + { 0x14, 9, 0b111110101 }, + { 0x42, 9, 0b111110110 }, + { 0x91, 9, 0b111110111 }, + { 0xA1, 9, 0b111111000 }, + { 0xB1, 9, 0b111111001 }, + { 0xC1, 9, 0b111111010 }, + { 0x09, 10, 0b1111110110 }, + { 0x23, 10, 0b1111110111 }, + { 0x33, 10, 0b1111111000 }, + { 0x52, 10, 0b1111111001 }, + { 0xF0, 10, 0b1111111010 }, + { 0x15, 11, 0b11111110110 }, + { 0x62, 11, 0b11111110111 }, + { 0x72, 11, 0b11111111000 }, + { 0xD1, 11, 0b11111111001 }, + { 0x0A, 12, 0b111111110100 }, + { 0x16, 12, 0b111111110101 }, + { 0x24, 12, 0b111111110110 }, + { 0x34, 12, 0b111111110111 }, + { 0xE1, 14, 0b11111111100000 }, + { 0x25, 15, 0b111111111000010 }, + { 0xF1, 15, 0b111111111000011 }, + { 0x17, 16, 0b1111111110001000 }, + { 0x18, 16, 0b1111111110001001 }, + { 0x19, 16, 0b1111111110001010 }, + { 0x1A, 16, 0b1111111110001011 }, + { 0x26, 16, 0b1111111110001100 }, + { 0x27, 16, 0b1111111110001101 }, + { 0x28, 16, 0b1111111110001110 }, + { 0x29, 16, 0b1111111110001111 }, + { 0x2A, 16, 0b1111111110010000 }, + { 0x35, 16, 0b1111111110010001 }, + { 0x36, 16, 0b1111111110010010 }, + { 0x37, 16, 0b1111111110010011 }, + { 0x38, 16, 0b1111111110010100 }, + { 0x39, 16, 0b1111111110010101 }, + { 0x3A, 16, 0b1111111110010110 }, + { 0x43, 16, 0b1111111110010111 }, + { 0x44, 16, 0b1111111110011000 }, + { 0x45, 16, 0b1111111110011001 }, + { 0x46, 16, 0b1111111110011010 }, + { 0x47, 16, 0b1111111110011011 }, + { 0x48, 16, 0b1111111110011100 }, + { 0x49, 16, 0b1111111110011101 }, + { 0x4A, 16, 0b1111111110011110 }, + { 0x53, 16, 0b1111111110011111 }, + { 0x54, 16, 0b1111111110100000 }, + { 0x55, 16, 0b1111111110100001 }, + { 0x56, 16, 0b1111111110100010 }, + { 0x57, 16, 0b1111111110100011 }, + { 0x58, 16, 0b1111111110100100 }, + { 0x59, 16, 0b1111111110100101 }, + { 0x5A, 16, 0b1111111110100110 }, + { 0x63, 16, 0b1111111110100111 }, + { 0x64, 16, 0b1111111110101000 }, + { 0x65, 16, 0b1111111110101001 }, + { 0x66, 16, 0b1111111110101010 }, + { 0x67, 16, 0b1111111110101011 }, + { 0x68, 16, 0b1111111110101100 }, + { 0x69, 16, 0b1111111110101101 }, + { 0x6A, 16, 0b1111111110101110 }, + { 0x73, 16, 0b1111111110101111 }, + { 0x74, 16, 0b1111111110110000 }, + { 0x75, 16, 0b1111111110110001 }, + { 0x76, 16, 0b1111111110110010 }, + { 0x77, 16, 0b1111111110110011 }, + { 0x78, 16, 0b1111111110110100 }, + { 0x79, 16, 0b1111111110110101 }, + { 0x7A, 16, 0b1111111110110110 }, + { 0x82, 16, 0b1111111110110111 }, + { 0x83, 16, 0b1111111110111000 }, + { 0x84, 16, 0b1111111110111001 }, + { 0x85, 16, 0b1111111110111010 }, + { 0x86, 16, 0b1111111110111011 }, + { 0x87, 16, 0b1111111110111100 }, + { 0x88, 16, 0b1111111110111101 }, + { 0x89, 16, 0b1111111110111110 }, + { 0x8A, 16, 0b1111111110111111 }, + { 0x92, 16, 0b1111111111000000 }, + { 0x93, 16, 0b1111111111000001 }, + { 0x94, 16, 0b1111111111000010 }, + { 0x95, 16, 0b1111111111000011 }, + { 0x96, 16, 0b1111111111000100 }, + { 0x97, 16, 0b1111111111000101 }, + { 0x98, 16, 0b1111111111000110 }, + { 0x99, 16, 0b1111111111000111 }, + { 0x9A, 16, 0b1111111111001000 }, + { 0xA2, 16, 0b1111111111001001 }, + { 0xA3, 16, 0b1111111111001010 }, + { 0xA4, 16, 0b1111111111001011 }, + { 0xA5, 16, 0b1111111111001100 }, + { 0xA6, 16, 0b1111111111001101 }, + { 0xA7, 16, 0b1111111111001110 }, + { 0xA8, 16, 0b1111111111001111 }, + { 0xA9, 16, 0b1111111111010000 }, + { 0xAA, 16, 0b1111111111010001 }, + { 0xB2, 16, 0b1111111111010010 }, + { 0xB3, 16, 0b1111111111010011 }, + { 0xB4, 16, 0b1111111111010100 }, + { 0xB5, 16, 0b1111111111010101 }, + { 0xB6, 16, 0b1111111111010110 }, + { 0xB7, 16, 0b1111111111010111 }, + { 0xB8, 16, 0b1111111111011000 }, + { 0xB9, 16, 0b1111111111011001 }, + { 0xBA, 16, 0b1111111111011010 }, + { 0xC2, 16, 0b1111111111011011 }, + { 0xC3, 16, 0b1111111111011100 }, + { 0xC4, 16, 0b1111111111011101 }, + { 0xC5, 16, 0b1111111111011110 }, + { 0xC6, 16, 0b1111111111011111 }, + { 0xC7, 16, 0b1111111111100000 }, + { 0xC8, 16, 0b1111111111100001 }, + { 0xC9, 16, 0b1111111111100010 }, + { 0xCA, 16, 0b1111111111100011 }, + { 0xD2, 16, 0b1111111111100100 }, + { 0xD3, 16, 0b1111111111100101 }, + { 0xD4, 16, 0b1111111111100110 }, + { 0xD5, 16, 0b1111111111100111 }, + { 0xD6, 16, 0b1111111111101000 }, + { 0xD7, 16, 0b1111111111101001 }, + { 0xD8, 16, 0b1111111111101010 }, + { 0xD9, 16, 0b1111111111101011 }, + { 0xDA, 16, 0b1111111111101100 }, + { 0xE2, 16, 0b1111111111101101 }, + { 0xE3, 16, 0b1111111111101110 }, + { 0xE4, 16, 0b1111111111101111 }, + { 0xE5, 16, 0b1111111111110000 }, + { 0xE6, 16, 0b1111111111110001 }, + { 0xE7, 16, 0b1111111111110010 }, + { 0xE8, 16, 0b1111111111110011 }, + { 0xE9, 16, 0b1111111111110100 }, + { 0xEA, 16, 0b1111111111110101 }, + { 0xF2, 16, 0b1111111111110110 }, + { 0xF3, 16, 0b1111111111110111 }, + { 0xF4, 16, 0b1111111111111000 }, + { 0xF5, 16, 0b1111111111111001 }, + { 0xF6, 16, 0b1111111111111010 }, + { 0xF7, 16, 0b1111111111111011 }, + { 0xF8, 16, 0b1111111111111100 }, + { 0xF9, 16, 0b1111111111111101 }, + { 0xFA, 16, 0b1111111111111110 }, + }, + .id = (1 << 4) | 1, +}; + +}