From 99c1c685fcbf1e83941299aa91ad4b0ca5697a36 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2024 09:02:16 -0400 Subject: [PATCH] LibGfx/JPEG2000: Decode COD marker segment data --- .../LibGfx/ImageFormats/JPEG2000Loader.cpp | 150 +++++++++++++++++- 1 file changed, 148 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp index 2dc6b63f70c..8879d0a03f0 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp @@ -185,6 +185,141 @@ static ErrorOr read_image_and_tile_size(ReadonlyBytes data) return siz; } +// A.6.1 Coding style default (COD) +struct CodingStyleDefault { + // Table A.13 – Coding style parameter values for the Scod parameter + bool has_explicit_precinct_size { false }; + bool may_use_SOP_marker { false }; + bool may_use_EPH_marker { false }; + + // Table A.16 – Progression order for the SGcod, SPcoc, and Ppoc parameters + enum ProgressionOrder { + LayerResolutionComponentPosition = 0, + ResolutionLayerComponentPosition = 1, + ResolutionPositionComponentLayer = 2, + PositionComponentResolutionLayer = 3, + ComponentPositionResolutionLayer = 4, + }; + + // Table A.17 – Multiple component transformation for the SGcod parameters + enum MultipleComponentTransformationType { + None = 0, + MultipleComponentTransformationUsed = 1, // See Annex G + }; + + // Table A.14 – Coding style parameter values of the SGcod parameter + ProgressionOrder progression_order { LayerResolutionComponentPosition }; + u16 number_of_layers { 0 }; + MultipleComponentTransformationType multiple_component_transformation_type { None }; + + // Table A.20 – Transformation for the SPcod and SPcoc parameters + enum Transformation { + Irreversible_9_7_Filter = 0, + Reversible_5_3_Filter = 1, + }; + + // Table A.15 – Coding style parameter values of the SPcod and SPcoc parameters + // "Number of decomposition levels, NL, Zero implies no transformation." + u8 number_of_decomposition_levels { 0 }; + u8 code_block_width_exponent { 0 }; // "xcb" in spec; 2 already added. + u8 code_block_height_exponent { 0 }; // "ycb" in spec; 2 already added. + u8 code_block_style { 0 }; + Transformation transformation { Irreversible_9_7_Filter }; + + // Table A.19 – Code-block style for the SPcod and SPcoc parameters + bool uses_selective_arithmetic_coding_bypass() const { return code_block_style & 1; } + bool reset_context_probabilities() const { return code_block_style & 2; } + bool uses_termination_on_each_coding_pass() const { return code_block_style & 4; } + bool uses_vertically_causal_context() const { return code_block_style & 8; } + bool uses_predictable_termination() const { return code_block_style & 0x10; } + bool uses_segmentation_symbols() const { return code_block_style & 0x20; } + + // If has_explicit_precinct_size is false, this contains the default { 15, 15 } number_of_decomposition_levels + 1 times. + // If has_explicit_precinct_size is true, this contains number_of_decomposition_levels + 1 explicit values stored in the COD marker segment. + struct PrecinctSize { + u8 PPx { 0 }; + u8 PPy { 0 }; + }; + Vector precinct_sizes; +}; + +static ErrorOr read_coding_style_default(ReadonlyBytes data) +{ + if (data.size() < 10) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for COD marker segment"); + + CodingStyleDefault cod; + + u8 Scod = data[0]; + cod.has_explicit_precinct_size = Scod & 1; + cod.may_use_SOP_marker = Scod & 2; + cod.may_use_EPH_marker = Scod & 4; + + u32 SGcod = *reinterpret_cast const*>(data.data() + 1); + u8 progression_order = SGcod >> 24; + if (progression_order > 4) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid progression order"); + cod.progression_order = static_cast(progression_order); + + cod.number_of_layers = (SGcod >> 8) & 0xFFFF; + if (cod.number_of_layers == 0) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid number of layers"); + + u8 multiple_component_transformation_type = SGcod & 0xFF; + if (multiple_component_transformation_type > 1) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid multiple component transformation type"); + cod.multiple_component_transformation_type = static_cast(multiple_component_transformation_type); + + cod.number_of_decomposition_levels = data[5]; + if (cod.number_of_decomposition_levels > 32) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid number of decomposition levels"); + + // Table A.18 – Width or height exponent of the code-blocks for the SPcod and SPcoc parameters + u8 xcb = (data[6] & 0xF) + 2; + u8 ycb = (data[7] & 0xF) + 2; + if (xcb > 10 || ycb > 10 || xcb + ycb > 12) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid code block size"); + cod.code_block_width_exponent = xcb; + cod.code_block_height_exponent = ycb; + + cod.code_block_style = data[8]; + + u8 transformation = data[9]; + if (transformation > 1) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid transformation"); + cod.transformation = static_cast(transformation); + + if (cod.has_explicit_precinct_size) { + if (data.size() < 10u + cod.number_of_decomposition_levels + 1u) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for COD marker segment precinct sizes"); + for (size_t i = 0; i < cod.number_of_decomposition_levels + 1u; ++i) { + u8 b = data[10 + i]; + + // Table A.21 – Precinct width and height for the SPcod and SPcoc parameters + CodingStyleDefault::PrecinctSize precinct_size; + precinct_size.PPx = b & 0xF; + precinct_size.PPy = b >> 4; + if ((precinct_size.PPx == 0 || precinct_size.PPy == 0) && i > 0) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid precinct size"); + cod.precinct_sizes.append(precinct_size); + } + } else { + for (size_t i = 0; i < cod.number_of_decomposition_levels + 1u; ++i) + cod.precinct_sizes.append({ 15, 15 }); + } + + dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COD marker segment: has_explicit_precinct_size={}, may_use_SOP_marker={}, may_use_EPH_marker={}, progression_order={}, number_of_layers={}", cod.has_explicit_precinct_size, cod.may_use_SOP_marker, cod.may_use_EPH_marker, (int)cod.progression_order, cod.number_of_layers); + dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COD marker segment: multiple_component_transformation_type={}, number_of_decomposition_levels={}, code_block_width_exponent={}, code_block_height_exponent={}", (int)cod.multiple_component_transformation_type, cod.number_of_decomposition_levels, cod.code_block_width_exponent, cod.code_block_height_exponent); + dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COD marker segment: code_block_style={}, transformation={}", cod.code_block_style, (int)cod.transformation); + if (cod.has_explicit_precinct_size) { + dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COD marker segment: {} explicit precinct sizes:", cod.precinct_sizes.size()); + for (auto [i, precinct_size] : enumerate(cod.precinct_sizes)) + dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COD marker segment: precinct_size[{}]: PPx={}, PPy={}", i, precinct_size.PPx, precinct_size.PPy); + } + + return cod; +} + struct JPEG2000LoadingContext { enum class State { NotDecoded = 0, @@ -202,6 +337,7 @@ struct JPEG2000LoadingContext { // Data from marker segments: ImageAndTileSize siz; + CodingStyleDefault cod; }; struct MarkerSegment { @@ -261,6 +397,7 @@ static ErrorOr parse_codestream_main_header(JPEG2000LoadingContext& contex return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected SIZ marker"); context.siz = TRY(read_image_and_tile_size(marker.data.value())); + bool saw_COD_marker = false; while (true) { u16 marker = TRY(peek_marker(context)); switch (marker) { @@ -275,13 +412,22 @@ static ErrorOr parse_codestream_main_header(JPEG2000LoadingContext& contex case J2K_PLM: case J2K_CRG: case J2K_COM: { - // FIXME: These are valid main header markers. Parse contents. auto marker = TRY(read_marker_at_cursor(context)); - dbgln("JPEG2000ImageDecoderPlugin: marker {:#04x} not yet implemented", marker.marker); + if (marker.marker == J2K_COD) { + if (saw_COD_marker) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple COD markers in main header"); + context.cod = TRY(read_coding_style_default(marker.data.value())); + saw_COD_marker = true; + } else { + // FIXME: These are valid main header markers. Parse contents. + dbgln("JPEG2000ImageDecoderPlugin: marker {:#04x} not yet implemented", marker.marker); + } break; } case J2K_SOT: // SOT terminates the main header. + if (!saw_COD_marker) + return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Required COD marker not present in main header"); return {}; default: return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Unexpected marker in main header");