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
4 changes: 4 additions & 0 deletions Tests/LibGfx/TestImageDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ TEST_CASE(test_jbig2_decode)
TEST_INPUT("jbig2/bitmap-refine-tpgron.jbig2"sv),
TEST_INPUT("jbig2/bitmap-refine-template1.jbig2"sv),
TEST_INPUT("jbig2/bitmap-refine-template1-tpgron.jbig2"sv),
TEST_INPUT("jbig2/bitmap-halftone.jbig2"sv),
TEST_INPUT("jbig2/bitmap-halftone-template1.jbig2"sv),
TEST_INPUT("jbig2/bitmap-halftone-template2.jbig2"sv),
TEST_INPUT("jbig2/bitmap-halftone-template3.jbig2"sv),
TEST_INPUT("jbig2/bitmap-halftone-10bpp.jbig2"sv),
TEST_INPUT("jbig2/bitmap-halftone-10bpp-mmr.jbig2"sv),
TEST_INPUT("jbig2/bitmap-symbol.jbig2"sv),
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added Tests/LibGfx/test-inputs/jbig2/bitmap-halftone.jbig2
Binary file not shown.
72 changes: 72 additions & 0 deletions Tests/LibGfx/test-inputs/jbig2/json/bitmap-halftone-template1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"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": {
"is_eventually_lossless": true
}
}
},
{
"segment_number": 1,
"type": "pattern_dictionary",
"page_association": 1,
"retained": true,
"data": {
"flags": {
"pd_template": 1
},
"pattern_width": 16,
"pattern_height": 16,
"gray_max": "from_tiles",
"method": "unique_image_tiles",
"image_data": {
"from_file": "bitmap.bmp"
}
}
},
{
"segment_number": 2,
"type": "lossless_halftone_region",
"page_association": 1,
"referred_to_segments": [
{
"segment_number": 1,
"retained": false
}
],
"data": {
"region_segment_information": {
"width": 399,
"height": 400
},
"flags": {
"ht_template": 1
},
"grayscale_width": 25,
"grayscale_height": 25,
"grid_offset_x_times_256": 0,
"grid_offset_y_times_256": 0,
"grid_vector_x_times_256": 4096,
"grid_vector_y_times_256": 0,
"graymap_data": {
"match_image": "bitmap.bmp"
}
}
},
{
"segment_number": 3,
"type": "end_of_page",
"page_association": 1
}
]
}
72 changes: 72 additions & 0 deletions Tests/LibGfx/test-inputs/jbig2/json/bitmap-halftone-template2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"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": {
"is_eventually_lossless": true
}
}
},
{
"segment_number": 1,
"type": "pattern_dictionary",
"page_association": 1,
"retained": true,
"data": {
"flags": {
"pd_template": 2
},
"pattern_width": 16,
"pattern_height": 16,
"gray_max": "from_tiles",
"method": "unique_image_tiles",
"image_data": {
"from_file": "bitmap.bmp"
}
}
},
{
"segment_number": 2,
"type": "lossless_halftone_region",
"page_association": 1,
"referred_to_segments": [
{
"segment_number": 1,
"retained": false
}
],
"data": {
"region_segment_information": {
"width": 399,
"height": 400
},
"flags": {
"ht_template": 2
},
"grayscale_width": 25,
"grayscale_height": 25,
"grid_offset_x_times_256": 0,
"grid_offset_y_times_256": 0,
"grid_vector_x_times_256": 4096,
"grid_vector_y_times_256": 0,
"graymap_data": {
"match_image": "bitmap.bmp"
}
}
},
{
"segment_number": 3,
"type": "end_of_page",
"page_association": 1
}
]
}
72 changes: 72 additions & 0 deletions Tests/LibGfx/test-inputs/jbig2/json/bitmap-halftone-template3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"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": {
"is_eventually_lossless": true
}
}
},
{
"segment_number": 1,
"type": "pattern_dictionary",
"page_association": 1,
"retained": true,
"data": {
"flags": {
"pd_template": 3
},
"pattern_width": 16,
"pattern_height": 16,
"gray_max": "from_tiles",
"method": "unique_image_tiles",
"image_data": {
"from_file": "bitmap.bmp"
}
}
},
{
"segment_number": 2,
"type": "lossless_halftone_region",
"page_association": 1,
"referred_to_segments": [
{
"segment_number": 1,
"retained": false
}
],
"data": {
"region_segment_information": {
"width": 399,
"height": 400
},
"flags": {
"ht_template": 3
},
"grayscale_width": 25,
"grayscale_height": 25,
"grid_offset_x_times_256": 0,
"grid_offset_y_times_256": 0,
"grid_vector_x_times_256": 4096,
"grid_vector_y_times_256": 0,
"graymap_data": {
"match_image": "bitmap.bmp"
}
}
},
{
"segment_number": 3,
"type": "end_of_page",
"page_association": 1
}
]
}
66 changes: 66 additions & 0 deletions Tests/LibGfx/test-inputs/jbig2/json/bitmap-halftone.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"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": {
"is_eventually_lossless": true
}
}
},
{
"segment_number": 1,
"type": "pattern_dictionary",
"page_association": 1,
"retained": true,
"data": {
"pattern_width": 16,
"pattern_height": 16,
"gray_max": "from_tiles",
"method": "unique_image_tiles",
"image_data": {
"from_file": "bitmap.bmp"
}
}
},
{
"segment_number": 2,
"type": "lossless_halftone_region",
"page_association": 1,
"referred_to_segments": [
{
"segment_number": 1,
"retained": false
}
],
"data": {
"region_segment_information": {
"width": 399,
"height": 400
},
"grayscale_width": 25,
"grayscale_height": 25,
"grid_offset_x_times_256": 0,
"grid_offset_y_times_256": 0,
"grid_vector_x_times_256": 4096,
"grid_vector_y_times_256": 0,
"graymap_data": {
"match_image": "bitmap.bmp"
}
}
},
{
"segment_number": 3,
"type": "end_of_page",
"page_association": 1
}
]
}
49 changes: 48 additions & 1 deletion Userland/Libraries/LibGfx/ImageFormats/JBIG2Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1987,7 +1987,54 @@ static ErrorOr<void> encode_halftone_region(JBIG2::HalftoneRegionSegmentData con
if (enable_skip)
return Error::from_string_literal("JBIG2Writer: Halftone region skip pattern not yet implemented");

GrayscaleInputParameters inputs { .grayscale_image = halftone_region.grayscale_image, .skip_pattern = skip_pattern };
Vector<u64> grayscale_image = TRY(halftone_region.grayscale_image.visit(
[](Vector<u64> const& grayscale_image) -> ErrorOr<Vector<u64>> {
return grayscale_image;
},
[&halftone_region, &pattern_dictionary](NonnullRefPtr<Gfx::Bitmap> const& reference) -> ErrorOr<Vector<u64>> {
// FIXME: This does not handle rotation or non-trivial grid vectors yet.
if (halftone_region.grid_offset_x_times_256 != 0 || halftone_region.grid_offset_y_times_256 != 0)
return Error::from_string_literal("JBIG2Writer: Halftone region match_image with non-zero grid offsets not yet implemented");
if (pattern_dictionary.pattern_width != pattern_dictionary.pattern_height
|| halftone_region.grid_vector_x_times_256 / 256 != pattern_dictionary.pattern_width
|| halftone_region.grid_vector_y_times_256 != 0)
return Error::from_string_literal("JBIG2Writer: Halftone region match_image with non-trivial grid vectors not yet implemented");

Vector<u64> converted_image;
TRY(converted_image.try_resize(halftone_region.grayscale_width * halftone_region.grayscale_height));
for (u32 y = 0; y < halftone_region.grayscale_height; ++y) {
for (u32 x = 0; x < halftone_region.grayscale_width; ++x) {
// Find best tile in pattern dictionary that matches reference best.
// FIXME: This is a naive, inefficient implementation.
u32 best_pattern_index = 0;
u32 best_pattern_difference = UINT32_MAX;
for (u32 pattern_index = 0; pattern_index <= pattern_dictionary.gray_max; ++pattern_index) {
u32 pattern_x = pattern_index * pattern_dictionary.pattern_width;
u32 pattern_difference = 0;
for (u32 py = 0; py < pattern_dictionary.pattern_height; ++py) {
for (u32 px = 0; px < pattern_dictionary.pattern_width; ++px) {
Comment on lines +2005 to +2015
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

casual n^5 algo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The halftone region is a tiling of the reference image, so the outer x/y walks the reference image in tile increments and the inner walks the tile data. So in a way this does O(number of patterns) work per pixel (to find the best pattern to match each tile). I'd call this O(n^3).

Still, as the comment says, yes, this is inefficient. (It's not super duper slow in practice though, just regular slow: I tried covering Tests/LibGfx/test-inputs/jpg/big_image.jpg, a 4000x3000 image, with 16 possible 4x4 tiles, and that took less than a second.)

int reference_x = x * pattern_dictionary.pattern_width + px;
int reference_y = y * pattern_dictionary.pattern_height + py;
if (reference_x >= reference->width() || reference_y >= reference->height())
continue;
auto pattern_pixel = pattern_dictionary.image->get_bit(pattern_x + px, py);
auto reference_pixel = reference->get_pixel(reference_x, reference_y);
pattern_difference += abs(reference_pixel.luminosity() - (pattern_pixel ? 0 : 255));
}
}
if (pattern_difference < best_pattern_difference) {
best_pattern_difference = pattern_difference;
best_pattern_index = pattern_index;
}
}
converted_image[y * halftone_region.grayscale_width + x] = best_pattern_index;
}
}

return converted_image;
}));

GrayscaleInputParameters inputs { .grayscale_image = grayscale_image, .skip_pattern = skip_pattern };
inputs.uses_mmr = halftone_region.flags & 1;
inputs.skip_pattern = skip_pattern;
inputs.bpp = bits_per_pattern;
Expand Down
11 changes: 8 additions & 3 deletions Userland/Libraries/LibGfx/ImageFormats/JBIG2Writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,14 @@ struct HalftoneRegionSegmentData {
u16 grid_vector_x_times_256 { 0 };
u16 grid_vector_y_times_256 { 0 };

// Indices into pattern dictionary. At most 64 bits set per pixel.
// grayscale_width * grayscale_height entries.
Vector<u64> grayscale_image;
Variant<
// Indices into pattern dictionary. At most 64 bits set per pixel,
// grayscale_width * grayscale_height entries,
Vector<u64>,

// Reference image, compute gray_map by matching pattern dictionary tiles to it.
NonnullRefPtr<Gfx::Bitmap>>
grayscale_image;

MQArithmeticEncoder::Trailing7FFFHandling trailing_7fff_handling { MQArithmeticEncoder::Trailing7FFFHandling::Keep };
};
Expand Down
Loading
Loading