diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index d33d6494105b1d..1b5e97a759446d 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -366,6 +366,8 @@ TEST_CASE(test_jbig2_decode) TEST_INPUT("jbig2/bitmap-trailing-7fff-stripped-harder.jbig2"sv), TEST_INPUT("jbig2/bitmap-trailing-7fff-stripped-harder-refine.jbig2"sv), TEST_INPUT("jbig2/bitmap-refine.jbig2"sv), + TEST_INPUT("jbig2/bitmap-refine-page.jbig2"sv), + TEST_INPUT("jbig2/bitmap-refine-page-subrect.jbig2"sv), TEST_INPUT("jbig2/bitmap-refine-customat.jbig2"sv), TEST_INPUT("jbig2/bitmap-refine-lossless.jbig2"sv), TEST_INPUT("jbig2/bitmap-refine-refine.jbig2"sv), @@ -420,7 +422,6 @@ TEST_CASE(test_jbig2_decode) // - intermediate direct regions (code support added in #26197) // - symbol refinement referring to symbol in same segment // Missing tests for things that aren't implemented yet: - // - immediate refinement regions not referring to a direct region (i.e. refining the page) // - exttemplate // - colors }; diff --git a/Tests/LibGfx/test-inputs/jbig2/bitmap-refine-page-subrect.jbig2 b/Tests/LibGfx/test-inputs/jbig2/bitmap-refine-page-subrect.jbig2 new file mode 100644 index 00000000000000..04d00fcd662f52 Binary files /dev/null and b/Tests/LibGfx/test-inputs/jbig2/bitmap-refine-page-subrect.jbig2 differ diff --git a/Tests/LibGfx/test-inputs/jbig2/bitmap-refine-page.jbig2 b/Tests/LibGfx/test-inputs/jbig2/bitmap-refine-page.jbig2 new file mode 100644 index 00000000000000..87c9dc1c975c03 Binary files /dev/null and b/Tests/LibGfx/test-inputs/jbig2/bitmap-refine-page.jbig2 differ diff --git a/Tests/LibGfx/test-inputs/jbig2/json/bitmap-refine-page-subrect.json b/Tests/LibGfx/test-inputs/jbig2/json/bitmap-refine-page-subrect.json new file mode 100644 index 00000000000000..0a04f558103532 --- /dev/null +++ b/Tests/LibGfx/test-inputs/jbig2/json/bitmap-refine-page-subrect.json @@ -0,0 +1,60 @@ +{ + "global_header": { + "organization": "sequential", + "number_of_pages": 1 + }, + "segments": [ + { + "segment_number": 0, + "type": "page_information", + "page_association": 1, + "data": { + "page_width": 399, + "page_height": 400, + "flags": { + "direct_region_segments_override_default_combination_operator": true + } + } + }, + { + "segment_number": 1, + "type": "generic_region", + "page_association": 1, + "data": { + "image_data": { + "from_file": "bitmap-blemish.bmp" + } + } + }, + { + "segment_number": 2, + "type": "generic_refinement_region", + "page_association": 1, + "data": { + "region_segment_information": { + "x": 10, + "y": 20, + "width": 110, + "height": 380, + "flags": { + "external_combination_operator": "replace" + } + }, + "image_data": { + "from_file": "bitmap.bmp", + "crop": { + "x": 10, + "y": 20, + "width": 110, + "height": 380 + } + } + } + }, + { + "segment_number": 3, + "type": "end_of_page", + "page_association": 1 + } + ] +} diff --git a/Tests/LibGfx/test-inputs/jbig2/json/bitmap-refine-page.json b/Tests/LibGfx/test-inputs/jbig2/json/bitmap-refine-page.json new file mode 100644 index 00000000000000..f9781934924fae --- /dev/null +++ b/Tests/LibGfx/test-inputs/jbig2/json/bitmap-refine-page.json @@ -0,0 +1,50 @@ +{ + "global_header": { + "organization": "sequential", + "number_of_pages": 1 + }, + "segments": [ + { + "segment_number": 0, + "type": "page_information", + "page_association": 1, + "data": { + "page_width": 399, + "page_height": 400, + "flags": { + "direct_region_segments_override_default_combination_operator": true + } + } + }, + { + "segment_number": 1, + "type": "generic_region", + "page_association": 1, + "data": { + "image_data": { + "from_file": "bitmap-blemish.bmp" + } + } + }, + { + "segment_number": 2, + "type": "generic_refinement_region", + "page_association": 1, + "data": { + "region_segment_information": { + "flags": { + "external_combination_operator": "replace" + } + }, + "image_data": { + "from_file": "bitmap.bmp" + } + } + }, + { + "segment_number": 3, + "type": "end_of_page", + "page_association": 1 + } + ] +} diff --git a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp index d26523cceb1374..1cba3d8d3740d9 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp @@ -3190,16 +3190,19 @@ static ErrorOr decode_generic_refinement_region(JBIG2LoadingContex // "3) Determine the buffer associated with the region segment that this segment refers to." // Details described in 7.4.7.4 Reference bitmap selection. - BilevelImage const* reference_bitmap = nullptr; - if (segment.referred_to_segments.size() == 1) { - reference_bitmap = segment.referred_to_segments[0]->aux_buffer.ptr(); - VERIFY(reference_bitmap->width() == segment.referred_to_segments[0]->aux_buffer_information_field.width); - VERIFY(reference_bitmap->height() == segment.referred_to_segments[0]->aux_buffer_information_field.height); - } else { - // When adding support for this and for intermediate generic refinement regions, make sure to only allow - // this case for immediate generic refinement regions. - return Error::from_string_literal("JBIG2ImageDecoderPlugin: Generic refinement region without reference segment not yet implemented"); - } + BilevelSubImage reference_bitmap = [&]() { + if (segment.referred_to_segments.size() == 1) { + auto reference_bitmap = segment.referred_to_segments[0]->aux_buffer; + VERIFY(reference_bitmap->width() == segment.referred_to_segments[0]->aux_buffer_information_field.width); + VERIFY(reference_bitmap->height() == segment.referred_to_segments[0]->aux_buffer_information_field.height); + return reference_bitmap->as_subbitmap(); + } + + // Enforced by validate_segment_header_references() earlier. + VERIFY(segment.type() != JBIG2::SegmentType::IntermediateGenericRefinementRegion); + + return context.page.bits->subbitmap(information_field.rect()); + }(); // "4) Invoke the generic refinement region decoding procedure described in 6.3, with the parameters to the // generic refinement region decoding procedure set as shown in Table 38." @@ -3208,8 +3211,7 @@ static ErrorOr decode_generic_refinement_region(JBIG2LoadingContex inputs.region_width = information_field.width; inputs.region_height = information_field.height; inputs.gr_template = arithmetic_coding_template; - auto subbitmap = reference_bitmap->as_subbitmap(); - inputs.reference_bitmap = &subbitmap; + inputs.reference_bitmap = &reference_bitmap; inputs.reference_x_offset = 0; inputs.reference_y_offset = 0; inputs.is_typical_prediction_used = typical_prediction_generic_refinement_on; @@ -3666,7 +3668,7 @@ ErrorOr JBIG2ImageDecoderPlugin::frame(size_t index, Optio return ImageFrameDescriptor { move(bitmap), 0 }; } -ErrorOr JBIG2ImageDecoderPlugin::decode_embedded(Vector data) +ErrorOr> JBIG2ImageDecoderPlugin::decode_embedded(Vector data) { auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) JBIG2ImageDecoderPlugin({}))); plugin->m_context->organization = JBIG2::Organization::Embedded; @@ -3683,7 +3685,7 @@ ErrorOr JBIG2ImageDecoderPlugin::decode_embedded(Vectorm_context)); - return plugin->m_context->page.bits->to_byte_buffer(); + return *plugin->m_context->page.bits; } } diff --git a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.h b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.h index 20395377a7512e..02784bbb2115e4 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.h +++ b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.h @@ -12,6 +12,7 @@ namespace Gfx { +class BilevelImage; struct JBIG2LoadingContext; struct MQArithmeticCoderContext; class MQArithmeticDecoder; @@ -69,7 +70,7 @@ class JBIG2ImageDecoderPlugin : public ImageDecoderPlugin { virtual size_t frame_count() override; virtual ErrorOr frame(size_t index, Optional ideal_size = {}) override; - static ErrorOr decode_embedded(Vector); + static ErrorOr> decode_embedded(Vector); private: JBIG2ImageDecoderPlugin(JBIG2DecoderOptions); diff --git a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Shared.h b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Shared.h index 76faa9cc682208..3bf3958a156250 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Shared.h +++ b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Shared.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace Gfx::JBIG2 { @@ -118,6 +119,11 @@ struct [[gnu::packed]] RegionSegmentInformationField { BigEndian y_location; u8 flags { 0 }; + IntRect rect() const + { + return { x_location, y_location, width, height }; + } + CombinationOperator external_combination_operator() const { VERIFY((flags & 0x7) <= 4); diff --git a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Writer.cpp b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Writer.cpp index e4ff5d689d0a63..3fa749dbba088e 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JBIG2Writer.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/JBIG2Writer.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -337,7 +338,7 @@ namespace { struct GenericRefinementRegionEncodingInputParameters { BilevelImage const& image; // Of dimensions "GRW" x "GRH" in spec terms. u8 gr_template { 0 }; // "GRTEMPLATE" in spec. - BilevelImage const* reference_bitmap { nullptr }; // "GRREFERENCE" in spec. + BilevelSubImage reference_bitmap; // "GRREFERENCE" in spec. i32 reference_x_offset { 0 }; // "GRREFERENCEDX" in spec. i32 reference_y_offset { 0 }; // "GRREFERENCEDY" in spec. bool is_typical_prediction_used { false }; // "TPGRON" in spec. @@ -378,7 +379,7 @@ static ErrorOr generic_refinement_region_encoding_procedure(GenericRefinem }; // Figure 12 – 13-pixel refinement template showing the AT pixels at their nominal locations - constexpr auto compute_context_0 = [](ReadonlySpan adaptive_pixels, BilevelImage const& reference, int reference_x, int reference_y, BilevelImage const& buffer, int x, int y) -> u16 { + constexpr auto compute_context_0 = [](ReadonlySpan adaptive_pixels, BilevelSubImage const& reference, int reference_x, int reference_y, BilevelImage const& buffer, int x, int y) -> u16 { u16 result = 0; for (int dy = -1; dy <= 1; ++dy) { @@ -399,7 +400,7 @@ static ErrorOr generic_refinement_region_encoding_procedure(GenericRefinem }; // Figure 13 – 10-pixel refinement template - constexpr auto compute_context_1 = [](ReadonlySpan, BilevelImage const& reference, int reference_x, int reference_y, BilevelImage const& buffer, int x, int y) -> u16 { + constexpr auto compute_context_1 = [](ReadonlySpan, BilevelSubImage const& reference, int reference_x, int reference_y, BilevelImage const& buffer, int x, int y) -> u16 { u16 result = 0; for (int dy = -1; dy <= 1; ++dy) { @@ -440,10 +441,10 @@ static ErrorOr generic_refinement_region_encoding_procedure(GenericRefinem auto predict = [&](size_t x, size_t y) -> Optional { // "• a 3 × 3 pixel array in the reference bitmap (Figure 16), centred at the location // corresponding to the current pixel, contains pixels all of the same value." - bool prediction = get_pixel(*inputs.reference_bitmap, x - inputs.reference_x_offset - 1, y - inputs.reference_y_offset - 1); + bool prediction = get_pixel(inputs.reference_bitmap, x - inputs.reference_x_offset - 1, y - inputs.reference_y_offset - 1); for (int dy = -1; dy <= 1; ++dy) for (int dx = -1; dx <= 1; ++dx) - if (get_pixel(*inputs.reference_bitmap, x - inputs.reference_x_offset + dx, y - inputs.reference_y_offset + dy) != prediction) + if (get_pixel(inputs.reference_bitmap, x - inputs.reference_x_offset + dx, y - inputs.reference_y_offset + dy) != prediction) return {}; return prediction; }; @@ -471,7 +472,7 @@ static ErrorOr generic_refinement_region_encoding_procedure(GenericRefinem // "c) If LTP = 0 then, from left to right, explicitly decode all pixels of the current row of GRREG. The // procedure for each pixel is as follows:" for (size_t x = 0; x < width; ++x) { - u16 context = compute_context(inputs.adaptive_template_pixels, *inputs.reference_bitmap, x - inputs.reference_x_offset, y - inputs.reference_y_offset, inputs.image, x, y); + u16 context = compute_context(inputs.adaptive_template_pixels, inputs.reference_bitmap, x - inputs.reference_x_offset, y - inputs.reference_y_offset, inputs.image, x, y); encoder.encode_bit(inputs.image.get_bit(x, y), contexts.contexts[context]); } } else { @@ -484,7 +485,7 @@ static ErrorOr generic_refinement_region_encoding_procedure(GenericRefinem // TPGRON must be 1 if LTP is set. (The spec has an explicit "TPGRON is 1 AND" check here, but it is pointless.) VERIFY(inputs.is_typical_prediction_used); if (!prediction.has_value()) { - u16 context = compute_context(inputs.adaptive_template_pixels, *inputs.reference_bitmap, x - inputs.reference_x_offset, y - inputs.reference_y_offset, inputs.image, x, y); + u16 context = compute_context(inputs.adaptive_template_pixels, inputs.reference_bitmap, x - inputs.reference_x_offset, y - inputs.reference_y_offset, inputs.image, x, y); encoder.encode_bit(inputs.image.get_bit(x, y), contexts.contexts[context]); } } @@ -726,7 +727,7 @@ static ErrorOr text_region_encoding_procedure(TextRegionEncodingInputParam auto reference_bitmap = TRY(symbol_image(symbol)); GenericRefinementRegionEncodingInputParameters refinement_inputs { .image = symbol_instance.refinement_data->refines_to, - .reference_bitmap = reference_bitmap, + .reference_bitmap = reference_bitmap->as_subbitmap(), }; // FIXME: Instead, just compute the delta here instead of having it be passed in? @@ -1136,7 +1137,7 @@ static ErrorOr symbol_dictionary_encoding_procedure(SymbolDictionary // Table 18 – Parameters used to decode a symbol's bitmap when REFAGGNINST = 1 GenericRefinementRegionEncodingInputParameters refinement_inputs { .image = *refinement_image.refines_to, - .reference_bitmap = IBO, + .reference_bitmap = IBO->as_subbitmap(), }; refinement_inputs.gr_template = inputs.refinement_template; refinement_inputs.reference_x_offset = refinement_image.delta_x_offset; @@ -1543,6 +1544,13 @@ struct SerializedSegmentData { }; struct JBIG2EncodingContext { + JBIG2EncodingContext(Vector const& segments) + : segments(segments) + { + } + + Vector const& segments; + HashMap segment_by_id; HashMap segment_data_by_id; @@ -2266,24 +2274,46 @@ static ErrorOr encode_generic_refinement_region(JBIG2::GenericRefinementRe // 7.4.7 Generic refinement region syntax if (header.referred_to_segments.size() > 1) return Error::from_string_literal("JBIG2Writer: Generic refinement region must refer to at most one segment"); - if (header.referred_to_segments.size() == 0) - return Error::from_string_literal("JBIG2Writer: Generic refinement region refining page not yet implemented"); - auto maybe_segment = context.segment_by_id.get(header.referred_to_segments[0].segment_number); - if (!maybe_segment.has_value()) - return Error::from_string_literal("JBIG2Writer: Could not find referred-to segment for generic refinement region"); - auto const& referred_to_segment = *maybe_segment.value(); + // 7.4.7.4 Reference bitmap selection + auto const reference_bitmap = TRY([&]() -> ErrorOr { + // "If this segment refers to another region segment, then set the reference bitmap GRREFERENCE to be the current + // contents of the auxiliary buffer associated with the region segment that this segment refers to." + if (header.referred_to_segments.size() == 1) { + auto maybe_segment = context.segment_by_id.get(header.referred_to_segments[0].segment_number); + if (!maybe_segment.has_value()) + return Error::from_string_literal("JBIG2Writer: Could not find referred-to segment for generic refinement region"); + auto const& referred_to_segment = *maybe_segment.value(); + + return TRY(referred_to_segment.data.visit( + [](JBIG2::IntermediateGenericRegionSegmentData const& generic_region_wrapper) -> ErrorOr { + return generic_region_wrapper.generic_region.image; + }, + [](JBIG2::IntermediateGenericRefinementRegionSegmentData const& generic_refinement_region_wrapper) -> ErrorOr { + return generic_refinement_region_wrapper.generic_refinement_region.image; + }, + [](auto const&) -> ErrorOr { + return Error::from_string_literal("JBIG2Writer: Generic refinement region can only refer to intermediate region segments"); + })) + ->as_subbitmap(); + } - auto const* reference_bitmap = TRY(referred_to_segment.data.visit( - [](JBIG2::IntermediateGenericRegionSegmentData const& generic_region_wrapper) -> ErrorOr { - return generic_region_wrapper.generic_region.image; - }, - [](JBIG2::IntermediateGenericRefinementRegionSegmentData const& generic_refinement_region_wrapper) -> ErrorOr { - return generic_refinement_region_wrapper.generic_refinement_region.image; - }, - [](auto const&) -> ErrorOr { - return Error::from_string_literal("JBIG2Writer: Generic refinement region can only refer to intermediate region segments"); - })); + // "If this segment does not refer to another region segment, set GRREFERENCE to be a bitmap containing the current + // contents of the page buffer (see clause 8), restricted to the area of the page buffer specified by this segment's region + // segment information field." + VERIFY(header.referred_to_segments.size() == 0); + Vector preceding_segments_on_same_page; + for (auto const& segment : context.segments) { + if (segment.header.page_association == 0 || segment.header.page_association == header.page_association) { + if (&segment.header == &header) + break; + auto const& data = context.segment_data_by_id.get(segment.header.segment_number); + preceding_segments_on_same_page.append(data->data); + } + } + auto bitmap = TRY(JBIG2ImageDecoderPlugin::decode_embedded(preceding_segments_on_same_page)); + return bitmap->subbitmap(generic_refinement_region.region_segment_information.rect()); + }()); GenericRefinementRegionEncodingInputParameters inputs { .image = *generic_refinement_region.image, @@ -2624,7 +2654,7 @@ ErrorOr JBIG2Writer::encode_with_explicit_data(Stream& stream, JBIG2::File TRY(encode_jbig2_header(stream, file_data.header)); - JBIG2EncodingContext context; + JBIG2EncodingContext context { file_data.segments }; for (auto const& segment : file_data.segments) { if (context.segment_by_id.set(segment.header.segment_number, &segment) != HashSetResult::InsertedNewEntry) return Error::from_string_literal("JBIG2Writer: Duplicate segment number"); diff --git a/Userland/Libraries/LibPDF/Filter.cpp b/Userland/Libraries/LibPDF/Filter.cpp index a64c37302caf3b..9641b2e9a2297e 100644 --- a/Userland/Libraries/LibPDF/Filter.cpp +++ b/Userland/Libraries/LibPDF/Filter.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -309,7 +310,7 @@ PDFErrorOr Filter::decode_jbig2(Document* document, ReadonlyBytes by } segments.append(bytes); - auto decoded = TRY(Gfx::JBIG2ImageDecoderPlugin::decode_embedded(segments)); + auto decoded = TRY(TRY(Gfx::JBIG2ImageDecoderPlugin::decode_embedded(segments))->to_byte_buffer()); // JBIG2 treats `1` as "ink present" (black) and `0` as "no ink" (white). // PDF treats `1` as "light present" (white) and `1` as "no light" (black).