Sfoglia il codice sorgente

LibGfx: Remove home-grown JPEG codec in favor of libjpeg-turbo

Andreas Kling 1 anno fa
parent
commit
2a888ca626

+ 0 - 4
AK/Debug.h.in

@@ -134,10 +134,6 @@
 #    cmakedefine01 JOB_DEBUG
 #endif
 
-#ifndef JPEG_DEBUG
-#    cmakedefine01 JPEG_DEBUG
-#endif
-
 #ifndef JPEG2000_DEBUG
 #    cmakedefine01 JPEG2000_DEBUG
 #endif

+ 0 - 1
Meta/CMake/all_the_debug_macros.cmake

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

+ 0 - 5
Meta/Lagom/CMakeLists.txt

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

+ 0 - 1
Meta/gn/secondary/AK/BUILD.gn

@@ -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=",

+ 3 - 20
Tests/LibGfx/TestImageDecoder.cpp

@@ -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());
     }
 }
 

BIN
Tests/LibGfx/test-inputs/jpg/12-bit-progressive.jpg


BIN
Tests/LibGfx/test-inputs/jpg/12-bit.jpg


+ 5 - 0
Userland/Libraries/LibGfx/CMakeLists.txt

@@ -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
+ 84 - 1943
Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp


+ 2 - 18
Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.h

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

+ 0 - 111
Userland/Libraries/LibGfx/ImageFormats/JPEGShared.h

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

+ 71 - 614
Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.cpp

@@ -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 {
-
-enum Mode {
-    RGB,
-    CMYK,
-};
-
-// 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 {};
+struct MemoryDestinationManager : public jpeg_destination_mgr {
+    Vector<u8>& buffer;
+    static constexpr size_t BUFFER_SIZE_INCREMENT = 65536;
+
+    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;
     }
-
-    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));
-    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;
+    struct jpeg_compress_struct cinfo { };
+    struct jpeg_error_mgr jerr { };
 
-    // 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);
+    cinfo.err = jpeg_std_error(&jerr);
 
-    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;
+    jpeg_create_compress(&cinfo);
 
-    // "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);
+    Vector<u8> buffer;
+    MemoryDestinationManager dest_manager(buffer);
+    cinfo.dest = &dest_manager;
 
-    if (icc_data.size() > max_icc_data_size)
-        return Error::from_string_view("JPEGWriter: icc data too large for jpeg format"sv);
+    cinfo.image_width = bitmap.size().width();
+    cinfo.image_height = bitmap.size().height();
+    cinfo.input_components = 4;
 
-    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);
+    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();
     }
-    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));
+    jpeg_set_defaults(&cinfo);
+    jpeg_set_colorspace(&cinfo, JCS_YCbCr);
+    jpeg_set_quality(&cinfo, options.quality, TRUE);
 
-    // 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));
+    if (options.icc_data.has_value()) {
+        jpeg_write_icc_profile(&cinfo, options.icc_data->data(), options.icc_data->size());
     }
 
-    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));
+    jpeg_start_compress(&cinfo, TRUE);
 
-    TRY(stream.write_until_depleted("Adobe\0"sv.bytes()));
+    Vector<JSAMPLE> row_buffer;
+    row_buffer.resize(bitmap.size().width() * 4);
 
-    // 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));
+    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);
     }
 
-    // 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;
+    jpeg_finish_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
 
-    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));
+    TRY(stream.write_until_depleted(buffer));
     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);
 }
 
 }

+ 6 - 0
Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.h

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

+ 0 - 457
Userland/Libraries/LibGfx/ImageFormats/JPEGWriterTables.h

@@ -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,
-};
-
-}

+ 0 - 2
Userland/Utilities/CMakeLists.txt

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

+ 0 - 11
Userland/Utilities/image.cpp

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

+ 0 - 105
Userland/Utilities/test-jpeg-roundtrip.cpp

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

+ 5 - 0
vcpkg.json

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

Some files were not shown because too many files changed in this diff