Skip to content

Commit

Permalink
LibGfx/JPEG2000: Support [8..16] bits per pixel
Browse files Browse the repository at this point in the history
Do this by making decode_code_block() write to a float buffer,
instead of an i16 buffer.

Alternatively, we could make decode_code_block() stop after some
number of bits since we only need 8 anyways and update a few places
in JPEG2000.cpp that currently read bit_depth(), but this here seems
perf-neutral (even slightly positive) and is simpler, so let's go
with this for now.

(16 bpp images are very rare, and I haven't seen images with more than
16 bpp, even though the spec allows up to 38 bpp.)
  • Loading branch information
nico committed Feb 28, 2025
1 parent 58e6128 commit e69f054
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 16 deletions.
10 changes: 5 additions & 5 deletions Tests/LibGfx/TestImageDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,9 +601,9 @@ TEST_CASE(test_jpeg2000_spec_annex_j_10_bitplane_decoding)
// Table J.22 – Arithmetic decode of first code-block
constexpr Array input = to_array<u8>({ 0x01, 0x8F, 0x0D, 0xC8, 0x75, 0x5D });

Vector<i16> output;
Vector<float> output;
output.resize(5);
Gfx::JPEG2000::Span2D<i16> result { output.span(), { 1, 5 }, 1 };
Gfx::JPEG2000::Span2D<float> result { output.span(), { 1, 5 }, 1 };

// 16, 9, 3 are from J.10.3 Packet headers, Table J.20 – Decoding first packet header.
TRY_OR_FAIL(Gfx::JPEG2000::decode_code_block(result, Gfx::JPEG2000::SubBand::HorizontalLowpassVerticalLowpass, 16, { input }, 9, 3));
Expand All @@ -619,9 +619,9 @@ TEST_CASE(test_jpeg2000_spec_annex_j_10_bitplane_decoding)
// Table J.23 – Arithmetic decode of second code-block
constexpr Array input = to_array<u8>({ 0x0F, 0xB1, 0x76 });

Vector<i16> output;
Vector<float> output;
output.resize(4);
Gfx::JPEG2000::Span2D<i16> result { output.span(), { 1, 4 }, 1 };
Gfx::JPEG2000::Span2D<float> result { output.span(), { 1, 4 }, 1 };

// 7, 10, 7 are from J.10.3 Packet headers, Table J.21 – Decoding second packet header.
TRY_OR_FAIL(Gfx::JPEG2000::decode_code_block(result, Gfx::JPEG2000::SubBand::HorizontalLowpassVerticalHighpass, 7, { input }, 10, 7));
Expand Down Expand Up @@ -724,6 +724,7 @@ TEST_CASE(test_jpeg2000_decode)
TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog0-EPH-SOP.jp2"sv),
TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-PLT.jp2"sv),
TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-TLM.jp2"sv),
TEST_INPUT("jpeg2000/kakadu-lossless-rgba-u16-prog1-layers1-res6.jp2"sv),
};

for (auto test_input : test_inputs) {
Expand Down Expand Up @@ -891,7 +892,6 @@ TEST_CASE(test_jpeg2000_decode_unsupported)
{
Array test_inputs = {
TEST_INPUT("jpeg2000/kakadu-lossless-cmyka-u8-prog1-layers1-res6.jp2"sv),
TEST_INPUT("jpeg2000/kakadu-lossless-rgba-u16-prog1-layers1-res6.jp2"sv),
TEST_INPUT("jpeg2000/openjpeg-lossless-indexed-u8-rgb-u8.jp2"sv),
TEST_INPUT("jpeg2000/openjpeg-lossless-bgra-u8.jp2"sv),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ inline auto number_of_passes_from_segment_index_in_bypass_mode(unsigned segment_
return segment_index % 2 == 1 ? 2u : 1u;
}

inline ErrorOr<void> decode_code_block(Span2D<i16> result, SubBand sub_band, int number_of_coding_passes, Vector<ReadonlyBytes, 1> segments, int M_b, int p, BitplaneDecodingOptions options = {})
inline ErrorOr<void> decode_code_block(Span2D<float> result, SubBand sub_band, int number_of_coding_passes, Vector<ReadonlyBytes, 1> segments, int M_b, int p, BitplaneDecodingOptions options = {})
{
// This is an implementation of the bitplane decoding algorithm described in Annex D of the JPEG2000 spec.
// It's modeled closely after Figure D.3 – Flow chart for all coding passes on a code-block bit-plane,
Expand Down
21 changes: 11 additions & 10 deletions Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1888,14 +1888,14 @@ static int compute_M_b(JPEG2000LoadingContext& context, TileData& tile, int comp

static ErrorOr<void> decode_bitplanes_to_coefficients(JPEG2000LoadingContext& context)
{
auto copy_and_dequantize_if_needed = [&](JPEG2000::Span2D<float> output, Span<i16> input, QuantizationDefault const& quantization_parameters, JPEG2000::SubBand sub_band_type, int component_index, int r) {
auto copy_and_dequantize_if_needed = [&](JPEG2000::Span2D<float> output, ReadonlySpan<float> input, QuantizationDefault const& quantization_parameters, JPEG2000::SubBand sub_band_type, int component_index, int r) {
int w = output.size.width();
int h = output.size.height();
VERIFY(w * h == static_cast<int>(input.size()));

for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
float value = static_cast<float>(input[y * w + x]);
float value = input[y * w + x];

// E.1 Inverse quantization procedure
// The coefficients store qbar_b.
Expand Down Expand Up @@ -1947,7 +1947,7 @@ static ErrorOr<void> decode_bitplanes_to_coefficients(JPEG2000LoadingContext& co
// FIXME: Codeblocks all use independent arithmetic coders, so this could run in parallel.
for (auto& precinct : sub_band.precincts) {

Vector<i16> precinct_coefficients;
Vector<float> precinct_coefficients;
auto clipped_precinct_rect = precinct.rect.intersected(sub_band.rect);
precinct_coefficients.resize(clipped_precinct_rect.width() * clipped_precinct_rect.height());

Expand All @@ -1956,7 +1956,7 @@ static ErrorOr<void> decode_bitplanes_to_coefficients(JPEG2000LoadingContext& co
ByteBuffer storage;
Vector<ReadonlyBytes, 1> combined_segments = TRY(code_block.segments_for_all_layers(storage));

JPEG2000::Span2D<i16> output;
JPEG2000::Span2D<float> output;
output.size = code_block.rect.size();
output.pitch = clipped_precinct_rect.width();
output.data = precinct_coefficients.span().slice((code_block.rect.y() - clipped_precinct_rect.y()) * output.pitch + (code_block.rect.x() - clipped_precinct_rect.x()));
Expand Down Expand Up @@ -2224,12 +2224,13 @@ static ErrorOr<void> convert_to_bitmap(JPEG2000LoadingContext& context)
for (auto const& [component_index, component] : enumerate(tile.components)) {
if (context.siz.components[component_index].is_signed())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Only unsigned components supported yet");
if (context.siz.components[component_index].bit_depth() > 8)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: More than 8 bits per component not supported yet");
if (context.siz.components[component_index].bit_depth() < 8) {
for (float& sample : component.samples)
sample = (sample * 255.0f) / ((1 << context.siz.components[component_index].bit_depth()) - 1);
}

// > 16bpp currently overflow the u16s internal to decode_code_block().
if (context.siz.components[component_index].bit_depth() > 16)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: More than 16 bits per component not supported yet");

for (float& sample : component.samples)
sample = (sample * 255.0f) / ((1 << context.siz.components[component_index].bit_depth()) - 1);
}
}

Expand Down

0 comments on commit e69f054

Please sign in to comment.