Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Tests/LibGfx/TestImageDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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
};
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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
}
]
}
50 changes: 50 additions & 0 deletions Tests/LibGfx/test-inputs/jbig2/json/bitmap-refine-page.json
Original file line number Diff line number Diff line change
@@ -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
}
]
}
30 changes: 16 additions & 14 deletions Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3190,16 +3190,19 @@ static ErrorOr<RegionResult> 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."
Expand All @@ -3208,8 +3211,7 @@ static ErrorOr<RegionResult> 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;
Expand Down Expand Up @@ -3666,7 +3668,7 @@ ErrorOr<ImageFrameDescriptor> JBIG2ImageDecoderPlugin::frame(size_t index, Optio
return ImageFrameDescriptor { move(bitmap), 0 };
}

ErrorOr<ByteBuffer> JBIG2ImageDecoderPlugin::decode_embedded(Vector<ReadonlyBytes> data)
ErrorOr<NonnullRefPtr<BilevelImage>> JBIG2ImageDecoderPlugin::decode_embedded(Vector<ReadonlyBytes> data)
{
auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) JBIG2ImageDecoderPlugin({})));
plugin->m_context->organization = JBIG2::Organization::Embedded;
Expand All @@ -3683,7 +3685,7 @@ ErrorOr<ByteBuffer> JBIG2ImageDecoderPlugin::decode_embedded(Vector<ReadonlyByte

TRY(decode_data(*plugin->m_context));

return plugin->m_context->page.bits->to_byte_buffer();
return *plugin->m_context->page.bits;
}

}
3 changes: 2 additions & 1 deletion Userland/Libraries/LibGfx/ImageFormats/JBIG2Loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace Gfx {

class BilevelImage;
struct JBIG2LoadingContext;
struct MQArithmeticCoderContext;
class MQArithmeticDecoder;
Expand Down Expand Up @@ -69,7 +70,7 @@ class JBIG2ImageDecoderPlugin : public ImageDecoderPlugin {
virtual size_t frame_count() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) override;

static ErrorOr<ByteBuffer> decode_embedded(Vector<ReadonlyBytes>);
static ErrorOr<NonnullRefPtr<BilevelImage>> decode_embedded(Vector<ReadonlyBytes>);

private:
JBIG2ImageDecoderPlugin(JBIG2DecoderOptions);
Expand Down
6 changes: 6 additions & 0 deletions Userland/Libraries/LibGfx/ImageFormats/JBIG2Shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibGfx/ImageFormats/MQArithmeticCoder.h>
#include <LibGfx/Rect.h>

namespace Gfx::JBIG2 {

Expand Down Expand Up @@ -118,6 +119,11 @@ struct [[gnu::packed]] RegionSegmentInformationField {
BigEndian<u32> y_location;
u8 flags { 0 };

IntRect rect() const
{
return { x_location, y_location, width, height };
}

CombinationOperator external_combination_operator() const
{
VERIFY((flags & 0x7) <= 4);
Expand Down
82 changes: 56 additions & 26 deletions Userland/Libraries/LibGfx/ImageFormats/JBIG2Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/BilevelImage.h>
#include <LibGfx/ImageFormats/CCITTEncoder.h>
#include <LibGfx/ImageFormats/JBIG2Loader.h>
#include <LibGfx/ImageFormats/JBIG2Writer.h>
#include <LibGfx/ImageFormats/MQArithmeticCoder.h>
#include <LibTextCodec/Encoder.h>
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -378,7 +379,7 @@ static ErrorOr<void> 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<JBIG2::AdaptiveTemplatePixel> 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<JBIG2::AdaptiveTemplatePixel> 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) {
Expand All @@ -399,7 +400,7 @@ static ErrorOr<void> generic_refinement_region_encoding_procedure(GenericRefinem
};

// Figure 13 – 10-pixel refinement template
constexpr auto compute_context_1 = [](ReadonlySpan<JBIG2::AdaptiveTemplatePixel>, BilevelImage const& reference, int reference_x, int reference_y, BilevelImage const& buffer, int x, int y) -> u16 {
constexpr auto compute_context_1 = [](ReadonlySpan<JBIG2::AdaptiveTemplatePixel>, 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) {
Expand Down Expand Up @@ -440,10 +441,10 @@ static ErrorOr<void> generic_refinement_region_encoding_procedure(GenericRefinem
auto predict = [&](size_t x, size_t y) -> Optional<bool> {
// "• 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;
};
Expand Down Expand Up @@ -471,7 +472,7 @@ static ErrorOr<void> 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 {
Expand All @@ -484,7 +485,7 @@ static ErrorOr<void> 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]);
}
}
Expand Down Expand Up @@ -726,7 +727,7 @@ static ErrorOr<void> 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?
Expand Down Expand Up @@ -1136,7 +1137,7 @@ static ErrorOr<ByteBuffer> 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;
Expand Down Expand Up @@ -1543,6 +1544,13 @@ struct SerializedSegmentData {
};

struct JBIG2EncodingContext {
JBIG2EncodingContext(Vector<JBIG2::SegmentData> const& segments)
: segments(segments)
{
}

Vector<JBIG2::SegmentData> const& segments;

HashMap<u32, JBIG2::SegmentData const*> segment_by_id;

HashMap<u32, SerializedSegmentData> segment_data_by_id;
Expand Down Expand Up @@ -2266,24 +2274,46 @@ static ErrorOr<void> 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<BilevelSubImage> {
// "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<BilevelImage const*> {
return generic_region_wrapper.generic_region.image;
},
[](JBIG2::IntermediateGenericRefinementRegionSegmentData const& generic_refinement_region_wrapper) -> ErrorOr<BilevelImage const*> {
return generic_refinement_region_wrapper.generic_refinement_region.image;
},
[](auto const&) -> ErrorOr<BilevelImage const*> {
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<BilevelImage const*> {
return generic_region_wrapper.generic_region.image;
},
[](JBIG2::IntermediateGenericRefinementRegionSegmentData const& generic_refinement_region_wrapper) -> ErrorOr<BilevelImage const*> {
return generic_refinement_region_wrapper.generic_refinement_region.image;
},
[](auto const&) -> ErrorOr<BilevelImage const*> {
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<ReadonlyBytes> 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,
Expand Down Expand Up @@ -2624,7 +2654,7 @@ ErrorOr<void> 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");
Expand Down
Loading
Loading