mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
LibGfx: Remove home-grown JPEG codec in favor of libjpeg-turbo
This commit is contained in:
parent
808784092c
commit
2a888ca626
Notes:
sideshowbarker
2024-07-17 06:09:44 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/LadybirdBrowser/ladybird/commit/2a888ca626 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/183 Reviewed-by: https://github.com/nico
18 changed files with 215 additions and 3341 deletions
|
@ -134,10 +134,6 @@
|
|||
# cmakedefine01 JOB_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef JPEG_DEBUG
|
||||
# cmakedefine01 JPEG_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef JPEG2000_DEBUG
|
||||
# cmakedefine01 JPEG2000_DEBUG
|
||||
#endif
|
||||
|
|
|
@ -29,7 +29,6 @@ set(IMAGE_DECODER_DEBUG ON)
|
|||
set(IMAGE_LOADER_DEBUG ON)
|
||||
set(JBIG2_DEBUG ON)
|
||||
set(JOB_DEBUG ON)
|
||||
set(JPEG_DEBUG ON)
|
||||
set(JPEG2000_DEBUG ON)
|
||||
set(JS_BYTECODE_DEBUG ON)
|
||||
set(JS_MODULE_DEBUG ON)
|
||||
|
|
|
@ -573,11 +573,6 @@ if (BUILD_TESTING)
|
|||
# It is therefore not reasonable to run it on Lagom, and we only run the Regex test
|
||||
lagom_test(../../Tests/LibRegex/Regex.cpp LIBS LibRegex WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../Tests/LibRegex)
|
||||
|
||||
# test-jpeg-roundtrip
|
||||
add_executable(test-jpeg-roundtrip
|
||||
../../Userland/Utilities/test-jpeg-roundtrip.cpp)
|
||||
target_link_libraries(test-jpeg-roundtrip AK LibGfx LibMain)
|
||||
|
||||
# JavaScriptTestRunner + LibTest tests
|
||||
# test-js
|
||||
add_executable(test-js
|
||||
|
|
|
@ -250,7 +250,6 @@ write_cmake_config("ak_debug_gen") {
|
|||
"IMAGE_LOADER_DEBUG=",
|
||||
"JBIG2_DEBUG=",
|
||||
"JOB_DEBUG=",
|
||||
"JPEG_DEBUG=",
|
||||
"JPEG2000_DEBUG=",
|
||||
"JS_BYTECODE_DEBUG=",
|
||||
"JS_MODULE_DEBUG=",
|
||||
|
|
|
@ -508,24 +508,6 @@ TEST_CASE(test_jpeg_sof2_successive_aproximation)
|
|||
TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 600, 800 }));
|
||||
}
|
||||
|
||||
TEST_CASE(test_jpeg_sof1_12bits)
|
||||
{
|
||||
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("jpg/12-bit.jpg"sv)));
|
||||
EXPECT(Gfx::JPEGImageDecoderPlugin::sniff(file->bytes()));
|
||||
auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEGImageDecoderPlugin::create(file->bytes()));
|
||||
|
||||
TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 320, 240 }));
|
||||
}
|
||||
|
||||
TEST_CASE(test_jpeg_sof2_12bits)
|
||||
{
|
||||
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("jpg/12-bit-progressive.jpg"sv)));
|
||||
EXPECT(Gfx::JPEGImageDecoderPlugin::sniff(file->bytes()));
|
||||
auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEGImageDecoderPlugin::create(file->bytes()));
|
||||
|
||||
TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 320, 240 }));
|
||||
}
|
||||
|
||||
TEST_CASE(test_jpeg_empty_icc)
|
||||
{
|
||||
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("jpg/gradient_empty_icc.jpg"sv)));
|
||||
|
@ -561,8 +543,9 @@ TEST_CASE(test_jpeg_malformed_header)
|
|||
|
||||
for (auto test_input : test_inputs) {
|
||||
auto file = TRY_OR_FAIL(Core::MappedFile::map(test_input));
|
||||
auto plugin_decoder_or_error = Gfx::JPEGImageDecoderPlugin::create(file->bytes());
|
||||
EXPECT(plugin_decoder_or_error.is_error());
|
||||
auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEGImageDecoderPlugin::create(file->bytes()));
|
||||
auto frame_or_error = plugin_decoder->frame(0);
|
||||
EXPECT(frame_or_error.is_error());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
|
@ -114,3 +114,8 @@ add_dependencies(LibGfx generate_tiff_files_handler)
|
|||
|
||||
list(TRANSFORM generated_sources PREPEND "${CMAKE_CURRENT_BINARY_DIR}/")
|
||||
install(FILES ${generated_sources} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/LibGfx/ImageFormats")
|
||||
|
||||
find_package(JPEG REQUIRED)
|
||||
target_include_directories(LibGfx PRIVATE ${JPEG_INCLUDE_DIRS})
|
||||
target_link_libraries(LibGfx PRIVATE ${JPEG_LIBRARIES})
|
||||
target_link_directories(LibGfx PRIVATE ${JPEG_LIBRARY_DIRS})
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,37 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2022-2023, Lucas Chollet <lucas.chollet@serenityos.org>
|
||||
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <LibGfx/ImageFormats/ImageDecoder.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
struct JPEGLoadingContext;
|
||||
|
||||
// For the specification, see: https://www.w3.org/Graphics/JPEG/itu-t81.pdf
|
||||
|
||||
struct JPEGDecoderOptions {
|
||||
enum class CMYK {
|
||||
// For standalone jpeg files.
|
||||
Normal,
|
||||
|
||||
// For jpeg data embedded in PDF files.
|
||||
PDF,
|
||||
};
|
||||
CMYK cmyk { CMYK::Normal };
|
||||
};
|
||||
|
||||
class JPEGImageDecoderPlugin : public ImageDecoderPlugin {
|
||||
public:
|
||||
static bool sniff(ReadonlyBytes);
|
||||
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
|
||||
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create_with_options(ReadonlyBytes, JPEGDecoderOptions = {});
|
||||
|
||||
virtual ~JPEGImageDecoderPlugin() override;
|
||||
virtual IntSize size() override;
|
||||
|
@ -46,7 +30,7 @@ public:
|
|||
virtual ErrorOr<NonnullRefPtr<CMYKBitmap>> cmyk_frame() override;
|
||||
|
||||
private:
|
||||
JPEGImageDecoderPlugin(NonnullOwnPtr<JPEGLoadingContext>);
|
||||
explicit JPEGImageDecoderPlugin(NonnullOwnPtr<JPEGLoadingContext>);
|
||||
|
||||
NonnullOwnPtr<JPEGLoadingContext> m_context;
|
||||
};
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// These names are defined in B.1.1.3 - Marker assignments
|
||||
|
||||
#define JPEG_APPN0 0XFFE0
|
||||
#define JPEG_APPN1 0XFFE1
|
||||
#define JPEG_APPN2 0XFFE2
|
||||
#define JPEG_APPN3 0XFFE3
|
||||
#define JPEG_APPN4 0XFFE4
|
||||
#define JPEG_APPN5 0XFFE5
|
||||
#define JPEG_APPN6 0XFFE6
|
||||
#define JPEG_APPN7 0XFFE7
|
||||
#define JPEG_APPN8 0XFFE8
|
||||
#define JPEG_APPN9 0XFFE9
|
||||
#define JPEG_APPN10 0XFFEA
|
||||
#define JPEG_APPN11 0XFFEB
|
||||
#define JPEG_APPN12 0XFFEC
|
||||
#define JPEG_APPN13 0XFFED
|
||||
#define JPEG_APPN14 0xFFEE
|
||||
#define JPEG_APPN15 0xFFEF
|
||||
|
||||
#define JPEG_RESERVED1 0xFFF1
|
||||
#define JPEG_RESERVED2 0xFFF2
|
||||
#define JPEG_RESERVED3 0xFFF3
|
||||
#define JPEG_RESERVED4 0xFFF4
|
||||
#define JPEG_RESERVED5 0xFFF5
|
||||
#define JPEG_RESERVED6 0xFFF6
|
||||
#define JPEG_RESERVED7 0xFFF7
|
||||
#define JPEG_RESERVED8 0xFFF8
|
||||
#define JPEG_RESERVED9 0xFFF9
|
||||
#define JPEG_RESERVEDA 0xFFFA
|
||||
#define JPEG_RESERVEDB 0xFFFB
|
||||
#define JPEG_RESERVEDC 0xFFFC
|
||||
#define JPEG_RESERVEDD 0xFFFD
|
||||
|
||||
#define JPEG_RST0 0xFFD0
|
||||
#define JPEG_RST1 0xFFD1
|
||||
#define JPEG_RST2 0xFFD2
|
||||
#define JPEG_RST3 0xFFD3
|
||||
#define JPEG_RST4 0xFFD4
|
||||
#define JPEG_RST5 0xFFD5
|
||||
#define JPEG_RST6 0xFFD6
|
||||
#define JPEG_RST7 0xFFD7
|
||||
|
||||
#define JPEG_ZRL 0xF0
|
||||
|
||||
#define JPEG_DHP 0xFFDE
|
||||
#define JPEG_EXP 0xFFDF
|
||||
|
||||
#define JPEG_DAC 0XFFCC
|
||||
#define JPEG_DHT 0XFFC4
|
||||
#define JPEG_DQT 0XFFDB
|
||||
#define JPEG_EOI 0xFFD9
|
||||
#define JPEG_DRI 0XFFDD
|
||||
#define JPEG_SOF0 0XFFC0
|
||||
#define JPEG_SOF1 0XFFC1
|
||||
#define JPEG_SOF2 0xFFC2
|
||||
#define JPEG_SOF15 0xFFCF
|
||||
#define JPEG_SOI 0XFFD8
|
||||
#define JPEG_SOS 0XFFDA
|
||||
#define JPEG_COM 0xFFFE
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
using Marker = u16;
|
||||
|
||||
constexpr static u8 zigzag_map[64] {
|
||||
0, 1, 8, 16, 9, 2, 3, 10,
|
||||
17, 24, 32, 25, 18, 11, 4, 5,
|
||||
12, 19, 26, 33, 40, 48, 41, 34,
|
||||
27, 20, 13, 6, 7, 14, 21, 28,
|
||||
35, 42, 49, 56, 57, 50, 43, 36,
|
||||
29, 22, 15, 23, 30, 37, 44, 51,
|
||||
58, 59, 52, 45, 38, 31, 39, 46,
|
||||
53, 60, 61, 54, 47, 55, 62, 63
|
||||
};
|
||||
|
||||
/**
|
||||
* MCU means group of data units that are coded together. A data unit is an 8x8
|
||||
* block of component data. In interleaved scans, number of non-interleaved data
|
||||
* units of a component C is Ch * Cv, where Ch and Cv represent the horizontal &
|
||||
* vertical subsampling factors of the component, respectively. A MacroBlock is
|
||||
* an 8x8 block of RGB values before encoding, and 8x8 block of YCbCr values when
|
||||
* we're done decoding the huffman stream.
|
||||
*/
|
||||
struct Macroblock {
|
||||
union {
|
||||
i16 y[64] = { 0 };
|
||||
i16 r[64];
|
||||
};
|
||||
|
||||
union {
|
||||
i16 cb[64] = { 0 };
|
||||
i16 g[64];
|
||||
};
|
||||
|
||||
union {
|
||||
i16 cr[64] = { 0 };
|
||||
i16 b[64];
|
||||
};
|
||||
|
||||
i16 k[64] = { 0 };
|
||||
};
|
||||
|
||||
}
|
|
@ -1,656 +1,113 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
|
||||
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.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 <AK/Stream.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/CMYKBitmap.h>
|
||||
#include <LibGfx/ImageFormats/JPEGWriter.h>
|
||||
#include <jpeglib.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
namespace {
|
||||
struct MemoryDestinationManager : public jpeg_destination_mgr {
|
||||
Vector<u8>& buffer;
|
||||
static constexpr size_t BUFFER_SIZE_INCREMENT = 65536;
|
||||
|
||||
enum Mode {
|
||||
RGB,
|
||||
CMYK,
|
||||
MemoryDestinationManager(Vector<u8>& buffer)
|
||||
: buffer(buffer)
|
||||
{
|
||||
init_destination = [](j_compress_ptr cinfo) {
|
||||
auto* dest = static_cast<MemoryDestinationManager*>(cinfo->dest);
|
||||
dest->buffer.resize(BUFFER_SIZE_INCREMENT);
|
||||
dest->next_output_byte = dest->buffer.data();
|
||||
dest->free_in_buffer = dest->buffer.capacity();
|
||||
};
|
||||
|
||||
empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
|
||||
auto* dest = static_cast<MemoryDestinationManager*>(cinfo->dest);
|
||||
size_t old_size = dest->buffer.size();
|
||||
dest->buffer.resize(old_size + BUFFER_SIZE_INCREMENT);
|
||||
dest->next_output_byte = dest->buffer.data() + old_size;
|
||||
dest->free_in_buffer = BUFFER_SIZE_INCREMENT;
|
||||
return TRUE;
|
||||
};
|
||||
|
||||
term_destination = [](j_compress_ptr cinfo) {
|
||||
auto* dest = static_cast<MemoryDestinationManager*>(cinfo->dest);
|
||||
dest->buffer.resize(dest->buffer.size() - dest->free_in_buffer);
|
||||
};
|
||||
|
||||
next_output_byte = nullptr;
|
||||
free_in_buffer = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// 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 = ceil_div(bitmap.width(), 8);
|
||||
u64 const vertical_macroblocks = ceil_div(bitmap.height(), 8);
|
||||
TRY(m_macroblocks.try_resize(horizontal_macroblocks * 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 {};
|
||||
}
|
||||
|
||||
ErrorOr<void> initialize_mcu(CMYKBitmap const& bitmap)
|
||||
{
|
||||
u64 const horizontal_macroblocks = ceil_div(bitmap.size().width(), 8);
|
||||
u64 const vertical_macroblocks = ceil_div(bitmap.size().height(), 8);
|
||||
TRY(m_macroblocks.try_resize(horizontal_macroblocks * vertical_macroblocks));
|
||||
|
||||
for (u16 y {}; y < bitmap.size().height(); ++y) {
|
||||
u16 const vertical_macroblock_index = y / 8;
|
||||
u16 const vertical_pixel_offset = y - vertical_macroblock_index * 8;
|
||||
|
||||
for (u16 x {}; x < bitmap.size().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.scanline(y)[x];
|
||||
|
||||
// To get YCCK, the CMY part is converted to RGB (ignoring the K component), and then the RGB is converted to YCbCr.
|
||||
// r is `255 - c` (and similar for g/m b/y), but with the Adobe YCCK color transform marker, the CMY
|
||||
// channels are stored inverted, which cancels out: 255 - (255 - x) == x.
|
||||
// K is stored as-is (meaning it's inverted once for the color transform).
|
||||
u8 r = original_pixel.c;
|
||||
u8 g = original_pixel.m;
|
||||
u8 b = original_pixel.y;
|
||||
u8 k = 255 - original_pixel.k;
|
||||
|
||||
// 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 * r + 0.587 * g + 0.114 * b, 0, 255);
|
||||
auto const cb = clamp(-0.1687 * r - 0.3313 * g + 0.5 * b + 128, 0, 255);
|
||||
auto const cr = clamp(0.5 * r - 0.4187 * g - 0.0813 * b + 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;
|
||||
macroblock.k[pixel_offset] = k - 128;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Array<double, 64> create_cosine_lookup_table()
|
||||
{
|
||||
static constexpr double pi_over_16 = AK::Pi<double> / 16;
|
||||
|
||||
Array<double, 64> table;
|
||||
|
||||
for (u8 u = 0; u < 8; ++u) {
|
||||
for (u8 x = 0; x < 8; ++x)
|
||||
table[u * 8 + x] = cos((2 * x + 1) * u * pi_over_16);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
void fdct_and_quantization(Mode mode)
|
||||
{
|
||||
static auto cosine_table = create_cosine_lookup_table();
|
||||
|
||||
for (auto& macroblock : m_macroblocks) {
|
||||
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 = [&](u8 u, u8 v) {
|
||||
double sum {};
|
||||
for (u8 y {}; y < 8; ++y) {
|
||||
for (u8 x {}; x < 8; ++x)
|
||||
sum += component[y * 8 + x] * cosine_table[u * 8 + x] * cosine_table[v * 8 + y];
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
|
||||
for (u8 v {}; v < 8; ++v) {
|
||||
double const cv = v == 0 ? inverse_sqrt_2 : 1;
|
||||
for (u8 u {}; u < 8; ++u) {
|
||||
auto const table_index = v * 8 + u;
|
||||
|
||||
double const cu = u == 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);
|
||||
if (mode == Mode::CMYK)
|
||||
convert_one_component(macroblock.k, m_luminance_quantization_table);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorOr<void> write_huffman_stream(Mode mode)
|
||||
{
|
||||
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));
|
||||
|
||||
if (mode == Mode::CMYK) {
|
||||
TRY(encode_dc(dc_luminance_huffman_table, macroblock.k, 3));
|
||||
TRY(encode_ac(ac_luminance_huffman_table, macroblock.k));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (coefficient == 0)
|
||||
return 0;
|
||||
|
||||
return floor(log2(abs(coefficient))) + 1;
|
||||
}
|
||||
|
||||
QuantizationTable m_luminance_quantization_table {};
|
||||
QuantizationTable m_chrominance_quantization_table {};
|
||||
|
||||
Vector<Macroblock> m_macroblocks {};
|
||||
Array<i16, 4> m_last_dc_values {};
|
||||
|
||||
JPEGBigEndianOutputBitStream m_bit_stream;
|
||||
};
|
||||
|
||||
ErrorOr<void> add_start_of_image(Stream& stream)
|
||||
ErrorOr<void> JPEGWriter::encode_impl(Stream& stream, auto const& bitmap, Options const& options, ColorSpace color_space)
|
||||
{
|
||||
TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOI));
|
||||
struct jpeg_compress_struct cinfo { };
|
||||
struct jpeg_error_mgr jerr { };
|
||||
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
|
||||
jpeg_create_compress(&cinfo);
|
||||
|
||||
Vector<u8> buffer;
|
||||
MemoryDestinationManager dest_manager(buffer);
|
||||
cinfo.dest = &dest_manager;
|
||||
|
||||
cinfo.image_width = bitmap.size().width();
|
||||
cinfo.image_height = bitmap.size().height();
|
||||
cinfo.input_components = 4;
|
||||
|
||||
switch (color_space) {
|
||||
case ColorSpace::RGB:
|
||||
cinfo.in_color_space = JCS_EXT_BGRX;
|
||||
break;
|
||||
case ColorSpace::CMYK:
|
||||
cinfo.in_color_space = JCS_CMYK;
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
jpeg_set_defaults(&cinfo);
|
||||
jpeg_set_colorspace(&cinfo, JCS_YCbCr);
|
||||
jpeg_set_quality(&cinfo, options.quality, TRUE);
|
||||
|
||||
if (options.icc_data.has_value()) {
|
||||
jpeg_write_icc_profile(&cinfo, options.icc_data->data(), options.icc_data->size());
|
||||
}
|
||||
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
|
||||
Vector<JSAMPLE> row_buffer;
|
||||
row_buffer.resize(bitmap.size().width() * 4);
|
||||
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
auto const* row_ptr = reinterpret_cast<u8 const*>(bitmap.scanline(cinfo.next_scanline));
|
||||
JSAMPROW row_pointer = (JSAMPROW)row_ptr;
|
||||
jpeg_write_scanlines(&cinfo, &row_pointer, 1);
|
||||
}
|
||||
|
||||
jpeg_finish_compress(&cinfo);
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
|
||||
TRY(stream.write_until_depleted(buffer));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> add_end_of_image(Stream& stream)
|
||||
{
|
||||
TRY(stream.write_value<BigEndian<Marker>>(JPEG_EOI));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> add_icc_data(Stream& stream, ReadonlyBytes icc_data)
|
||||
{
|
||||
// https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf, JFIF section
|
||||
constexpr StringView icc_chunk_name = "ICC_PROFILE\0"sv;
|
||||
|
||||
// One JPEG chunk is at most 65535 bytes long, which includes the size of the 2-byte
|
||||
// "length" field. This leaves 65533 bytes for the actual data. One ICC chunk needs
|
||||
// 12 bytes for the "ICC_PROFILE\0" app id and then one byte each for the current
|
||||
// sequence number and the number of ICC chunks. This leaves 65519 bytes for the
|
||||
// ICC data.
|
||||
constexpr size_t icc_chunk_header_size = 2 + icc_chunk_name.length() + 1 + 1;
|
||||
constexpr size_t max_chunk_size = 65535 - icc_chunk_header_size;
|
||||
static_assert(max_chunk_size == 65519);
|
||||
|
||||
constexpr size_t max_number_of_icc_chunks = 255; // Chunk IDs are stored in an u8 and start at 1.
|
||||
constexpr size_t max_icc_data_size = max_chunk_size * max_number_of_icc_chunks;
|
||||
|
||||
// "The 1-byte chunk count limits the size of embeddable profiles to 16 707 345 bytes.""
|
||||
static_assert(max_icc_data_size == 16'707'345);
|
||||
|
||||
if (icc_data.size() > max_icc_data_size)
|
||||
return Error::from_string_view("JPEGWriter: icc data too large for jpeg format"sv);
|
||||
|
||||
size_t const number_of_icc_chunks = AK::ceil_div(icc_data.size(), max_chunk_size);
|
||||
for (size_t chunk_id = 1; chunk_id <= number_of_icc_chunks; ++chunk_id) {
|
||||
size_t const chunk_size = min(icc_data.size(), max_chunk_size);
|
||||
|
||||
TRY(stream.write_value<BigEndian<Marker>>(JPEG_APPN2));
|
||||
TRY(stream.write_value<BigEndian<u16>>(icc_chunk_header_size + chunk_size));
|
||||
TRY(stream.write_until_depleted(icc_chunk_name.bytes()));
|
||||
TRY(stream.write_value<u8>(chunk_id));
|
||||
TRY(stream.write_value<u8>(number_of_icc_chunks));
|
||||
TRY(stream.write_until_depleted(icc_data.slice(0, chunk_size)));
|
||||
icc_data = icc_data.slice(chunk_size);
|
||||
}
|
||||
VERIFY(icc_data.is_empty());
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> add_frame_header(Stream& stream, JPEGEncodingContext const& context, IntSize size, Mode mode)
|
||||
{
|
||||
// B.2.2 - Frame header syntax
|
||||
TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOF0));
|
||||
|
||||
u16 const Nf = mode == Mode::CMYK ? 4 : 3;
|
||||
|
||||
// Lf = 8 + 3 × Nf
|
||||
TRY(stream.write_value<BigEndian<u16>>(8 + 3 * Nf));
|
||||
|
||||
// P
|
||||
TRY(stream.write_value<u8>(8));
|
||||
|
||||
// Y
|
||||
TRY(stream.write_value<BigEndian<u16>>(size.height()));
|
||||
|
||||
// X
|
||||
TRY(stream.write_value<BigEndian<u16>>(size.width()));
|
||||
|
||||
// Nf
|
||||
TRY(stream.write_value<u8>(Nf));
|
||||
|
||||
// Encode Nf components
|
||||
for (u8 i {}; i < Nf; ++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 || i == 3 ? context.luminance_quantization_table() : context.chrominance_quantization_table()).id));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> add_ycck_color_transform_header(Stream& stream)
|
||||
{
|
||||
// T-REC-T.872-201206-I!!PDF-E.pdf, 6.5.3 APP14 marker segment for colour encoding
|
||||
TRY(stream.write_value<BigEndian<Marker>>(JPEG_APPN14));
|
||||
TRY(stream.write_value<BigEndian<u16>>(14));
|
||||
|
||||
TRY(stream.write_until_depleted("Adobe\0"sv.bytes()));
|
||||
|
||||
// These values are ignored.
|
||||
TRY(stream.write_value<u8>(0x64));
|
||||
TRY(stream.write_value<BigEndian<u16>>(0x0000));
|
||||
TRY(stream.write_value<BigEndian<u16>>(0x0000));
|
||||
|
||||
// YCCK
|
||||
TRY(stream.write_value<u8>(0x2));
|
||||
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 (u8 i = 0; i < 64; ++i)
|
||||
TRY(stream.write_value<u8>(table.table[zigzag_map[i]]));
|
||||
|
||||
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, Mode mode)
|
||||
{
|
||||
// B.2.3 - Scan header syntax
|
||||
TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOS));
|
||||
|
||||
u16 const Ns = mode == Mode::CMYK ? 4 : 3;
|
||||
|
||||
// Ls - 6 + 2 × Ns
|
||||
TRY(stream.write_value<BigEndian<u16>>(6 + 2 * Ns));
|
||||
|
||||
// Ns
|
||||
TRY(stream.write_value<u8>(Ns));
|
||||
|
||||
// Encode Ns components
|
||||
for (u8 i {}; i < Ns; ++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 || i == 3 ? 0 : 1;
|
||||
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> add_headers(Stream& stream, JPEGEncodingContext& context, JPEGWriter::Options const& options, IntSize size, Mode mode)
|
||||
{
|
||||
context.set_luminance_quantization_table(s_default_luminance_quantization_table, options.quality);
|
||||
context.set_chrominance_quantization_table(s_default_chrominance_quantization_table, options.quality);
|
||||
|
||||
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));
|
||||
|
||||
if (options.icc_data.has_value())
|
||||
TRY(add_icc_data(stream, options.icc_data.value()));
|
||||
|
||||
if (mode == Mode::CMYK)
|
||||
TRY(add_ycck_color_transform_header(stream));
|
||||
TRY(add_frame_header(stream, context, size, mode));
|
||||
|
||||
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, mode));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> add_image(Stream& stream, JPEGEncodingContext& context, Mode mode)
|
||||
{
|
||||
context.fdct_and_quantization(mode);
|
||||
TRY(context.write_huffman_stream(mode));
|
||||
TRY(add_end_of_image(stream));
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ErrorOr<void> JPEGWriter::encode(Stream& stream, Bitmap const& bitmap, Options const& options)
|
||||
{
|
||||
JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } };
|
||||
TRY(add_headers(stream, context, options, bitmap.size(), Mode::RGB));
|
||||
TRY(context.initialize_mcu(bitmap));
|
||||
TRY(add_image(stream, context, Mode::RGB));
|
||||
return {};
|
||||
return encode_impl(stream, bitmap, options, ColorSpace::RGB);
|
||||
}
|
||||
|
||||
ErrorOr<void> JPEGWriter::encode(Stream& stream, CMYKBitmap const& bitmap, Options const& options)
|
||||
{
|
||||
JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } };
|
||||
TRY(add_headers(stream, context, options, bitmap.size(), Mode::CMYK));
|
||||
TRY(context.initialize_mcu(bitmap));
|
||||
TRY(add_image(stream, context, Mode::CMYK));
|
||||
return {};
|
||||
return encode_impl(stream, bitmap, options, ColorSpace::CMYK);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,12 @@ public:
|
|||
static ErrorOr<void> encode(Stream&, CMYKBitmap const&, Options const& = {});
|
||||
|
||||
private:
|
||||
enum class ColorSpace {
|
||||
RGB,
|
||||
CMYK,
|
||||
};
|
||||
static ErrorOr<void> encode_impl(Stream&, auto const&, Options const&, ColorSpace);
|
||||
|
||||
JPEGWriter() = delete;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,457 +0,0 @@
|
|||
/*
|
||||
* 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,
|
||||
};
|
||||
|
||||
}
|
|
@ -10,7 +10,6 @@ set(CMD_SOURCES
|
|||
js.cpp
|
||||
lzcat.cpp
|
||||
tar.cpp
|
||||
test-jpeg-roundtrip.cpp
|
||||
ttfdisasm.cpp
|
||||
unzip.cpp
|
||||
wasm.cpp
|
||||
|
@ -43,7 +42,6 @@ target_link_libraries(isobmff PRIVATE LibGfx)
|
|||
target_link_libraries(js PRIVATE LibCrypto LibJS LibLine LibLocale LibTextCodec)
|
||||
target_link_libraries(lzcat PRIVATE LibCompress)
|
||||
target_link_libraries(tar PRIVATE LibArchive LibCompress LibFileSystem)
|
||||
target_link_libraries(test-jpeg-roundtrip PRIVATE LibGfx)
|
||||
target_link_libraries(ttfdisasm PRIVATE LibGfx)
|
||||
target_link_libraries(unzip PRIVATE LibArchive LibCompress LibCrypto LibFileSystem)
|
||||
target_link_libraries(wasm PRIVATE LibFileSystem LibJS LibLine LibWasm)
|
||||
|
|
|
@ -157,17 +157,6 @@ static ErrorOr<void> save_image(LoadedImage& image, StringView out_path, bool pp
|
|||
return Core::OutputBufferedFile::create(move(output_stream));
|
||||
};
|
||||
|
||||
if (image.bitmap.has<RefPtr<Gfx::CMYKBitmap>>()) {
|
||||
auto& cmyk_frame = image.bitmap.get<RefPtr<Gfx::CMYKBitmap>>();
|
||||
|
||||
if (out_path.ends_with(".jpg"sv, CaseSensitivity::CaseInsensitive) || out_path.ends_with(".jpeg"sv, CaseSensitivity::CaseInsensitive)) {
|
||||
TRY(Gfx::JPEGWriter::encode(*TRY(stream()), *cmyk_frame, { .icc_data = image.icc_data, .quality = jpeg_quality }));
|
||||
return {};
|
||||
}
|
||||
|
||||
return Error::from_string_view("Can save CMYK bitmaps only as .jpg, convert to RGB first with --convert-to-color-profile"sv);
|
||||
}
|
||||
|
||||
auto& frame = image.bitmap.get<RefPtr<Gfx::Bitmap>>();
|
||||
|
||||
if (out_path.ends_with(".gif"sv, CaseSensitivity::CaseInsensitive)) {
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/DeltaE.h>
|
||||
#include <LibGfx/ICC/Profile.h>
|
||||
#include <LibGfx/ICC/WellKnownProfiles.h>
|
||||
#include <LibGfx/ImageFormats/JPEGLoader.h>
|
||||
#include <LibGfx/ImageFormats/JPEGWriter.h>
|
||||
#include <LibMain/Main.h>
|
||||
|
||||
struct Fixpoint {
|
||||
Gfx::Color fixpoint;
|
||||
int number_of_iterations {};
|
||||
};
|
||||
|
||||
static ErrorOr<Fixpoint> compute_fixpoint(Gfx::Color start_color)
|
||||
{
|
||||
auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { 8, 8 }));
|
||||
bitmap->fill(start_color);
|
||||
|
||||
int number_of_iterations = 1;
|
||||
Color last_color = start_color;
|
||||
while (true) {
|
||||
AllocatingMemoryStream stream;
|
||||
TRY(Gfx::JPEGWriter::encode(stream, *bitmap));
|
||||
auto data = TRY(stream.read_until_eof());
|
||||
auto plugin_decoder = TRY(Gfx::JPEGImageDecoderPlugin::create(data));
|
||||
auto frame = TRY(plugin_decoder->frame(0));
|
||||
|
||||
Color current_color = frame.image->get_pixel(4, 4);
|
||||
if (current_color == last_color)
|
||||
break;
|
||||
|
||||
++number_of_iterations;
|
||||
last_color = current_color;
|
||||
bitmap = *frame.image;
|
||||
}
|
||||
return Fixpoint { last_color, number_of_iterations };
|
||||
}
|
||||
|
||||
static ErrorOr<float> perceived_distance_in_sRGB(Gfx::Color a, Gfx::Color b)
|
||||
{
|
||||
auto sRGB = TRY(Gfx::ICC::sRGB());
|
||||
|
||||
Array<u8, 3> array_a { a.red(), a.green(), a.blue() };
|
||||
Array<u8, 3> array_b { b.red(), b.green(), b.blue() };
|
||||
|
||||
return DeltaE(TRY(sRGB->to_lab(array_a)), TRY(sRGB->to_lab(array_b)));
|
||||
}
|
||||
|
||||
struct Stats {
|
||||
float max_delta {};
|
||||
int max_number_of_iterations {};
|
||||
};
|
||||
|
||||
static ErrorOr<void> test(Gfx::Color color, Stats& stats)
|
||||
{
|
||||
auto fixpoint = TRY(compute_fixpoint(color));
|
||||
|
||||
float perceived_distance = TRY(perceived_distance_in_sRGB(color, fixpoint.fixpoint));
|
||||
|
||||
outln("color {} converges to {} after saving {} times, delta {}", color, fixpoint.fixpoint, fixpoint.number_of_iterations, perceived_distance);
|
||||
|
||||
stats.max_delta = max(stats.max_delta, perceived_distance);
|
||||
stats.max_number_of_iterations = max(stats.max_number_of_iterations, fixpoint.number_of_iterations);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<int> serenity_main(Main::Arguments)
|
||||
{
|
||||
Stats stats;
|
||||
|
||||
TRY(test(Gfx::Color::Red, stats));
|
||||
TRY(test(Gfx::Color::Green, stats));
|
||||
TRY(test(Gfx::Color::Blue, stats));
|
||||
|
||||
TRY(test(Gfx::Color::LightBlue, stats));
|
||||
|
||||
TRY(test(Gfx::Color::MidRed, stats));
|
||||
TRY(test(Gfx::Color::MidGreen, stats));
|
||||
TRY(test(Gfx::Color::MidBlue, stats));
|
||||
|
||||
TRY(test(Gfx::Color::DarkRed, stats));
|
||||
TRY(test(Gfx::Color::DarkGreen, stats));
|
||||
TRY(test(Gfx::Color::DarkBlue, stats));
|
||||
|
||||
TRY(test(Gfx::Color::Cyan, stats));
|
||||
TRY(test(Gfx::Color::Magenta, stats));
|
||||
TRY(test(Gfx::Color::Yellow, stats));
|
||||
|
||||
TRY(test(Gfx::Color::Black, stats));
|
||||
TRY(test(Gfx::Color::DarkGray, stats));
|
||||
TRY(test(Gfx::Color::MidGray, stats));
|
||||
TRY(test(Gfx::Color::LightGray, stats));
|
||||
TRY(test(Gfx::Color::White, stats));
|
||||
|
||||
outln();
|
||||
outln("max delta {}, max number of iterations {}", stats.max_delta, stats.max_number_of_iterations);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
"platform": "linux | freebsd | openbsd"
|
||||
},
|
||||
"icu",
|
||||
"libjpeg-turbo",
|
||||
"sqlite3",
|
||||
"woff2"
|
||||
],
|
||||
|
@ -18,6 +19,10 @@
|
|||
"name": "icu",
|
||||
"version": "74.2#1"
|
||||
},
|
||||
{
|
||||
"name": "libjpeg-turbo",
|
||||
"version": "3.0.2"
|
||||
},
|
||||
{
|
||||
"name": "sqlite3",
|
||||
"version": "3.45.3"
|
||||
|
|
Loading…
Reference in a new issue