LibGfx/JPEG: Add a JPEG encoder :^)

This encoder is very naive as it only output SOF0 images and uses
pre-defined Huffman tables.

There is also a small bug with quantization which make using it
over-degrade the quality.
This commit is contained in:
Lucas CHOLLET 2023-06-20 16:32:17 -04:00 committed by Jelle Raaijmakers
parent 503720b574
commit 226b214142
Notes: sideshowbarker 2024-07-17 08:55:54 +09:00
4 changed files with 981 additions and 0 deletions

View file

@ -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

View file

@ -0,0 +1,501 @@
/*
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "JPEGWriter.h"
#include "JPEGShared.h"
#include "JPEGWriterTables.h"
#include <AK/BitStream.h>
#include <AK/Endian.h>
#include <AK/Function.h>
#include <LibGfx/Bitmap.h>
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<Bytes> read_some(Bytes) override
{
return Error::from_errno(EBADF);
}
virtual ErrorOr<size_t> write_some(ReadonlyBytes bytes) override
{
VERIFY(m_bit_offset == 0);
return m_stream.write_some(bytes);
}
template<Unsigned T>
ErrorOr<void> 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<u8>(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<void> 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<void> 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<double> / 16;
constexpr double inverse_sqrt_2 = M_SQRT1_2;
auto const convert_one_component = [](i16 component[], QuantizationTable const& table) {
Array<i16, 64> 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<void> 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<void> write_symbol(OutputHuffmanTable::Symbol symbol)
{
return m_bit_stream.write_bits(symbol.word, symbol.code_length);
};
ErrorOr<void> 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<u16>(diff, size));
return {};
}
ErrorOr<void> 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<u16>(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<Macroblock> m_macroblocks {};
Array<i16, 3> m_last_dc_values {};
u64 m_vertical_macroblocks {};
JPEGBigEndianOutputBitStream m_bit_stream;
};
ErrorOr<void> add_start_of_image(Stream& stream)
{
TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOI));
return {};
}
ErrorOr<void> add_end_of_image(Stream& stream)
{
TRY(stream.write_value<BigEndian<Marker>>(JPEG_EOI));
return {};
}
ErrorOr<void> add_frame_header(Stream& stream, JPEGEncodingContext const& context, Bitmap const& bitmap)
{
// B.2.2 - Frame header syntax
TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOF0));
// Lf = 8 + 3 × Nf, we only support a single image per frame so Nf = 3
TRY(stream.write_value<BigEndian<u16>>(17));
// P
TRY(stream.write_value<u8>(8));
// Y
TRY(stream.write_value<BigEndian<u16>>(bitmap.height()));
// X
TRY(stream.write_value<BigEndian<u16>>(bitmap.width()));
// Nf, as mentioned earlier, we only support Nf = 3
TRY(stream.write_value<u8>(3));
// Encode 3 components
for (u8 i {}; i < 3; ++i) {
// Ci
TRY(stream.write_value<u8>(i + 1));
// Hi and Vi
TRY(stream.write_value<u8>((1 << 4) | 1));
// Tqi
TRY(stream.write_value<u8>((i == 0 ? context.luminance_quantization_table() : context.chrominance_quantization_table()).id));
}
return {};
}
ErrorOr<void> add_quantization_table(Stream& stream, QuantizationTable const& table)
{
// B.2.4.1 - Quantization table-specification syntax
TRY(stream.write_value<BigEndian<Marker>>(JPEG_DQT));
// Lq = 2 + 1 * 65
TRY(stream.write_value<BigEndian<u16>>(2 + 65));
// Pq and Tq
TRY(stream.write_value<u8>((0 << 4) | table.id));
for (auto coefficient : table.table)
TRY(stream.write_value<u8>(coefficient));
return {};
}
ErrorOr<Vector<Vector<u8>, 16>> sort_symbols_per_size(OutputHuffmanTable const& table)
{
// JPEG only allows symbol with a size less than or equal to 16.
Vector<Vector<u8>, 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<void> add_huffman_table(Stream& stream, OutputHuffmanTable const& table)
{
// B.2.4.2 - Huffman table-specification syntax
TRY(stream.write_value<BigEndian<Marker>>(JPEG_DHT));
// Lh
TRY(stream.write_value<BigEndian<u16>>(2 + 17 + table.table.size()));
// Tc and Th
TRY(stream.write_value<u8>(table.id));
auto const vectorized_table = TRY(sort_symbols_per_size(table));
for (auto const& symbol_vector : vectorized_table)
TRY(stream.write_value<u8>(symbol_vector.size()));
for (auto const& symbol_vector : vectorized_table) {
for (auto symbol : symbol_vector)
TRY(stream.write_value<u8>(symbol));
}
return {};
}
ErrorOr<void> add_scan_header(Stream& stream)
{
// B.2.3 - Scan header syntax
TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOS));
// Ls - 6 + 2 × Ns
TRY(stream.write_value<BigEndian<u16>>(6 + 2 * 3));
// Ns
TRY(stream.write_value<u8>(3));
// Encode 3 components
for (u8 i {}; i < 3; ++i) {
// Csj
TRY(stream.write_value<u8>(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<u8>((huffman_identifier << 4) | huffman_identifier));
}
// Ss
TRY(stream.write_value<u8>(0));
// Se
TRY(stream.write_value<u8>(63));
// Ah and Al
TRY(stream.write_value<u8>((0 << 4) | 0));
return {};
}
}
ErrorOr<void> 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 {};
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <LibGfx/Forward.h>
namespace Gfx {
class JPEGWriter {
public:
static ErrorOr<void> encode(Stream&, Bitmap const&);
private:
JPEGWriter() = delete;
};
}

View file

@ -0,0 +1,457 @@
/*
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Vector.h>
namespace Gfx {
struct QuantizationTable {
Array<u8, 64> 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<Symbol, 16> 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,
};
}