Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
37 changes: 27 additions & 10 deletions Userland/Utilities/jbig2-from-json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1578,35 +1578,48 @@ static ErrorOr<u8> jbig2_halftone_region_flags_from_json(JsonObject const& objec
return flags;
}

static ErrorOr<Vector<u64>> jbig2_halftone_graymap_from_json(ToJSONOptions const&, JsonObject const& object)
static ErrorOr<Variant<Vector<u64>, NonnullRefPtr<Gfx::Bitmap>>> jbig2_halftone_graymap_from_json(ToJSONOptions const& options, JsonObject const& object)
{
Vector<u64> graymap;
Optional<Variant<Vector<u64>, NonnullRefPtr<Gfx::Bitmap>>> graymap;

TRY(object.try_for_each_member([&](StringView key, JsonValue const& value) -> ErrorOr<void> {
if (key == "array") {
if (value.is_array()) {
Vector<u64> graymap_data;
for (auto const& row : value.as_array().values()) {
if (!row.is_array())
return Error::from_string_literal("expected array for \"array\" entries");

for (auto const& element : row.as_array().values()) {
if (auto value = element.get_u64(); value.has_value()) {
TRY(graymap.try_append(value.value()));
TRY(graymap_data.try_append(value.value()));
continue;
}
return Error::from_string_literal("expected u64 for \"graymap_data\" elements");
}
}
graymap = move(graymap_data);
return {};
}
return Error::from_string_literal("expected array for \"array\"");
}

if (key == "match_image") {
if (value.is_string()) {
graymap = TRY(jbig2_bitmap_from_json(options, value.as_string()));
return {};
}
return Error::from_string_literal("expected string for \"match_image\"");
}

dbgln("graymap_data key {}", key);
return Error::from_string_literal("unknown graymap_data key");
}));

return graymap;
if (!graymap.has_value())
return Error::from_string_literal("graymap_data object must have \"array\" or \"match_image\" member");

return graymap.release_value();
}

static ErrorOr<Gfx::JBIG2::HalftoneRegionSegmentData> jbig2_halftone_region_from_json(ToJSONOptions const& options, Optional<JsonObject const&> object)
Expand All @@ -1622,8 +1635,9 @@ static ErrorOr<Gfx::JBIG2::HalftoneRegionSegmentData> jbig2_halftone_region_from
i32 grid_offset_y_times_256 { 0 };
u16 grid_vector_x_times_256 { 0 };
u16 grid_vector_y_times_256 { 0 };
Vector<u64> grayscale_image;
Optional<Variant<Vector<u64>, NonnullRefPtr<Gfx::Bitmap>>> grayscale_image;
Gfx::MQArithmeticEncoder::Trailing7FFFHandling trailing_7fff_handling { Gfx::MQArithmeticEncoder::Trailing7FFFHandling::Keep };

TRY(object->try_for_each_member([&](StringView key, JsonValue const& value) -> ErrorOr<void> {
if (key == "region_segment_information"sv) {
if (value.is_object()) {
Expand Down Expand Up @@ -1708,10 +1722,10 @@ static ErrorOr<Gfx::JBIG2::HalftoneRegionSegmentData> jbig2_halftone_region_from
}
if (value.is_string()) {
if (value.as_string() == "identity_tile_indices"sv) {
grayscale_image.clear();
u32 num_pixels = grayscale_width * grayscale_height;
for (u32 i = 0; i < num_pixels; ++i)
TRY(grayscale_image.try_append(i));
Vector<u64> graymap;
for (u32 i = 0; i < grayscale_width * grayscale_height; ++i)
TRY(graymap.try_append(i));
grayscale_image = move(graymap);
return {};
}
}
Expand All @@ -1722,6 +1736,9 @@ static ErrorOr<Gfx::JBIG2::HalftoneRegionSegmentData> jbig2_halftone_region_from
return Error::from_string_literal("unknown halftone_region key");
}));

if (!grayscale_image.has_value())
return Error::from_string_literal("halftone_region \"data\" object missing \"graymap_data\"");

return Gfx::JBIG2::HalftoneRegionSegmentData {
region_segment_information,
flags,
Expand All @@ -1731,7 +1748,7 @@ static ErrorOr<Gfx::JBIG2::HalftoneRegionSegmentData> jbig2_halftone_region_from
grid_offset_y_times_256,
grid_vector_x_times_256,
grid_vector_y_times_256,
move(grayscale_image),
grayscale_image.release_value(),
trailing_7fff_handling,
};
}
Expand Down