From b45a4508c7f277941fd4ac25b29c5896f2bc9a97 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Thu, 21 Mar 2024 08:41:37 -0400 Subject: [PATCH] LibGfx/JBIG2: Implement support for context templates 1, 2, and 3 Template 2 is needed by some symbols in 0000372.pdf page 11 and 0000857.pdf pages 1-4. Implement the others too while here. (The mentioned pages in those two PDFs also use the "end of stripe" segment, so they still don't render yet. We still don't support EXTTEMPLATE. --- Tests/LibGfx/TestImageDecoder.cpp | 6 + .../LibGfx/ImageFormats/JBIG2Loader.cpp | 108 +++++++++++++++--- 2 files changed, 100 insertions(+), 14 deletions(-) diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index a81b227feac..c9eae20a317 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -356,6 +356,12 @@ TEST_CASE(test_jbig2_decode) Array test_inputs = { TEST_INPUT("jbig2/bitmap.jbig2"sv), TEST_INPUT("jbig2/bitmap-tpgdon.jbig2"sv), + TEST_INPUT("jbig2/bitmap-template1.jbig2"sv), + TEST_INPUT("jbig2/bitmap-template1-tpgdon.jbig2"sv), + TEST_INPUT("jbig2/bitmap-template2.jbig2"sv), + TEST_INPUT("jbig2/bitmap-template2-tpgdon.jbig2"sv), + TEST_INPUT("jbig2/bitmap-template3.jbig2"sv), + TEST_INPUT("jbig2/bitmap-template3-tpgdon.jbig2"sv), TEST_INPUT("jbig2/bitmap-symbol.jbig2"sv), }; diff --git a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp index 2d6741cd8a7..f02c86438b3 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp @@ -292,6 +292,16 @@ Optional ArithmeticIntegerDecoder::decode() } +static u8 number_of_context_bits_for_template(u8 template_) +{ + if (template_ == 0) + return 16; + if (template_ == 1) + return 13; + VERIFY(template_ == 2 || template_ == 3); + return 10; +} + // JBIG2 spec, Annex D, D.4.1 ID string static constexpr u8 id_string[] = { 0x97, 0x4A, 0x42, 0x32, 0x0D, 0x0A, 0x1A, 0x0A }; @@ -879,18 +889,24 @@ static ErrorOr> generic_region_decoding_procedure(Gener } // 6.2.5 Decoding using a template and arithmetic coding - if (inputs.gb_template != 0) - return Error::from_string_literal("JBIG2ImageDecoderPlugin: Cannot decode GBTEMPLATE != 0 yet"); - - if (inputs.adaptive_template_pixels[0].x != 3 || inputs.adaptive_template_pixels[0].y != -1 - || inputs.adaptive_template_pixels[1].x != -3 || inputs.adaptive_template_pixels[1].y != -1 - || inputs.adaptive_template_pixels[2].x != 2 || inputs.adaptive_template_pixels[2].y != -2 - || inputs.adaptive_template_pixels[3].x != -2 || inputs.adaptive_template_pixels[3].y != -2) - return Error::from_string_literal("JBIG2ImageDecoderPlugin: Cannot handle custom adaptive pixels yet"); - if (inputs.is_extended_reference_template_used) return Error::from_string_literal("JBIG2ImageDecoderPlugin: Cannot decode EXTTEMPLATE yet"); + if (inputs.gb_template == 0) { + if (inputs.adaptive_template_pixels[0].x != 3 || inputs.adaptive_template_pixels[0].y != -1 + || inputs.adaptive_template_pixels[1].x != -3 || inputs.adaptive_template_pixels[1].y != -1 + || inputs.adaptive_template_pixels[2].x != 2 || inputs.adaptive_template_pixels[2].y != -2 + || inputs.adaptive_template_pixels[3].x != -2 || inputs.adaptive_template_pixels[3].y != -2) + return Error::from_string_literal("JBIG2ImageDecoderPlugin: Cannot handle custom adaptive pixels yet"); + } else if (inputs.gb_template == 1) { + if (inputs.adaptive_template_pixels[0].x != 3 || inputs.adaptive_template_pixels[0].y != -1) + return Error::from_string_literal("JBIG2ImageDecoderPlugin: Cannot handle custom adaptive pixels yet"); + } else { + VERIFY(inputs.gb_template == 2 || inputs.gb_template == 3); + if (inputs.adaptive_template_pixels[0].x != 2 || inputs.adaptive_template_pixels[0].y != -1) + return Error::from_string_literal("JBIG2ImageDecoderPlugin: Cannot handle custom adaptive pixels yet"); + } + if (inputs.skip_pattern.has_value()) return Error::from_string_literal("JBIG2ImageDecoderPlugin: Cannot decode USESKIP yet"); @@ -903,7 +919,7 @@ static ErrorOr> generic_region_decoding_procedure(Gener }; // Figure 3(a) – Template when GBTEMPLATE = 0 and EXTTEMPLATE = 0, - auto compute_context = [&get_pixel](NonnullOwnPtr const& buffer, int x, int y) -> u16 { + auto compute_context_0 = [&get_pixel](NonnullOwnPtr const& buffer, int x, int y) -> u16 { u16 result = 0; for (int i = 0; i < 5; ++i) result = (result << 1) | (u16)get_pixel(buffer, x - 2 + i, y - 2); @@ -914,6 +930,51 @@ static ErrorOr> generic_region_decoding_procedure(Gener return result; }; + // Figure 4 – Template when GBTEMPLATE = 1 + auto compute_context_1 = [&get_pixel](NonnullOwnPtr const& buffer, int x, int y) -> u16 { + u16 result = 0; + for (int i = 0; i < 4; ++i) + result = (result << 1) | (u16)get_pixel(buffer, x - 1 + i, y - 2); + for (int i = 0; i < 6; ++i) + result = (result << 1) | (u16)get_pixel(buffer, x - 2 + i, y - 1); + for (int i = 0; i < 3; ++i) + result = (result << 1) | (u16)get_pixel(buffer, x - 3 + i, y); + return result; + }; + + // Figure 5 – Template when GBTEMPLATE = 2 + auto compute_context_2 = [&get_pixel](NonnullOwnPtr const& buffer, int x, int y) -> u16 { + u16 result = 0; + for (int i = 0; i < 3; ++i) + result = (result << 1) | (u16)get_pixel(buffer, x - 1 + i, y - 2); + for (int i = 0; i < 5; ++i) + result = (result << 1) | (u16)get_pixel(buffer, x - 2 + i, y - 1); + for (int i = 0; i < 2; ++i) + result = (result << 1) | (u16)get_pixel(buffer, x - 2 + i, y); + return result; + }; + + // Figure 6 – Template when GBTEMPLATE = 3 + auto compute_context_3 = [&get_pixel](NonnullOwnPtr const& buffer, int x, int y) -> u16 { + u16 result = 0; + for (int i = 0; i < 6; ++i) + result = (result << 1) | (u16)get_pixel(buffer, x - 3 + i, y - 1); + for (int i = 0; i < 4; ++i) + result = (result << 1) | (u16)get_pixel(buffer, x - 4 + i, y); + return result; + }; + + auto compute_context = [&inputs, &compute_context_0, &compute_context_1, &compute_context_2, &compute_context_3](NonnullOwnPtr const& buffer, int x, int y) -> u16 { + if (inputs.gb_template == 0) + return compute_context_0(buffer, x, y); + if (inputs.gb_template == 1) + return compute_context_1(buffer, x, y); + if (inputs.gb_template == 2) + return compute_context_2(buffer, x, y); + VERIFY(inputs.gb_template == 3); + return compute_context_3(buffer, x, y); + }; + // "The values of the pixels in this neighbourhood define a context. Each context has its own adaptive probability estimate // used by the arithmetic coder (see Annex E)." // "* Decode the current pixel by invoking the arithmetic entropy decoding procedure, with CX set to the value formed by @@ -925,13 +986,33 @@ static ErrorOr> generic_region_decoding_procedure(Gener // Figure 8 – Reused context for coding the SLTP value when GBTEMPLATE is 0 constexpr u16 sltp_context_for_template_0 = 0b10011'0110010'0101; + // Figure 9 – Reused context for coding the SLTP value when GBTEMPLATE is 1 + constexpr u16 sltp_context_for_template_1 = 0b0011'110010'101; + + // Figure 10 – Reused context for coding the SLTP value when GBTEMPLATE is 2 + constexpr u16 sltp_context_for_template_2 = 0b001'11001'01; + + // Figure 11 – Reused context for coding the SLTP value when GBTEMPLATE is 3 + constexpr u16 sltp_context_for_template_3 = 0b011001'0101; + + u16 sltp_context = [](u8 gb_template) { + if (gb_template == 0) + return sltp_context_for_template_0; + if (gb_template == 1) + return sltp_context_for_template_1; + if (gb_template == 2) + return sltp_context_for_template_2; + VERIFY(gb_template == 3); + return sltp_context_for_template_3; + }(inputs.gb_template); + // 6.2.5.7 Decoding the bitmap JBIG2::ArithmeticDecoder& decoder = *inputs.arithmetic_decoder; bool ltp = false; // "LTP" in spec. "Line (uses) Typical Prediction" maybe? for (size_t y = 0; y < inputs.region_height; ++y) { if (inputs.is_typical_prediction_used) { // "SLTP" in spec. "Swap LTP" or "Switch LTP" maybe? - bool sltp = decoder.get_next_bit(contexts[sltp_context_for_template_0]); + bool sltp = decoder.get_next_bit(contexts[sltp_context]); ltp = ltp ^ sltp; if (ltp) { for (size_t x = 0; x < inputs.region_width; ++x) @@ -1383,8 +1464,7 @@ static ErrorOr>> symbol_dictionary_decoding_procedu auto decoder = TRY(JBIG2::ArithmeticDecoder::initialize(data)); Vector contexts; - u8 template_size = inputs.symbol_template == 0 ? 16 : (inputs.symbol_template == 1 ? 13 : 10); - contexts.resize(1 << template_size); + contexts.resize(1 << number_of_context_bits_for_template(inputs.symbol_template)); // 6.5.6 Height class delta height // "If SDHUFF is 1, decode a value using the Huffman table specified by SDHUFFDH. @@ -1875,7 +1955,7 @@ static ErrorOr decode_immediate_generic_region(JBIG2LoadingContext& contex // Done above. // "2) As described in E.3.7, reset all the arithmetic coding statistics to zero." Vector contexts; - contexts.resize(1 << 16); + contexts.resize(1 << number_of_context_bits_for_template(arithmetic_coding_template)); // "3) Invoke the generic region decoding procedure described in 6.2, with the parameters to the generic region decoding procedure set as shown in Table 37." GenericRegionDecodingInputParameters inputs;