/*
* Copyright (c) 2024, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/BitStream.h>
#include <AK/Debug.h>
#include <AK/Enumerate.h>
#include <AK/MemoryStream.h>
#include <LibGfx/ICC/Profile.h>
#include <LibGfx/ImageFormats/ISOBMFF/JPEG2000Boxes.h>
#include <LibGfx/ImageFormats/ISOBMFF/Reader.h>
#include <LibGfx/ImageFormats/JPEG2000BitplaneDecoding.h>
#include <LibGfx/ImageFormats/JPEG2000InverseDiscreteWaveletTransform.h>
#include <LibGfx/ImageFormats/JPEG2000Loader.h>
#include <LibGfx/ImageFormats/JPEG2000ProgressionIterators.h>
#include <LibGfx/ImageFormats/JPEG2000TagTree.h>
#include <LibTextCodec/Decoder.h>
// Core coding system spec (.jp2 format): T-REC-T.800-201511-S!!PDF-E.pdf available here:
// https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-T.800-201511-S!!PDF-E&type=items
// There is a useful example bitstream in the spec in:
// J.10 An example of decoding showing intermediate
// Extensions (.jpx format): T-REC-T.801-202106-S!!PDF-E.pdf available here:
// https://handle.itu.int/11.1002/1000/14666-en?locatt=format:pdf&auth
// rfc3745 lists the MIME type. It only mentions the jp2_id_string as magic number.
// A short overview of the JPEG2000 format:
//
// Image Decomposition
// -------------------
//
// 1. An image is first divided into independent tiles
// 2. Each tile is split into tile components (one each for R, G, B, A)
// 3. Each tile component undergoes Discrete Wavelet Transform (DWT)
//
// Resolution Levels and Subbands
// ------------------------------
//
// The DWT produces hierarchical resolution levels with these subbands:
// - Level 0: Single LL (Lowpass-Lowpass) subband
// - Level 1+: HL (Highpass-Lowpass), LH (Lowpass-Highpass), and HH (Highpass-Highpass) subbands
//
// Subband Layout:
// +-----+-----+----------+
// | LL0 | HL1 | |
// +-----+-----+ HL2 |
// | LH1 | HH1 | |
// +-----+-----+----------+
// | | |
// | LH2 | HH2 |
// | | |
// +-----------+----------+
//
// Precinct Structure
// ------------------
// - Precincts are rectangular regions that span all subbands within a resolution level
// - Typical size: 512k × 512k pixels
// - Most images contain only a single precinct due to this large size
// - "Precinct limited to a subband": portion of precinct covering one subband
//
// Layer System
// -----------
// - Coefficients' bitplanes can be stored separately
// - Groups of bitplanes form "layers"
// - For example, for an 8bpp image, layer 0 might contain the first two bitplanes, layer 1 the next two, etc.
// - Enables progressive refinement of image color resolution
//
// Codeblock Organization
// ----------------------
// - Each precinct is divided into codeblocks
// - A codeblock is the smallest coded unit in JPEG2000
// - Typical codeblock size: 64×64 pixels
// - Codeblocks store coefficient bitplanes from wavelet transformation
// - Independent arithmetic decoder contexts enable parallel decoding
// - A codeblock can be split into segments. A segment is a group of bytes
// that are fed into the arithmetic decoder as one unit. Most files use one segment,
// but the code block styles "termination on each coding pass" and
// "selective arithmetic coding bypass" use multiple segments.
//
// Packets
// -------
// "All compressed image data representing a specific tile, layer, component, resolution level and precinct appears in the
// codestream in a contiguous segment called a packet."
// A packet contains a packet header, and information about all codeblocks in the packet.
namespace Gfx {
// A JPEG2000 image can be stored in a codestream with markers, similar to a JPEG image,
// or in a JP2 file, which is a container format based on boxes similar to ISOBMFF.
// This is the marker for the codestream version.
// T.800 Annex A, Codestream syntax, A.2 Information in the marker segments and A.3 Construction of the codestream
static constexpr u8 marker_id_string[] = { 0xFF, 0x4F, 0xFF, 0x51 };
// This is the marker for the box version.
// T.800 Annex I, JP2 file format syntax, I.5.1 JPEG 2000 Signature box
static constexpr u8 jp2_id_string[] = { 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A };
// Table A.2 – List of markers and marker segments
// "Delimiting markers and marker segments"
#define J2K_SOC 0xFF4F // "Start of codestream"
#define J2K_SOT 0xFF90 // "Start of tile-part"
#define J2K_SOD 0xFF93 // "Start of data"
#define J2K_EOC 0xFFD9 // "End of codestream"
// "Fixed information marker segments"
#define J2K_SIZ 0xFF51 // "Image and tile size"
// "Functional marker segments"
#define J2K_COD 0xFF52 // "Coding style default"
#define J2K_COC 0xFF53 // "Coding style component"
#define J2K_RGN 0xFF5E // "Region-of-interest"
#define J2K_QCD 0xFF5C // "Quantization default"
#define J2K_QCC 0xFF5D // "Quantization component"
#define J2K_POC 0xFF5F // "Progression order change"
// "Pointer marker segments"
#define J2K_TLM 0xFF55 // "Tile-part lengths"
#define J2K_PLM 0xFF57 // "Packet length, main header"
#define J2K_PLT 0xFF58 // "Packet length, tile-part header"
#define J2K_PPM 0xFF60 // "Packed packet headers, main header"
#define J2K_PPT 0xFF61 // "Packed packet headers, tile-part header"
// "In-bit-stream markers and marker segments"
#define J2K_SOP 0xFF91 // "Start of packet"
#define J2K_EPH 0xFF92 // "End of packet header"
// "Informational marker segments"
#define J2K_CRG 0xFF63 // "Component registration"
#define J2K_COM 0xFF64 // "Comment"
// A.4.2 Start of tile-part (SOT)
struct StartOfTilePart {
// "Tile index. This number refers to the tiles in raster order starting at the number 0."
u16 tile_index { 0 }; // "Isot" in spec.
// "Length, in bytes, from the beginning of the first byte of this SOT marker segment of the tile-part to
// the end of the data of that tile-part. Figure A.16 shows this alignment. Only the last tile-part in the
// codestream may contain a 0 for Psot. If the Psot is 0, this tile-part is assumed to contain all data until
// the EOC marker."
u32 tile_part_length { 0 }; // "Psot" in spec.
// "Tile-part index. There is a specific order required for decoding tile-parts; this index denotes the order
// from 0. If there is only one tile-part for a tile, then this value is zero. The tile-parts of this tile shall
// appear in the codestream in this order, although not necessarily consecutively."
u8 tile_part_index { 0 }; // "TPsot" in spec.
// "Number of tile-parts of a tile in the codestream. Two values are allowed: the correct number of tile-
// parts for that tile and zero. A zero value indicates that the number of tile-parts of this tile is not
// specified in this tile-part.
u8 number_of_tile_parts { 0 }; // "TNsot" in spec.
};
static ErrorOr<StartOfTilePart> read_start_of_tile_part(ReadonlyBytes data)
{
FixedMemoryStream stream { data };
StartOfTilePart sot;
sot.tile_index = TRY(stream.read_value<BigEndian<u16>>());
sot.tile_part_length = TRY(stream.read_value<BigEndian<u32>>());
sot.tile_part_index = TRY(stream.read_value<u8>());
sot.number_of_tile_parts = TRY(stream.read_value<u8>());
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: SOT marker segment: tile_index={}, tile_part_length={}, tile_part_index={}, number_of_tile_parts={}", sot.tile_index, sot.tile_part_length, sot.tile_part_index, sot.number_of_tile_parts);
return sot;
}
// A.5.1 Image and tile size (SIZ)
struct ImageAndTileSize {
// "Denotes capabilities that a decoder needs to properly decode the codestream."
u16 needed_decoder_capabilities { 0 }; // "Rsiz" in spec.
// "Width of the reference grid."
u32 width { 0 }; // "Xsiz" in spec.
// "Height of the reference grid."
u32 height { 0 }; // "Ysiz" in spec.
// "Horizontal offset from the origin of the reference grid to the left side of the image area."
u32 x_offset { 0 }; // "XOsiz" in spec.
// "Vertical offset from the origin of the reference grid to the top side of the image area."
u32 y_offset { 0 }; // "YOsiz" in spec.
// "Width of one reference tile with respect to the reference grid."
u32 tile_width { 0 }; // "XTsiz" in spec.
// "Height of one reference tile with respect to the reference grid."
u32 tile_height { 0 }; // "YTsiz" in spec.
// "Horizontal offset from the origin of the reference grid to the left side of the first tile."
u32 tile_x_offset { 0 }; // "XTOsiz" in spec.
// "Vertical offset from the origin of the reference grid to the top side of the first tile."
u32 tile_y_offset { 0 }; // "YTOsiz" in spec.
// "Csiz" isn't stored in this struct. It corresponds to `components.size()`.
struct ComponentInformation {
// "Precision (depth) in bits and sign of the ith component samples."
u8 depth_and_sign { 0 }; // "Ssiz" in spec.
// Table A.11 – Component Ssiz parameter
u8 bit_depth() const { return (depth_and_sign & 0x7F) + 1; }
bool is_signed() const { return depth_and_sign & 0x80; }
// "Horizontal separation of a sample of the ith component with respect to the reference grid."
u8 horizontal_separation { 0 }; // "XRsiz" in spec.
// "Vertical separation of a sample of the ith component with respect to the reference grid."
u8 vertical_separation { 0 }; // "YRsiz" in spec.
};
Vector<ComponentInformation> components;
// (B-5)
u32 number_of_x_tiles() const { return ceil_div(width - x_offset, tile_width); }
u32 number_of_y_tiles() const { return ceil_div(height - y_offset, tile_height); }
IntPoint tile_2d_index_from_1d_index(u32 tile_index) const
{
// (B-6)
return { tile_index % number_of_x_tiles(), tile_index / number_of_x_tiles() };
}
IntRect reference_grid_coordinates_for_tile(IntPoint tile_2d_index) const
{
int p = tile_2d_index.x();
int q = tile_2d_index.y();
int tx0 = max(tile_x_offset + p * tile_width, x_offset); // (B-7)
int ty0 = max(tile_y_offset + q * tile_height, y_offset); // (B-8)
int tx1 = min(tile_x_offset + (p + 1) * tile_width, width); // (B-9)
int ty1 = min(tile_y_offset + (q + 1) * tile_height, height); // (B-10)
return { tx0, ty0, tx1 - tx0, ty1 - ty0 }; // (B-11)
}
IntRect reference_grid_coordinates_for_tile_component(IntRect tile_rect, int component_index) const
{
// (B-12)
int tcx0 = ceil_div(tile_rect.left(), static_cast<int>(components[component_index].horizontal_separation));
int tcx1 = ceil_div(tile_rect.right(), static_cast<int>(components[component_index].horizontal_separation));
int tcy0 = ceil_div(tile_rect.top(), static_cast<int>(components[component_index].vertical_separation));
int tcy1 = ceil_div(tile_rect.bottom(), static_cast<int>(components[component_index].vertical_separation));
return { tcx0, tcy0, tcx1 - tcx0, tcy1 - tcy0 }; // (B-13)
}
IntRect reference_grid_coordinates_for_tile_component(IntPoint tile_2d_index, int component_index) const
{
auto tile_rect = reference_grid_coordinates_for_tile(tile_2d_index);
return reference_grid_coordinates_for_tile_component(tile_rect, component_index);
}
IntRect reference_grid_coordinates_for_ll_band(IntRect tile_rect, int component_index, int r, int N_L) const
{
// B.5
// (B-14)
auto component_rect = reference_grid_coordinates_for_tile_component(tile_rect, component_index);
int denominator = 1 << (N_L - r);
int trx0 = ceil_div(component_rect.left(), denominator);
int try0 = ceil_div(component_rect.top(), denominator);
int trx1 = ceil_div(component_rect.right(), denominator);
int try1 = ceil_div(component_rect.bottom(), denominator);
return { trx0, try0, trx1 - trx0, try1 - try0 };
}
IntRect reference_grid_coordinates_for_sub_band(IntRect tile_rect, int component_index, int n_b, JPEG2000::SubBand sub_band) const
{
// B.5
// Table B.1 – Quantities (xob, yob) for sub-band b
int xob = 0;
int yob = 0;
if (sub_band == JPEG2000::SubBand::HorizontalHighpassVerticalLowpass || sub_band == JPEG2000::SubBand::HorizontalHighpassVerticalHighpass)
xob = 1;
if (sub_band == JPEG2000::SubBand::HorizontalLowpassVerticalHighpass || sub_band == JPEG2000::SubBand::HorizontalHighpassVerticalHighpass)
yob = 1;
VERIFY(n_b >= 1 || (n_b == 0 && sub_band == JPEG2000::SubBand::HorizontalLowpassVerticalLowpass));
// If n_b is 0, `1 << (n_b - 1)` is undefined, but n_b is only 0 for the LL band, where xob and yob are 0 anyways.
// So the value of o_scale doesn't matter in that case.
int o_scale = 0;
if (n_b > 0)
o_scale = 1 << (n_b - 1);
// (B-15)
auto component_rect = reference_grid_coordinates_for_tile_component(tile_rect, component_index);
int denominator = 1 << n_b;
int tbx0 = ceil_div(component_rect.left() - o_scale * xob, denominator);
int tby0 = ceil_div(component_rect.top() - o_scale * yob, denominator);
int tbx1 = ceil_div(component_rect.right() - o_scale * xob, denominator);
int tby1 = ceil_div(component_rect.bottom() - o_scale * yob, denominator);
return { tbx0, tby0, tbx1 - tbx0, tby1 - tby0 };
}
IntRect reference_grid_coordinates_for_sub_band(IntPoint tile_2d_index, int component_index, int n_b, JPEG2000::SubBand sub_band) const
{
auto tile_rect = reference_grid_coordinates_for_tile(tile_2d_index);
return reference_grid_coordinates_for_sub_band(tile_rect, component_index, n_b, sub_band);
}
};
static ErrorOr<ImageAndTileSize> read_image_and_tile_size(ReadonlyBytes data)
{
FixedMemoryStream stream { data };
ImageAndTileSize siz;
siz.needed_decoder_capabilities = TRY(stream.read_value<BigEndian<u16>>());
siz.width = TRY(stream.read_value<BigEndian<u32>>());
siz.height = TRY(stream.read_value<BigEndian<u32>>());
siz.x_offset = TRY(stream.read_value<BigEndian<u32>>());
siz.y_offset = TRY(stream.read_value<BigEndian<u32>>());
siz.tile_width = TRY(stream.read_value<BigEndian<u32>>());
siz.tile_height = TRY(stream.read_value<BigEndian<u32>>());
siz.tile_x_offset = TRY(stream.read_value<BigEndian<u32>>());
siz.tile_y_offset = TRY(stream.read_value<BigEndian<u32>>());
u16 component_count = TRY(stream.read_value<BigEndian<u16>>()); // "Csiz" in spec.
// Table A.9 – Image and tile size parameter values
// Xsiz, Ysiz, XTsiz, YTsiz: 1 to 2^32-1.
if (siz.width == 0 || siz.height == 0 || siz.tile_width == 0 || siz.tile_height == 0)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid image or tile size");
// Ad-hoc: Limit image size to < 4 GiB.
if (static_cast<u64>(siz.width) * siz.height > INT32_MAX)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Image is suspiciously large, not decoding");
// CSiz: 1 to 16384.
if (component_count < 1 || component_count > 16384)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid number of components");
for (size_t i = 0; i < component_count; ++i) {
ImageAndTileSize::ComponentInformation component;
component.depth_and_sign = TRY(stream.read_value<u8>());
if (component.bit_depth() > 38)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid component depth");
component.horizontal_separation = TRY(stream.read_value<u8>());
component.vertical_separation = TRY(stream.read_value<u8>());
siz.components.append(component);
}
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: SIZ marker segment: needed_decoder_capabilities={}, width={}, height={}, x_offset={}, y_offset={}, tile_width={}, tile_height={}, tile_x_offset={}, tile_y_offset={}", siz.needed_decoder_capabilities, siz.width, siz.height, siz.x_offset, siz.y_offset, siz.tile_width, siz.tile_height, siz.tile_x_offset, siz.tile_y_offset);
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: SIZ marker segment: {} components:", component_count);
for (auto [i, component] : enumerate(siz.components))
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: SIZ marker segment: component[{}]: is_signed={}, bit_depth={}, horizontal_separation={}, vertical_separation={}", i, component.is_signed(), component.bit_depth(), component.horizontal_separation, component.vertical_separation);
return siz;
}
// Data shared by COD and COC marker segments
struct CodingStyleParameters {
// 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 };
JPEG2000::Transformation transformation { JPEG2000::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<PrecinctSize> precinct_sizes;
};
static ErrorOr<CodingStyleParameters> read_coding_style_parameters(ReadonlyBytes data, StringView name, bool has_explicit_precinct_size)
{
FixedMemoryStream stream { data };
CodingStyleParameters parameters;
parameters.number_of_decomposition_levels = TRY(stream.read_value<u8>());
if (parameters.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 = (TRY(stream.read_value<u8>()) & 0xF) + 2;
u8 ycb = (TRY(stream.read_value<u8>()) & 0xF) + 2;
if (xcb > 10 || ycb > 10 || xcb + ycb > 12)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid code block size");
parameters.code_block_width_exponent = xcb;
parameters.code_block_height_exponent = ycb;
parameters.code_block_style = TRY(stream.read_value<u8>());
if (parameters.code_block_style & 0xC0)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Reserved code block style bits set");
// Table A.20 – Transformation for the SPcod and SPcoc parameters
u8 transformation = TRY(stream.read_value<u8>());
if (transformation > 1)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid transformation");
parameters.transformation = transformation == 0 ? JPEG2000::Transformation::Irreversible_9_7_Filter : JPEG2000::Transformation::Reversible_5_3_Filter;
if (has_explicit_precinct_size) {
for (size_t i = 0; i < parameters.number_of_decomposition_levels + 1u; ++i) {
u8 b = TRY(stream.read_value<u8>());
// Table A.21 – Precinct width and height for the SPcod and SPcoc parameters
CodingStyleParameters::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");
parameters.precinct_sizes.append(precinct_size);
}
} else {
for (size_t i = 0; i < parameters.number_of_decomposition_levels + 1u; ++i)
parameters.precinct_sizes.append({ 15, 15 });
}
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: {} marker segment: number_of_decomposition_levels={}, code_block_width_exponent={}, code_block_height_exponent={}", name, parameters.number_of_decomposition_levels, parameters.code_block_width_exponent, parameters.code_block_height_exponent);
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: {} marker segment: code_block_style={}, transformation={}", name, parameters.code_block_style, (int)parameters.transformation);
if (has_explicit_precinct_size) {
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: {} marker segment: {} explicit precinct sizes:", name, parameters.precinct_sizes.size());
for (auto [i, precinct_size] : enumerate(parameters.precinct_sizes))
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: {} marker segment: precinct_size[{}]: PPx={}, PPy={}", name, i, precinct_size.PPx, precinct_size.PPy);
}
return parameters;
}
// 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 shall_use_EPH_marker { false };
// Table A.16 – Progression order for the SGcod, SPcoc, and Ppoc parameters
// B.12 Progression order
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 };
CodingStyleParameters parameters;
};
static ErrorOr<CodingStyleDefault> read_coding_style_default(ReadonlyBytes data)
{
FixedMemoryStream stream { data };
CodingStyleDefault cod;
u8 Scod = TRY(stream.read_value<u8>());
cod.has_explicit_precinct_size = Scod & 1;
cod.may_use_SOP_marker = Scod & 2;
cod.shall_use_EPH_marker = Scod & 4;
u32 SGcod = TRY(stream.read_value<BigEndian<u32>>());
u8 progression_order = SGcod >> 24;
if (progression_order > 4)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid progression order");
cod.progression_order = static_cast<CodingStyleDefault::ProgressionOrder>(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<CodingStyleDefault::MultipleComponentTransformationType>(multiple_component_transformation_type);
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COD marker segment: has_explicit_precinct_size={}, may_use_SOP_marker={}, shall_use_EPH_marker={}", cod.has_explicit_precinct_size, cod.may_use_SOP_marker, cod.shall_use_EPH_marker);
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COD marker segment: progression_order={}, number_of_layers={}, multiple_component_transformation_type={}", (int)cod.progression_order, cod.number_of_layers, (int)cod.multiple_component_transformation_type);
cod.parameters = TRY(read_coding_style_parameters(data.slice(stream.offset()), "COD"sv, cod.has_explicit_precinct_size));
return cod;
}
// A.6.2 Coding style component (COC)
struct CodingStyleComponent {
u16 component_index { 0 }; // "Ccoc" in spec.
// Table A.23 – Coding style parameter values for the Scoc parameter
bool has_explicit_precinct_size { false }; // "Scoc" in spec.
CodingStyleParameters parameters;
};
static ErrorOr<CodingStyleComponent> read_coding_style_component(ReadonlyBytes data, size_t number_of_components)
{
FixedMemoryStream stream { data };
// Table A.22 – Coding style component parameter values
CodingStyleComponent coc;
if (number_of_components < 257)
coc.component_index = TRY(stream.read_value<u8>());
else
coc.component_index = TRY(stream.read_value<BigEndian<u16>>());
u8 Scoc = TRY(stream.read_value<u8>());
coc.has_explicit_precinct_size = Scoc & 1;
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COC marker segment: component_index={}", coc.component_index);
coc.parameters = TRY(read_coding_style_parameters(data.slice(TRY(stream.tell())), "COC"sv, coc.has_explicit_precinct_size));
return coc;
}
// A.6.3 Region of interest (RGN)
struct RegionOfInterest {
u16 component_index { 0 }; // "Crgn" in spec.
// The only valid ROI style in T.800 is 0, so this doesn't "Srgn".
u8 implicit_roi_shift { 0 }; // "SPrgn" in spec and Table A.26 – Region-of-interest values from SPrgn parameter (Srgn = 0).
};
static ErrorOr<RegionOfInterest> read_region_of_interest(ReadonlyBytes data, size_t number_of_components)
{
FixedMemoryStream stream { data };
RegionOfInterest rgn;
if (number_of_components < 257)
rgn.component_index = TRY(stream.read_value<u8>());
else
rgn.component_index = TRY(stream.read_value<BigEndian<u16>>());
u8 roi_style = TRY(stream.read_value<u8>());
if (roi_style != 0)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid ROI style");
rgn.implicit_roi_shift = TRY(stream.read_value<u8>());
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: RGN marker segment: component_index={}, implicit_roi_shift={}", rgn.component_index, rgn.implicit_roi_shift);
return rgn;
}
// A.6.4 Quantization default (QCD)
struct QuantizationDefault {
enum QuantizationStyle {
NoQuantization = 0,
ScalarDerived = 1,
ScalarExpounded = 2,
};
QuantizationStyle quantization_style { NoQuantization };
u8 number_of_guard_bits { 0 };
struct ReversibleStepSize {
u8 exponent { 0 };
};
struct IrreversibleStepSize {
u16 mantissa { 0 };
u8 exponent { 0 };
};
// Stores a Vector<ReversibleStepSize> if quantization_style is NoQuantization, and a Vector<IrreversibleStepSize> otherwise.
// The size of the vector is >= 3*number_of_decomposition_levels + 1 if quantization_style is not ScalarDerived, and 1 otherwise.
using StepSizeType = Variant<Empty, Vector<ReversibleStepSize>, Vector<IrreversibleStepSize>>;
StepSizeType step_sizes;
};
static ErrorOr<QuantizationDefault> read_quantization_default(ReadonlyBytes data, StringView marker_name = "QCD"sv)
{
FixedMemoryStream stream { data };
QuantizationDefault qcd;
u8 sqcd = TRY(stream.read_value<u8>());
u8 quantization_style = sqcd & 0x1F;
if (quantization_style > 2)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid quantization style");
qcd.quantization_style = static_cast<QuantizationDefault::QuantizationStyle>(quantization_style);
qcd.number_of_guard_bits = sqcd >> 5;
qcd.step_sizes = TRY([&]() -> ErrorOr<QuantizationDefault::StepSizeType> {
if (quantization_style == QuantizationDefault::NoQuantization) {
// Table A.29 – Reversible step size values for the SPqcd and SPqcc parameters (reversible transform only)
if (data.size() < 2)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for QCD marker segment");
u8 number_of_decomposition_levels = (data.size() - 2) / 3;
Vector<QuantizationDefault::ReversibleStepSize> reversible_step_sizes;
for (size_t i = 0; i < 1u + 3u * number_of_decomposition_levels; ++i)
reversible_step_sizes.append({ static_cast<u8>(TRY(stream.read_value<u8>()) >> 3) });
return reversible_step_sizes;
}
// Table A.30 – Quantization values for the SPqcd and SPqcc parameters (irreversible transformation only)
if (data.size() < 3)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for QCD marker segment");
u8 number_of_decomposition_levels = 0;
if (quantization_style == QuantizationDefault::ScalarExpounded)
number_of_decomposition_levels = (data.size() - 3) / 6;
Vector<QuantizationDefault::IrreversibleStepSize> irreversible_step_sizes;
for (size_t i = 0; i < 1u + 3u * number_of_decomposition_levels; ++i) {
u16 value = TRY(stream.read_value<BigEndian<u16>>());
QuantizationDefault::IrreversibleStepSize step_size;
step_size.mantissa = value & 0x7FF;
step_size.exponent = value >> 11;
irreversible_step_sizes.append(step_size);
}
return irreversible_step_sizes;
}());
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: {} marker segment: quantization_style={}, number_of_guard_bits={}", marker_name, (int)qcd.quantization_style, qcd.number_of_guard_bits);
qcd.step_sizes.visit(
[](Empty) { VERIFY_NOT_REACHED(); },
[&](Vector<QuantizationDefault::ReversibleStepSize> const& step_sizes) {
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: {} marker segment: {} step sizes:", marker_name, step_sizes.size());
for (auto [i, step_size] : enumerate(step_sizes)) {
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: {} marker segment: step_size[{}]: exponent={}", marker_name, i, step_size.exponent);
}
},
[&](Vector<QuantizationDefault::IrreversibleStepSize> const& step_sizes) {
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: {} marker segment: {} step sizes:", marker_name, step_sizes.size());
for (auto [i, step_size] : enumerate(step_sizes)) {
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: {} marker segment: step_size[{}]: mantissa={}, exponent={}", marker_name, i, step_size.mantissa, step_size.exponent);
}
});
return qcd;
}
// A.6.5 Quantization component (QCC)
struct QuantizationComponent {
u16 component_index { 0 }; // "Cqcc" in spec.
QuantizationDefault qcd;
};
static ErrorOr<QuantizationComponent> read_quantization_component(ReadonlyBytes data, size_t number_of_components)
{
FixedMemoryStream stream { data };
QuantizationComponent qcc;
if (number_of_components < 257)
qcc.component_index = TRY(stream.read_value<u8>());
else
qcc.component_index = TRY(stream.read_value<BigEndian<u16>>());
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: QCC marker segment: component_index={}", qcc.component_index);
qcc.qcd = TRY(read_quantization_default(data.slice(TRY(stream.tell())), "QCC"sv));
return qcc;
}
// A.6.6 Progression order change (POC)
struct ProgressionOrderChange {
struct Entry {
// Start indices are all inclusive, end indices all exclusive.
// layer_start is implicitly always 0 and not stored in the codestream.
u8 resolution_level_start { 0 }; // "RSpoc" in spec.
u16 component_start { 0 }; // "CSpoc" in spec.
u16 layer_end { 0 }; // "LYEpoc" in spec.
u8 resolution_level_end { 0 }; // "REpoc" in spec.
u16 component_end { 0 }; // "CEpoc" in spec.
CodingStyleDefault::ProgressionOrder progression_order { CodingStyleDefault::ProgressionOrder::LayerResolutionComponentPosition }; // "Ppoc" in spec.
};
Vector<Entry> entries;
};
static ErrorOr<ProgressionOrderChange> read_progression_order_change(ReadonlyBytes data, size_t number_of_components)
{
FixedMemoryStream stream { data };
auto entry_size = number_of_components < 257 ? 7 : 9;
if (data.size() % entry_size != 0)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid POC marker segment size");
auto entry_count = data.size() / entry_size;
ProgressionOrderChange poc;
TRY(poc.entries.try_ensure_capacity(entry_count));
for (size_t i = 0; i < entry_count; ++i) {
// Table A.32 – Progression order change, tile parameter values
ProgressionOrderChange::Entry entry;
entry.resolution_level_start = TRY(stream.read_value<u8>());
if (entry.resolution_level_start > 32)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid resolution level start in POC");
if (number_of_components < 257)
entry.component_start = TRY(stream.read_value<u8>());
else
entry.component_start = TRY(stream.read_value<BigEndian<u16>>());
if (entry.component_start > 16'383)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid component start in POC");
entry.layer_end = TRY(stream.read_value<BigEndian<u16>>());
if (entry.layer_end == 0)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid layer end in POC");
entry.resolution_level_end = TRY(stream.read_value<u8>());
if (entry.resolution_level_end <= entry.resolution_level_start || entry.resolution_level_end > 33)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid resolution level end in POC");
if (number_of_components < 257)
entry.component_end = TRY(stream.read_value<u8>());
else
entry.component_end = TRY(stream.read_value<BigEndian<u16>>());
if (entry.component_end == 0)
entry.component_end = 256; // "(0 is interpreted as 256)"
if (entry.component_end <= entry.component_start || entry.component_end > 16'384)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid component end in POC");
u8 progression_order = TRY(stream.read_value<u8>());
if (progression_order > 4)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid progression order in POC");
entry.progression_order = static_cast<CodingStyleDefault::ProgressionOrder>(progression_order);
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: POC marker segment: entry[{}]: resolution_level_start={}, component_start={}, layer_end={}, resolution_level_end={}, component_end={}, progression_order={}",
i, entry.resolution_level_start, entry.component_start, entry.layer_end, entry.resolution_level_end, entry.component_end, (int)entry.progression_order);
poc.entries.append(entry);
}
return poc;
}
// A.9.2 Comment (COM)
struct Comment {
enum CommentType {
Binary = 0,
ISO_IEC_8859_15 = 1,
};
CommentType type { Binary }; // "Rcom" in spec.
ReadonlyBytes data;
};
static ErrorOr<Comment> read_comment(ReadonlyBytes data)
{
FixedMemoryStream stream { data };
Comment com;
u16 comment_type = TRY(stream.read_value<BigEndian<u16>>());
if (comment_type > 1)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid comment type");
com.type = static_cast<Comment::CommentType>(comment_type);
com.data = data.slice(TRY(stream.tell()));
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COM marker segment: comment_type={}, size()={}", (int)com.type, com.data.size());
if (com.type == Comment::ISO_IEC_8859_15)
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: COM marker segment, ISO/IEC 8859-15 text: '{}'", TRY(TextCodec::decoder_for("ISO-8859-15"sv)->to_utf8(StringView { com.data })));
return com;
}
struct TilePartData {
StartOfTilePart sot;
Vector<Comment> coms;
ReadonlyBytes data;
};
struct DecodedCodeBlock {
IntRect rect; // Confined to sub-band rect.
// Transient state used to read packet headers.
// B.10.4 Code-block inclusion
bool is_included { false };
// B.10.7.1 Single codeword segment
// "Lblock is a code-block state variable. [...] The value of Lblock is initially set to three."
u32 Lblock { 3 };
// Becomes true when the first packet including this codeblock is read.
bool has_been_included_in_previous_packet { false };
// Data read from packet headers.
// B.10.5 Zero bit-plane information
// "the number of missing most significant bit-planes, P, may vary from code-block to code-block;
// these missing bit-planes are all taken to be zero."
u32 p { 0 };
struct Layer {
struct Segment {
ReadonlyBytes data;
u32 index { 0 };
int number_of_passes { 0 };
};
Vector<Segment, 1> segments;
};
Vector<Layer, 1> layers;
u32 number_of_coding_passes() const
{
u32 total = 0;
for (auto const& layer : layers)
for (auto const& segment : layer.segments)
total += segment.number_of_passes;
return total;
}
u32 number_of_coding_passes_in_segment(u32 segment_index) const
{
u32 total = 0;
for (auto const& layer : layers) {
for (auto const& segment : layer.segments) {
if (segment.index == segment_index)
total += segment.number_of_passes;
}
}
return total;
}
Optional<u32> highest_segment_index() const
{
Optional<u32> highest_index;
for (auto const& layer : layers) {
for (auto const& segment : layer.segments)
highest_index = max(highest_index.value_or(segment.index), segment.index);
}
return highest_index;
}
ErrorOr<Vector<ReadonlyBytes, 1>> segments_for_all_layers(ByteBuffer& maybe_storage) const
{
Vector<Vector<ReadonlyBytes, 1>, 1> all_segment_parts_for_segment;
all_segment_parts_for_segment.resize(highest_segment_index().value_or(0) + 1);
for (auto const& layer : layers)
for (auto const& segment : layer.segments)
TRY(all_segment_parts_for_segment[segment.index].try_append(segment.data));
// Copy segments with multiple parts into consecutive storage.
size_t total_scratch_size = 0;
for (auto const& segment_parts : all_segment_parts_for_segment) {
if (segment_parts.size() > 1) {
for (auto const& segment_part : segment_parts)
total_scratch_size += segment_part.size();
}
}
if (total_scratch_size > 0)
maybe_storage = TRY(ByteBuffer::create_uninitialized(total_scratch_size));
Vector<ReadonlyBytes, 1> all_segments;
size_t scratch_offset = 0;
for (auto& segment_parts : all_segment_parts_for_segment) {
if (segment_parts.size() == 1) {
TRY(all_segments.try_append(segment_parts[0]));
continue;
}
auto start = scratch_offset;
for (auto const& segment_part : segment_parts) {
memcpy(maybe_storage.offset_pointer(scratch_offset), segment_part.data(), segment_part.size());
scratch_offset += segment_part.size();
}
TRY(all_segments.try_append(maybe_storage.bytes().slice(start, scratch_offset - start)));
}
return all_segments;
}
};
struct DecodedPrecinct {
IntRect rect; // NOT confined to sub-band rect.
int num_code_blocks_wide { 0 };
int num_code_blocks_high { 0 };
Vector<DecodedCodeBlock> code_blocks;
// Transient state used to read packet headers.
Optional<JPEG2000::TagTree> code_block_inclusion_tree;
Optional<JPEG2000::TagTree> p_tree;
};
struct DecodedSubBand {
IntRect rect;
// These are the same for all three sub-bands at a given resolution level.
int num_precincts_wide { 0 };
int num_precincts_high { 0 };
Vector<DecodedPrecinct> precincts;
// Valid after bitplane decoding. rect.width() * rect.height() == coefficients.size().
Vector<float> coefficients;
};
struct DecodedTileComponent {
IntRect rect;
DecodedSubBand nLL; // N_L LL in the spec, corresponds to resolution level 0.
using DecodedSubBands = Array<DecodedSubBand, 3>; // Ordered HL, LH, HH.
Vector<DecodedSubBands> decompositions;
static constexpr Array SubBandOrder { JPEG2000::SubBand::HorizontalHighpassVerticalLowpass, JPEG2000::SubBand::HorizontalLowpassVerticalHighpass, JPEG2000::SubBand::HorizontalHighpassVerticalHighpass };
// Valid after IDWT.
Vector<float> samples;
};
struct TileData {
// Data from codestream markers.
Optional<CodingStyleDefault> cod;
Vector<CodingStyleComponent> cocs;
Vector<RegionOfInterest> rgns;
Optional<QuantizationDefault> qcd;
Vector<QuantizationComponent> qccs;
Optional<ProgressionOrderChange> poc;
Vector<TilePartData> tile_parts;
// Data used during decoding.
IntRect rect;
Vector<DecodedTileComponent> components;
Vector<Vector<float>> channels;
Vector<ImageAndTileSize::ComponentInformation> channel_information;
// FIXME: This will have to move and be reorganized come POC support.
OwnPtr<JPEG2000::ProgressionIterator> progression_iterator;
};
enum class ColorSpace {
sRGB,
Gray,
CMYK,
Unsupported,
};
struct JPEG2000LoadingContext {
enum class State {
NotDecoded = 0,
DecodedImage,
Error,
};
State state { State::NotDecoded };
ReadonlyBytes codestream_data;
size_t codestream_cursor { 0 };
JPEG2000DecoderOptions options;
Optional<ISOBMFF::JPEG2000ColorSpecificationBox const&> color_box; // This is always set for box-based files.
Optional<ISOBMFF::JPEG2000PaletteBox const&> palette_box;
Optional<ISOBMFF::JPEG2000ComponentMappingBox const&> component_mapping_box;
Optional<ISOBMFF::JPEG2000ChannelDefinitionBox const&> channel_definition_box;
IntSize size;
ISOBMFF::BoxList boxes;
// Data from marker segments:
ImageAndTileSize siz;
CodingStyleDefault cod;
Vector<CodingStyleComponent> cocs;
Vector<RegionOfInterest> rgns;
QuantizationDefault qcd;
Vector<QuantizationComponent> qccs;
Optional<ProgressionOrderChange> poc;
Vector<Comment> coms;
Vector<TileData> tiles;
// Valid after headers have been decoded.
// The awkward `color_space_error` is so that determine_color_space() can always succeed and
// e.g. `file` can return data for JPEG2000s even if we can't decode the image data due to not
// yet supporting its colorspace.
ColorSpace color_space { ColorSpace::Unsupported };
Optional<Error> color_space_error;
// Valid once `state` is StateDecodedImage.
RefPtr<Bitmap> bitmap;
RefPtr<CMYKBitmap> cmyk_bitmap;
CodingStyleParameters const& coding_style_parameters_for_component(TileData const& tile, size_t component_index) const
{
// Tile-part COC > Tile-part COD > Main COC > Main COD
for (auto const& coc : tile.cocs) {
if (coc.component_index == component_index)
return coc.parameters;
}
if (tile.cod.has_value())
return tile.cod->parameters;
for (auto const& coc : cocs) {
if (coc.component_index == component_index)
return coc.parameters;
}
return cod.parameters;
}
QuantizationDefault const& quantization_parameters_for_component(TileData const& tile, size_t component_index)
{
// Tile-part QCC > Tile-part QCD > Main QCC > Main QCD
for (auto const& qcc : tile.qccs) {
if (qcc.component_index == component_index)
return qcc.qcd;
}
if (tile.qcd.has_value())
return tile.qcd.value();
for (auto const& qcc : qccs) {
if (qcc.component_index == component_index)
return qcc.qcd;
}
return qcd;
}
ErrorOr<JPEG2000::ProgressionData> next_progression_data(TileData& tile) const
{
JPEG2000::ProgressionData progression_data;
auto progression_data_has_packet = [&](JPEG2000::ProgressionData const& progression_data) {
if (progression_data.resolution_level > coding_style_parameters_for_component(tile, progression_data.component).number_of_decomposition_levels)
return false;
// "It can happen that numprecincts is 0 for a particular tile-component and resolution level. When this happens, there are no
// packets for this tile-component and resolution level."
// `num_precincts_wide` and `num_precincts_high` are the same for all sub-bands at a given resolution level, so it's
// enough to only check the first.
auto& component = tile.components[progression_data.component];
auto& sub_band_data = progression_data.resolution_level == 0 ? component.nLL : component.decompositions[progression_data.resolution_level - 1][0];
if (sub_band_data.num_precincts_wide == 0 || sub_band_data.num_precincts_high == 0)
return false;
return true;
};
do {
if (!tile.progression_iterator->has_next())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: No more progression orders but packets left");
progression_data = tile.progression_iterator->next();
} while (!progression_data_has_packet(progression_data));
return progression_data;
}
};
struct MarkerSegment {
u16 marker;
// OptionalNone for markers that don't have data.
// For markers that do have data, this does not include the marker length data. (`data.size() + 2` is the value of the marker length field.)
Optional<ReadonlyBytes> data;
};
static ErrorOr<u16> peek_marker(ReadonlyBytes data)
{
if (2 > data.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for marker");
return *reinterpret_cast<BigEndian<u16> const*>(data.data());
}
static ErrorOr<u16> peek_marker(JPEG2000LoadingContext& context)
{
return peek_marker(context.codestream_data.slice(context.codestream_cursor));
}
static ErrorOr<MarkerSegment> read_marker_at_cursor(JPEG2000LoadingContext& context)
{
u16 marker = TRY(peek_marker(context));
// "All markers with the marker code between 0xFF30 and 0xFF3F have no marker segment parameters. They shall be skipped by the decoder."
// "The SOC, SOD and EOC are delimiting markers not marker segments, and have no explicit length information or other parameters."
bool is_marker_segment = !(marker >= 0xFF30 && marker <= 0xFF3F) && marker != J2K_SOC && marker != J2K_SOD && marker != J2K_EOC;
MarkerSegment marker_segment;
marker_segment.marker = marker;
if (is_marker_segment) {
if (context.codestream_cursor + 4 > context.codestream_data.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for marker segment length");
u16 marker_length = *reinterpret_cast<BigEndian<u16> const*>(context.codestream_data.data() + context.codestream_cursor + 2);
if (marker_length < 2)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Marker segment length too small");
if (context.codestream_cursor + 2 + marker_length > context.codestream_data.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for marker segment data");
marker_segment.data = ReadonlyBytes { context.codestream_data.data() + context.codestream_cursor + 4, marker_length - 2u };
}
context.codestream_cursor += 2;
if (is_marker_segment)
context.codestream_cursor += 2 + marker_segment.data->size();
return marker_segment;
}
static ErrorOr<void> parse_codestream_main_header(JPEG2000LoadingContext& context)
{
// Figure A.3 – Construction of the main header
// "Required as the first marker"
auto marker = TRY(read_marker_at_cursor(context));
if (marker.marker != J2K_SOC)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected SOC marker");
// "Required as the second marker segment"
marker = TRY(read_marker_at_cursor(context));
if (marker.marker != J2K_SIZ)
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;
bool saw_QCD_marker = false;
while (true) {
u16 marker = TRY(peek_marker(context));
switch (marker) {
case J2K_COD:
case J2K_COC:
case J2K_QCD:
case J2K_QCC:
case J2K_RGN:
case J2K_POC:
case J2K_PPM:
case J2K_TLM:
case J2K_PLM:
case J2K_CRG:
case J2K_COM: {
auto marker = TRY(read_marker_at_cursor(context));
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 if (marker.marker == J2K_COC) {
context.cocs.append(TRY(read_coding_style_component(marker.data.value(), context.siz.components.size())));
} else if (marker.marker == J2K_QCD) {
if (saw_QCD_marker)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple QCD markers in main header");
context.qcd = TRY(read_quantization_default(marker.data.value()));
saw_QCD_marker = true;
} else if (marker.marker == J2K_QCC) {
context.qccs.append(TRY(read_quantization_component(marker.data.value(), context.siz.components.size())));
} else if (marker.marker == J2K_RGN) {
context.rgns.append(TRY(read_region_of_interest(marker.data.value(), context.siz.components.size())));
} else if (marker.marker == J2K_POC) {
if (context.poc.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple POC markers in main header");
context.poc = TRY(read_progression_order_change(marker.data.value(), context.siz.components.size()));
} else if (marker.marker == J2K_PPM) {
// FIXME: Implement. (I haven't yet found a way to generate files that use this.)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: PPM marker not yet implemented");
} else if (marker.marker == J2K_TLM) {
// TLM describes tile-part lengths, for random access. They can be ignored for now.
} else if (marker.marker == J2K_PLM) {
// PLM describes packet lengths, for random access. They can be ignored for now.
} else if (marker.marker == J2K_CRG) {
// FIXME: Implement. (I haven't yet found a way to generate files that use this.)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: CRG marker not yet implemented");
} else if (marker.marker == J2K_COM) {
context.coms.append(TRY(read_comment(marker.data.value())));
} else {
VERIFY_NOT_REACHED();
}
break;
}
case J2K_SOT: {
// SOT terminates the main header.
// A.4.2: "There shall be at least one SOT in a codestream."
if (!saw_COD_marker)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Required COD marker not present in main header");
if (!saw_QCD_marker)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Required QCD marker not present in main header");
// A.6.4: "there is not necessarily a correspondence with the number of sub-bands present because the sub-bands
// can be truncated with no requirement to correct [the QCD] marker segment."
size_t step_sizes_count = context.qcd.step_sizes.visit(
[](Empty) -> size_t { VERIFY_NOT_REACHED(); },
[](Vector<QuantizationDefault::ReversibleStepSize> const& step_sizes) { return step_sizes.size(); },
[](Vector<QuantizationDefault::IrreversibleStepSize> const& step_sizes) { return step_sizes.size(); });
// FIXME: What if number_of_decomposition_levels is in context.cocs and varies by component?
if (context.qcd.quantization_style != QuantizationDefault::ScalarDerived && step_sizes_count < context.cod.parameters.number_of_decomposition_levels * 3u + 1u)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough step sizes for number of decomposition levels");
return {};
}
default:
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Unexpected marker in main header");
}
}
}
static ErrorOr<void> parse_codestream_tile_header(JPEG2000LoadingContext& context)
{
// Figure A.4 – Construction of the first tile-part header of a given tile
// Figure A.5 – Construction of a non-first tile-part header
// "Required as the first marker segment of every tile-part header"
auto tile_start = context.codestream_cursor;
auto marker = TRY(read_marker_at_cursor(context));
if (marker.marker != J2K_SOT)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected SOT marker");
auto start_of_tile = TRY(read_start_of_tile_part(marker.data.value()));
context.tiles.resize(max(context.tiles.size(), (size_t)start_of_tile.tile_index + 1));
auto& tile = context.tiles[start_of_tile.tile_index];
if (tile.tile_parts.size() != start_of_tile.tile_part_index)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Tile part index out of order");
tile.tile_parts.append({});
auto& tile_part = tile.tile_parts.last();
tile_part.sot = start_of_tile;
bool found_start_of_data = false;
while (!found_start_of_data) {
u16 marker = TRY(peek_marker(context));
switch (marker) {
case J2K_SOD:
// "Required as the last marker segment of every tile-part header"
context.codestream_cursor += 2;
found_start_of_data = true;
break;
case J2K_COD:
case J2K_COC:
case J2K_QCD:
case J2K_QCC:
case J2K_RGN:
if (start_of_tile.tile_part_index != 0)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: COD, COC, QCD, QCC, RGN markers are only valid in the first tile-part header");
[[fallthrough]];
case J2K_POC:
case J2K_PPT:
case J2K_PLT:
case J2K_COM: {
auto marker = TRY(read_marker_at_cursor(context));
if (marker.marker == J2K_COD) {
if (tile.cod.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple COD markers in tile header");
tile.cod = TRY(read_coding_style_default(marker.data.value()));
} else if (marker.marker == J2K_COC) {
tile.cocs.append(TRY(read_coding_style_component(marker.data.value(), context.siz.components.size())));
} else if (marker.marker == J2K_QCD) {
if (tile.qcd.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple QCD markers in tile header");
tile.qcd = TRY(read_quantization_default(marker.data.value()));
} else if (marker.marker == J2K_QCC) {
tile.qccs.append(TRY(read_quantization_component(marker.data.value(), context.siz.components.size())));
} else if (marker.marker == J2K_RGN) {
tile.rgns.append(TRY(read_region_of_interest(marker.data.value(), context.siz.components.size())));
} else if (marker.marker == J2K_POC) {
if (tile.poc.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple POC markers in tile header");
tile.poc = TRY(read_progression_order_change(marker.data.value(), context.siz.components.size()));
} else if (marker.marker == J2K_PPT) {
// FIXME: Implement. (I haven't yet found a way to generate files that use this.)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: PPT marker not yet implemented");
} else if (marker.marker == J2K_PLT) {
// PLT describes packet lengths, for random access. They can be ignored for now.
} else if (marker.marker == J2K_COM) {
tile_part.coms.append(TRY(read_comment(marker.data.value())));
} else {
VERIFY_NOT_REACHED();
}
break;
}
default:
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Unexpected marker in tile header");
}
}
u32 tile_bitstream_length;
if (start_of_tile.tile_part_length == 0) {
// Leave room for EOC marker.
if (context.codestream_data.size() - context.codestream_cursor < 2)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for EOC marker");
tile_bitstream_length = context.codestream_data.size() - context.codestream_cursor - 2;
} else {
u32 tile_header_length = context.codestream_cursor - tile_start;
if (start_of_tile.tile_part_length < tile_header_length)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid tile part length");
tile_bitstream_length = start_of_tile.tile_part_length - tile_header_length;
}
if (context.codestream_cursor + tile_bitstream_length > context.codestream_data.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for tile bitstream");
tile_part.data = context.codestream_data.slice(context.codestream_cursor, tile_bitstream_length);
context.codestream_cursor += tile_bitstream_length;
dbgln_if(JPEG2000_DEBUG, "JPEG2000ImageDecoderPlugin: Tile bitstream length: {}", tile_bitstream_length);
return {};
}
static ErrorOr<void> parse_codestream_tile_headers(JPEG2000LoadingContext& context)
{
while (true) {
auto marker = TRY(peek_marker(context));
if (marker == J2K_EOC) {
context.codestream_cursor += 2;
break;
}
TRY(parse_codestream_tile_header(context));
}
if (context.codestream_cursor < context.codestream_data.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Unexpected data after EOC marker");
return {};
}
static ErrorOr<void> decode_jpeg2000_header(JPEG2000LoadingContext& context, ReadonlyBytes data)
{
if (!JPEG2000ImageDecoderPlugin::sniff(data))
return Error::from_string_literal("JPEG2000LoadingContext: Invalid JPEG2000 header");
if (data.starts_with(marker_id_string)) {
context.codestream_data = data;
TRY(parse_codestream_main_header(context));
context.size = { context.siz.width, context.siz.height };
return {};
}
auto reader = TRY(Gfx::ISOBMFF::Reader::create(TRY(try_make<FixedMemoryStream>(data))));
context.boxes = TRY(reader.read_entire_file());
dbgln_if(JPEG2000_DEBUG, "Embedded ISOBMFF boxes:");
if constexpr (JPEG2000_DEBUG) {
for (auto& box : context.boxes)
box->dump();
}
// I.2.2 File organization
// "A particular order of those boxes in the file is not generally implied. However, the JPEG 2000 Signature box
// shall be the first box in a JP2 file, the File Type box shall immediately follow the JPEG 2000 Signature box
// and the JP2 Header box shall fall before the Contiguous Codestream box."
if (context.boxes.size() < 4)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected at least four boxes");
// Required toplevel boxes: signature box, file type box, jp2 header box, contiguous codestream box.
if (context.boxes[0]->box_type() != ISOBMFF::BoxType::JPEG2000SignatureBox)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected JPEG2000SignatureBox as first box");
if (context.boxes[1]->box_type() != ISOBMFF::BoxType::FileTypeBox)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected FileTypeBox as second box");
Optional<size_t> jp2_header_box_index;
Optional<size_t> contiguous_codestream_box_index;
for (size_t i = 2; i < context.boxes.size(); ++i) {
if (context.boxes[i]->box_type() == ISOBMFF::BoxType::JPEG2000HeaderBox) {
// "Within a JP2 file, there shall be one and only one JP2 Header box."
if (jp2_header_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple JP2 Header boxes");
jp2_header_box_index = i;
}
if (context.boxes[i]->box_type() == ISOBMFF::BoxType::JPEG2000ContiguousCodestreamBox && !contiguous_codestream_box_index.has_value()) {
// "a conforming reader shall ignore all codestreams after the first codestream found in the file.
// Contiguous Codestream boxes may be found anywhere in the file except before the JP2 Header box."
contiguous_codestream_box_index = i;
if (!jp2_header_box_index.has_value() || contiguous_codestream_box_index.value() < jp2_header_box_index.value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: JP2 Header box must come before Contiguous Codestream box");
}
}
if (!jp2_header_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected JP2 Header box");
if (!contiguous_codestream_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected Contiguous Codestream box");
// FIXME: JPEG2000ContiguousCodestreamBox makes a copy of the codestream data. That's too heavy for header scanning.
// Add a mode to ISOBMFF::Reader where it only stores offsets for the codestream data and the ICC profile.
auto const& codestream_box = static_cast<ISOBMFF::JPEG2000ContiguousCodestreamBox const&>(*context.boxes[contiguous_codestream_box_index.value()]);
context.codestream_data = codestream_box.codestream.bytes();
// Required child boxes of the jp2 header box: image header box, color box.
Optional<size_t> image_header_box_index;
Optional<size_t> color_header_box_index;
Optional<size_t> palette_box_index;
Optional<size_t> component_mapping_box_index;
Optional<size_t> channel_definition_box_index;
auto const& header_box = static_cast<ISOBMFF::JPEG2000HeaderBox const&>(*context.boxes[jp2_header_box_index.value()]);
for (size_t i = 0; i < header_box.child_boxes().size(); ++i) {
auto const& subbox = header_box.child_boxes()[i];
if (subbox->box_type() == ISOBMFF::BoxType::JPEG2000ImageHeaderBox) {
if (image_header_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple Image Header boxes");
image_header_box_index = i;
}
if (subbox->box_type() == ISOBMFF::BoxType::JPEG2000ColorSpecificationBox) {
// T.800 says there should be just one 'colr' box, but T.801 allows several and says to pick the one with highest precedence.
bool use_this_color_box;
if (!color_header_box_index.has_value()) {
use_this_color_box = true;
} else {
auto const& new_header_box = static_cast<ISOBMFF::JPEG2000ColorSpecificationBox const&>(*header_box.child_boxes()[i]);
auto const& current_color_box = static_cast<ISOBMFF::JPEG2000ColorSpecificationBox const&>(*header_box.child_boxes()[color_header_box_index.value()]);
use_this_color_box = new_header_box.precedence > current_color_box.precedence;
}
if (use_this_color_box)
color_header_box_index = i;
}
if (subbox->box_type() == ISOBMFF::BoxType::JPEG2000PaletteBox) {
// T.800, I.5.3.4 Palette box
// "There shall be at most one Palette box inside a JP2 Header box."
if (palette_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple Palette boxes");
palette_box_index = i;
}
if (subbox->box_type() == ISOBMFF::BoxType::JPEG2000ComponentMappingBox) {
// T.800, I.5.3.5 Component Mapping box
// "There shall be at most one Component Mapping box inside a JP2 Header box."
if (component_mapping_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple Component Mapping boxes");
component_mapping_box_index = i;
}
if (subbox->box_type() == ISOBMFF::BoxType::JPEG2000ChannelDefinitionBox) {
// T.800, I.5.3.6 Channel Definition box
// "There shall be at most one Channel Definition box inside a JP2 Header box."
if (channel_definition_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple Channel Definition boxes");
channel_definition_box_index = i;
}
}
if (!image_header_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected Image Header box");
if (!color_header_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected Color Specification box");
auto const& image_header_box = static_cast<ISOBMFF::JPEG2000ImageHeaderBox const&>(*header_box.child_boxes()[image_header_box_index.value()]);
context.size = { image_header_box.width, image_header_box.height };
if (image_header_box.compression_type != ISOBMFF::JPEG2000ImageHeaderBox::CompressionType::Default)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Decoding of non-jpeg2000 data embedded in jpeg2000 files is not implemented");
context.color_box = static_cast<ISOBMFF::JPEG2000ColorSpecificationBox const&>(*header_box.child_boxes()[color_header_box_index.value()]);
// "If the JP2 Header box contains a Palette box, then it shall also contain a Component Mapping box.
// If the JP2 Header box does not contain a Palette box, then it shall not contain a Component Mapping box."
// This is violated in practice though; some files have a Palette box without a Component Mapping box.
// So check for something weaker.
if (!palette_box_index.has_value() && component_mapping_box_index.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Component Mapping should not be present without Palette box");
if (palette_box_index.has_value())
context.palette_box = static_cast<ISOBMFF::JPEG2000PaletteBox const&>(*header_box.child_boxes()[palette_box_index.value()]);
if (component_mapping_box_index.has_value())
context.component_mapping_box = static_cast<ISOBMFF::JPEG2000ComponentMappingBox const&>(*header_box.child_boxes()[component_mapping_box_index.value()]);
if (channel_definition_box_index.has_value())
context.channel_definition_box = static_cast<ISOBMFF::JPEG2000ChannelDefinitionBox const&>(*header_box.child_boxes()[channel_definition_box_index.value()]);
TRY(parse_codestream_main_header(context));
auto size_from_siz = IntSize { context.siz.width, context.siz.height };
if (size_from_siz != context.size) {
// FIXME: If this is common, warn and use size from SIZ marker.
dbgln("JPEG2000ImageDecoderPlugin: Image size from SIZ marker ({}) does not match image size from JP2 header ({})", size_from_siz, context.size);
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Image size from SIZ marker does not match image size from JP2 header");
}
return {};
}
static IntRect aligned_enclosing_rect(IntRect outer_rect, IntRect inner_rect, int width_increment, int height_increment)
{
int new_x = (inner_rect.x() / width_increment) * width_increment;
int new_y = (inner_rect.y() / height_increment) * height_increment;
int new_right = inner_rect.width() == 0 ? new_x : ceil_div(inner_rect.right(), width_increment) * width_increment;
int new_bottom = inner_rect.height() == 0 ? new_y : ceil_div(inner_rect.bottom(), height_increment) * height_increment;
return IntRect::intersection(outer_rect, IntRect::from_two_points({ new_x, new_y }, { new_right, new_bottom }));
}
static ErrorOr<void> compute_decoding_metadata(JPEG2000LoadingContext& context)
{
auto make_precinct = [&](DecodedSubBand const& sub_band, IntRect precinct_rect, int xcb_prime, int ycb_prime) -> ErrorOr<DecodedPrecinct> {
auto rect_covered_by_codeblocks = aligned_enclosing_rect(precinct_rect, sub_band.rect, 1 << xcb_prime, 1 << ycb_prime);
auto num_code_blocks_wide = rect_covered_by_codeblocks.width() / (1 << xcb_prime);
auto num_code_blocks_high = rect_covered_by_codeblocks.height() / (1 << ycb_prime);
DecodedPrecinct precinct;
precinct.rect = precinct_rect;
precinct.num_code_blocks_wide = num_code_blocks_wide;
precinct.num_code_blocks_high = num_code_blocks_high;
precinct.code_blocks.resize(num_code_blocks_wide * num_code_blocks_high);
dbgln_if(JPEG2000_DEBUG, "Precinct rect: {}, num_code_blocks_wide: {}, num_code_blocks_high: {}", precinct.rect, num_code_blocks_wide, num_code_blocks_high);
for (auto [code_block_index, current_block] : enumerate(precinct.code_blocks)) {
size_t code_block_x = code_block_index % num_code_blocks_wide;
size_t code_block_y = code_block_index / num_code_blocks_wide;
auto code_block_rect = IntRect { { code_block_x * (1 << xcb_prime), code_block_y * (1 << ycb_prime) }, { 1 << xcb_prime, 1 << ycb_prime } };
code_block_rect.set_location(code_block_rect.location() + rect_covered_by_codeblocks.location());
// B.7 Division of the sub-bands into code-blocks
// "NOTE – Code-blocks in the partition may extend beyond the boundaries of the sub-band coefficients. When this happens, only the
// coefficients lying within the sub-band are coded using the method described in Annex D. The first stripe coded using this method
// corresponds to the first four rows of sub-band coefficients in the code-block or to as many such rows as are present."
current_block.rect = code_block_rect.intersected(sub_band.rect);
}
if (!precinct.code_blocks.is_empty()) {
precinct.code_block_inclusion_tree = TRY(JPEG2000::TagTree::create(num_code_blocks_wide, num_code_blocks_high));
precinct.p_tree = TRY(JPEG2000::TagTree::create(num_code_blocks_wide, num_code_blocks_high));
}
return precinct;
};
auto make_sub_band = [&](TileData const& tile, int component_index, DecodedSubBand& sub_band, JPEG2000::SubBand sub_band_type, int r) -> ErrorOr<void> {
auto const& coding_parameters = context.coding_style_parameters_for_component(tile, component_index);
auto N_L = coding_parameters.number_of_decomposition_levels;
// Table F.1 – Decomposition level nb for sub-band b
// Note: The spec suggests that this ends with n_b = 1, but if N_L is 0, we have 0LL and nothing else.
auto n_b = [N_L](int r) { return r == 0 ? N_L : (N_L + 1 - r); };
sub_band.rect = context.siz.reference_grid_coordinates_for_sub_band(tile.rect, component_index, n_b(r), sub_band_type);
// Compute tile size at resolution level r.
auto ll_rect = context.siz.reference_grid_coordinates_for_ll_band(tile.rect, component_index, r, N_L);
dbgln_if(JPEG2000_DEBUG, "Sub-band rect: {}, ll rect {}", sub_band.rect, ll_rect);
// B.6
// (B-16)
int num_precincts_wide = 0;
int num_precincts_high = 0;
int PPx = coding_parameters.precinct_sizes[r].PPx;
int PPy = coding_parameters.precinct_sizes[r].PPy;
if (ll_rect.width() != 0)
num_precincts_wide = ceil_div(ll_rect.right(), 1 << PPx) - (ll_rect.left() / (1 << PPx));
if (ll_rect.height() != 0)
num_precincts_high = ceil_div(ll_rect.bottom(), 1 << PPy) - (ll_rect.top() / (1 << PPy));
sub_band.num_precincts_wide = num_precincts_wide;
sub_band.num_precincts_high = num_precincts_high;
auto precinct_origin = IntPoint { ll_rect.x() & ~((1 << PPx) - 1), ll_rect.y() & ~((1 << PPy) - 1) };
if (r > 0) {
PPx--;
PPy--;
precinct_origin /= 2;
}
// B.7
// (B-17)
// (The r > 0 check was done right above already.)
int xcb_prime = min(coding_parameters.code_block_width_exponent, PPx);
// (B-18)
// (The r > 0 check was done right above already.)
int ycb_prime = min(coding_parameters.code_block_height_exponent, PPy);
for (int precinct_y_index = 0; precinct_y_index < num_precincts_high; ++precinct_y_index) {
for (int precinct_x_index = 0; precinct_x_index < num_precincts_wide; ++precinct_x_index) {
auto precinct_rect = IntRect({ precinct_x_index * (1 << PPx), precinct_y_index * (1 << PPy), 1 << PPx, 1 << PPy });
precinct_rect.set_location(precinct_rect.location() + precinct_origin);
sub_band.precincts.append(TRY(make_precinct(sub_band, precinct_rect, xcb_prime, ycb_prime)));
}
}
return {};
};
auto make_component = [&](TileData const& tile, int component_index) -> ErrorOr<DecodedTileComponent> {
DecodedTileComponent component;
component.rect = context.siz.reference_grid_coordinates_for_tile_component(tile.rect, component_index);
dbgln_if(JPEG2000_DEBUG, "making nLL for component {}", component_index);
TRY(make_sub_band(tile, component_index, component.nLL, JPEG2000::SubBand::HorizontalLowpassVerticalLowpass, 0));
auto N_L = context.coding_style_parameters_for_component(tile, component_index).number_of_decomposition_levels;
for (int resolution_level = 1; resolution_level <= N_L; ++resolution_level) {
DecodedTileComponent::DecodedSubBands sub_bands;
for (auto [sub_band_index, sub_band] : enumerate(DecodedTileComponent::SubBandOrder)) {
dbgln_if(JPEG2000_DEBUG, "r {} making sub-band {} for component {}", resolution_level, (int)sub_band, component_index);
TRY(make_sub_band(tile, component_index, sub_bands[sub_band_index], sub_band, resolution_level));
}
component.decompositions.append(move(sub_bands));
}
return component;
};
auto make_tile = [&](size_t tile_index, TileData& tile) -> ErrorOr<void> {
auto const& cod = tile.cod.value_or(context.cod);
auto pq = context.siz.tile_2d_index_from_1d_index(tile_index);
tile.rect = context.siz.reference_grid_coordinates_for_tile(pq);
dbgln_if(JPEG2000_DEBUG, "tile {} rect {}", tile_index, tile.rect);
for (auto [component_index, component] : enumerate(context.siz.components)) {
VERIFY(component.bit_depth() >= 1);
VERIFY(component.bit_depth() <= 38);
if (component.horizontal_separation != 1)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Horizontal separation not yet implemented");
if (component.vertical_separation != 1)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Vertical separation not yet implemented");
tile.components.append(TRY(make_component(tile, component_index)));
}
return {};
};
auto make_progression_iterator = [&](JPEG2000LoadingContext const& context, TileData const& tile) -> ErrorOr<OwnPtr<JPEG2000::ProgressionIterator>> {
if (tile.poc.has_value() || context.poc.has_value())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: POC markers not yet supported");
auto number_of_layers = tile.cod.value_or(context.cod).number_of_layers;
int max_number_of_decomposition_levels = 0;
for (size_t component_index = 0; component_index < context.siz.components.size(); ++component_index)
max_number_of_decomposition_levels = max(max_number_of_decomposition_levels, context.coding_style_parameters_for_component(tile, component_index).number_of_decomposition_levels);
auto number_of_precincts_from_resolution_level_and_component = [&](int r, int component_index) {
auto const& sub_band = r == 0 ? tile.components[component_index].nLL : tile.components[component_index].decompositions[r - 1][0];
return sub_band.num_precincts_wide * sub_band.num_precincts_high;
};
switch (tile.cod.value_or(context.cod).progression_order) {
case CodingStyleDefault::ProgressionOrder::LayerResolutionComponentPosition:
return make<JPEG2000::LayerResolutionLevelComponentPositionProgressionIterator>(number_of_layers, max_number_of_decomposition_levels, context.siz.components.size(), move(number_of_precincts_from_resolution_level_and_component));
case CodingStyleDefault::ResolutionLayerComponentPosition:
return make<JPEG2000::ResolutionLevelLayerComponentPositionProgressionIterator>(number_of_layers, max_number_of_decomposition_levels, context.siz.components.size(), move(number_of_precincts_from_resolution_level_and_component));
case CodingStyleDefault::ResolutionPositionComponentLayer:
case CodingStyleDefault::PositionComponentResolutionLayer:
case CodingStyleDefault::ComponentPositionResolutionLayer: {
auto XRsiz = [&](size_t i) { return context.siz.components[i].horizontal_separation; };
auto YRsiz = [&](size_t i) { return context.siz.components[i].vertical_separation; };
// "To use this progression, XRsiz and YRsiz values must be powers of two for each component."
for (size_t component_index = 0; component_index < context.siz.components.size(); ++component_index) {
if (!is_power_of_two(XRsiz(component_index)) || !is_power_of_two(YRsiz(component_index)))
return Error::from_string_literal("JPEG2000Loader: ResolutionPositionComponentLayer progression order requires XRsiz and YRsiz to be powers of two");
}
auto PPx = [&](int resolution_level, int component) {
return context.coding_style_parameters_for_component(tile, component).precinct_sizes[resolution_level].PPx;
};
auto PPy = [&](int resolution_level, int component) {
return context.coding_style_parameters_for_component(tile, component).precinct_sizes[resolution_level].PPy;
};
auto N_L = [&](int component) {
return context.coding_style_parameters_for_component(tile, component).number_of_decomposition_levels;
};
auto num_precincts_wide = [&](int resolution_level, int component) {
auto const& sub_band = resolution_level == 0 ? tile.components[component].nLL : tile.components[component].decompositions[resolution_level - 1][0];
return sub_band.num_precincts_wide;
};
auto ll_rect = [&](int resolution_level, int component) {
// The outer N_L lambda has been move()d away by the time this runs and can't be called here.
auto N_L = context.coding_style_parameters_for_component(tile, component).number_of_decomposition_levels;
return context.siz.reference_grid_coordinates_for_ll_band(tile.rect, component, resolution_level, N_L);
};
if (tile.cod.value_or(context.cod).progression_order == CodingStyleDefault::ResolutionPositionComponentLayer)
return make<JPEG2000::ResolutionLevelPositionComponentLayerProgressionIterator>(
number_of_layers, max_number_of_decomposition_levels, context.siz.components.size(), move(number_of_precincts_from_resolution_level_and_component),
move(XRsiz), move(YRsiz), move(PPx), move(PPy), move(N_L), move(num_precincts_wide), tile.rect, move(ll_rect));
if (tile.cod.value_or(context.cod).progression_order == CodingStyleDefault::PositionComponentResolutionLayer)
return make<JPEG2000::PositionComponentResolutionLevelLayerProgressionIterator>(
number_of_layers, context.siz.components.size(), move(number_of_precincts_from_resolution_level_and_component),
move(XRsiz), move(YRsiz), move(PPx), move(PPy), move(N_L), move(num_precincts_wide), tile.rect, move(ll_rect));
return make<JPEG2000::ComponentPositionResolutionLevelLayerProgressionIterator>(
number_of_layers, context.siz.components.size(), move(number_of_precincts_from_resolution_level_and_component),
move(XRsiz), move(YRsiz), move(PPx), move(PPy), move(N_L), move(num_precincts_wide), tile.rect, move(ll_rect));
}
}
VERIFY_NOT_REACHED();
};
for (auto const& [tile_index, tile] : enumerate(context.tiles)) {
TRY(make_tile(tile_index, tile));
tile.progression_iterator = TRY(make_progression_iterator(context, tile));
}
return {};
}
static ErrorOr<u32> read_one_packet_header(JPEG2000LoadingContext& context, TileData& tile, ReadonlyBytes data)
{
auto progression_data = TRY(context.next_progression_data(tile));
FixedMemoryStream stream { data };
if (tile.cod.value_or(context.cod).may_use_SOP_marker && data.size() >= 2 && TRY(peek_marker(data)) == J2K_SOP) {
// A.8.1 Start of packet (SOP)
// "It may be used in the bit stream in front of every packet. It shall not be used unless indicated that it is
// allowed in the proper COD marker segment (see A.6.1). If PPM or PPT marker segments are used, then the SOP marker
// segment may appear immediately before the packet data in the bit stream.
// If SOP marker segments are allowed (by signalling in the COD marker segment, see A.6.1), each packet in any given tile-
// part may or may not be appended with an SOP marker segment."
// Just skip this data if it's there.
// FIMXE: Tweak once we add support for PPM and PPT.
u16 marker = TRY(stream.read_value<BigEndian<u16>>());
u16 marker_length = TRY(stream.read_value<BigEndian<u16>>());
u16 packet_sequence_number = TRY(stream.read_value<BigEndian<u16>>());
VERIFY(marker == J2K_SOP); // Due to the peek_marker check above.
if (marker_length != 4)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid SOP marker length");
(void)packet_sequence_number; // FIXME: Do something with this?
}
BigEndianInputBitStream bitstream { MaybeOwned { stream } };
// B.9 Packets
// "All compressed image data representing a specific tile, layer, component, resolution level and precinct appears in the
// codestream in a contiguous segment called a packet. Packet data is aligned at 8-bit (one byte) boundaries."
auto const& coding_parameters = context.coding_style_parameters_for_component(tile, progression_data.component);
auto const r = progression_data.resolution_level;
u32 const current_layer_index = progression_data.layer;
// B.10 Packet header information coding
// "The packets have headers with the following information:
// - zero length packet;
// - code-block inclusion;
// - zero bit-plane information;
// - number of coding passes;
// - length of the code-block compressed image data from a given code-block."
// B.10.1 Bit-stuffing routine
// "If the value of the byte is 0xFF, the next byte includes an extra zero bit stuffed into the MSB. Once all bits of the
// packet header have been assembled, the last byte is packed to the byte boundary and emitted."
u8 last_full_byte { 0 };
Function<ErrorOr<bool>()> read_bit = [&bitstream, &last_full_byte]() -> ErrorOr<bool> {
if (bitstream.is_aligned_to_byte_boundary()) {
if (last_full_byte == 0xFF) {
bool stuff_bit = TRY(bitstream.read_bit());
if (stuff_bit)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid bit-stuffing");
}
last_full_byte = 0;
}
bool bit = TRY(bitstream.read_bit());
last_full_byte = (last_full_byte << 1) | bit;
return bit;
};
// The most useful section to understand the overall flow is B.10.8 Order of information within packet header,
// which has an example packet header bitstream, and the data layout:
// "bit for zero or non-zero length packet
// for each sub-band (LL or HL, LH and HH)
// for all code-blocks in this sub-band confined to the relevant precinct, in raster order
// code-block inclusion bits (if not previously included then tag tree, else one bit)
// if code-block included
// if first instance of code-block
// zero bit-planes information
// number of coding passes included
// increase of code-block length indicator (Lblock)
// for each codeword segment
// length of codeword segment"
// The below implements these steps.
// "bit for zero or non-zero length packet"
// B.10.3 Zero length packet
// "The first bit in the packet header denotes whether the packet has a length of zero (empty packet). The value 0 indicates a
// zero length; no code-blocks are included in this case. The value 1 indicates a non-zero length; this case is considered
// exclusively hereinafter."
bool is_non_zero = TRY(read_bit());
bool is_empty = !is_non_zero;
// " for each sub-band (LL or HL, LH and HH)"
struct TemporaryCodeBlockData {
struct Segment {
u32 length { 0 };
u32 index { 0 };
int number_of_passes { 0 };
};
Vector<Segment, 1> codeword_segments;
};
struct TemporarySubBandData {
DecodedPrecinct* precinct { nullptr };
Vector<TemporaryCodeBlockData> temporary_code_block_data;
};
Array<TemporarySubBandData, 3> temporary_sub_band_data {};
static constexpr Array level_0_sub_bands { JPEG2000::SubBand::HorizontalLowpassVerticalLowpass };
auto sub_bands = r == 0 ? level_0_sub_bands.span() : DecodedTileComponent::SubBandOrder.span();
for (auto [sub_band_index, sub_band] : enumerate(sub_bands)) {
auto& component = tile.components[progression_data.component];
auto& sub_band_data = r == 0 ? component.nLL : component.decompositions[r - 1][sub_band_index];
auto& precinct = sub_band_data.precincts[progression_data.precinct];
// B.9: "Only those code-blocks that contain samples from the relevant sub-band, confined to the precinct, have any representation in the packet."
if (is_empty || precinct.num_code_blocks_wide == 0 || precinct.num_code_blocks_high == 0)
continue;
temporary_sub_band_data[sub_band_index].precinct = &precinct;
TRY(temporary_sub_band_data[sub_band_index].temporary_code_block_data.try_resize(precinct.code_blocks.size()));
for (auto const& [code_block_index, current_block] : enumerate(precinct.code_blocks)) {
size_t code_block_x = code_block_index % precinct.num_code_blocks_wide;
size_t code_block_y = code_block_index / precinct.num_code_blocks_wide;
// B.10.4 Code-block inclusion
bool is_included;
if (current_block.has_been_included_in_previous_packet) {
// "For code-blocks that have been included in a previous packet, a single bit is used to represent the information, where
// a 1 means that the code-block is included in this layer and a 0 means that it is not."
is_included = TRY(read_bit());
} else {
// "For code-blocks that have not been previously included in any packet, this information is signalled with a separate tag
// tree code for each precinct as confined to a sub-band. The values in this tag tree are the number of the layer in which the
// current code-block is first included."
is_included = TRY(precinct.code_block_inclusion_tree->read_value(code_block_x, code_block_y, read_bit, current_layer_index + 1)) <= current_layer_index;
}
dbgln_if(JPEG2000_DEBUG, "code-block inclusion: {}", is_included);
current_block.is_included = is_included;
if (!is_included)
continue;
// B.10.5 Zero bit-plane information
// "If a code-block is included for the first time,
// [...] the number of actual bit-planes for which coding passes are generated is Mb – P
// [...] these missing bit-planes are all taken to be zero
// [...] The value of P is coded in the packet header with a separate tag tree for every precinct"
// And Annex E, E.1 Inverse quantization procedure:
// "Mb = G + exp_b - 1 (E-2)
// where the number of guard bits G and the exponent exp_b are specified in the QCD or QCC marker segments (see A.6.4 and A.6.5)."
bool is_included_for_the_first_time = is_included && !current_block.has_been_included_in_previous_packet;
if (is_included_for_the_first_time) {
u32 p = TRY(precinct.p_tree->read_value(code_block_x, code_block_y, read_bit));
dbgln_if(JPEG2000_DEBUG, "zero bit-plane information: {}", p);
current_block.p = p;
current_block.has_been_included_in_previous_packet = true;
}
// B.10.6 Number of coding passes
// Table B.4 – Codewords for the number of coding passes for each code-block
u8 number_of_coding_passes = TRY([&]() -> ErrorOr<u8> {
if (!TRY(read_bit()))
return 1;
if (!TRY(read_bit()))
return 2;
u8 bits = TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
if (bits != 3)
return 3 + bits;
bits = TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
if (bits != 31)
return 6 + bits;
bits = TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
bits = (bits << 1) | TRY(read_bit());
return 37 + bits;
}());
dbgln_if(JPEG2000_DEBUG, "number of coding passes: {}", number_of_coding_passes);
// B.10.7 Length of the compressed image data from a given code-block
// "Multiple codeword segments arise when a termination occurs between coding passes which are included in the packet"
u32 passes_from_previous_layers = precinct.code_blocks[code_block_index].number_of_coding_passes();
JPEG2000::BitplaneDecodingOptions options;
options.uses_termination_on_each_coding_pass = coding_parameters.uses_termination_on_each_coding_pass();
options.uses_selective_arithmetic_coding_bypass = coding_parameters.uses_selective_arithmetic_coding_bypass();
int number_of_segments = [&]() {
auto old_segment_index = passes_from_previous_layers == 0 ? 0 : JPEG2000::segment_index_from_pass_index(options, passes_from_previous_layers - 1);
auto new_segment_index = JPEG2000::segment_index_from_pass_index(options, passes_from_previous_layers + number_of_coding_passes - 1);
auto number_of_segments = new_segment_index - old_segment_index;
// If the old layer does not end on a segment boundary, the new layer has to add one segment for continuing the previous segment
// in addition to counting the segments it contains and starts.
if (old_segment_index == JPEG2000::segment_index_from_pass_index(options, passes_from_previous_layers))
number_of_segments++;
return number_of_segments;
}();
// B.10.7.1 Single codeword segment
// "A codeword segment is the number of bytes contributed to a packet by a code-block.
// The length of a codeword segment is represented by a binary number of length:
// bits = Lblock + ⌊log2(number_of_coding_passes)⌋
// where Lblock is a code-block state variable. A separate Lblock is used for each code-block in the precinct.
// The value of Lblock is initially set to three. The number of bytes contributed by each code-block is preceded by signalling
// bits that increase the value of Lblock, as needed. A signalling bit of zero indicates the current value of Lblock is sufficient.
// If there are k ones followed by a zero, the value of Lblock is incremented by k."
// B.10.7.2 Multiple codeword segments
// "Let T be the set of indices of terminated coding passes included for the code-block in the packet as indicated in Tables D.8
// and D.9. If the index final coding pass included in the packet is not a member of T, then it is added to T. Let n_1 < ... < n_K
// be the indices in T. K lengths are signalled consecutively with each length using the mechanism described in B.10.7.1."
// "using the mechanism" means adjusting Lblock just once, and then reading one code word segment length with the
// number of passes per segment, apparently.
// We combine both cases: the single segment case is a special case of the multiple segment case.
// For the B.10.7.1 case, we'll have number_of_segments = 1 and number_of_passes_in_segment = number_of_coding_passes.
u32 k = 0;
while (TRY(read_bit()))
k++;
current_block.Lblock += k;
auto read_one_codeword_segment_length = [&](int number_of_passes) -> ErrorOr<u32> {
u32 bits = current_block.Lblock + (u32)floor(log2(number_of_passes));
if (bits > 32)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Too many bits for length of codeword segment");
u32 length = 0;
for (u32 i = 0; i < bits; ++i) {
bool bit = TRY(read_bit());
length = (length << 1) | bit;
}
return length;
};
VERIFY(temporary_sub_band_data[sub_band_index].temporary_code_block_data[code_block_index].codeword_segments.is_empty());
int number_of_passes_used = 0;
for (int i = 0; i < number_of_segments; ++i) {
int number_of_passes_in_segment = number_of_coding_passes;
u32 segment_index = JPEG2000::segment_index_from_pass_index(options, passes_from_previous_layers) + i;
if (coding_parameters.uses_termination_on_each_coding_pass()) {
number_of_passes_in_segment = 1;
} else if (coding_parameters.uses_selective_arithmetic_coding_bypass()) {
number_of_passes_in_segment = JPEG2000::number_of_passes_from_segment_index_in_bypass_mode(segment_index);
// Correction at start: Did the previous layer end in an incomplete segment that's continued in this layer?
Optional<u32> previous_segment_id = precinct.code_blocks[code_block_index].highest_segment_index();
if (previous_segment_id.has_value() && segment_index == previous_segment_id.value())
number_of_passes_in_segment -= precinct.code_blocks[code_block_index].number_of_coding_passes_in_segment(segment_index);
// Correction at end: Does this layer end in an incomplete segment that's continued in the next layer?
if (i == number_of_segments - 1)
number_of_passes_in_segment = min(number_of_coding_passes - number_of_passes_used, number_of_passes_in_segment);
}
u32 length = TRY(read_one_codeword_segment_length(number_of_passes_in_segment));
dbgln_if(JPEG2000_DEBUG, "length({}) {}", i, length);
temporary_sub_band_data[sub_band_index].temporary_code_block_data[code_block_index].codeword_segments.append({ length, segment_index, number_of_passes_in_segment });
number_of_passes_used += number_of_passes_in_segment;
VERIFY(number_of_passes_used <= number_of_coding_passes);
}
VERIFY(number_of_passes_used == number_of_coding_passes);
}
}
if (last_full_byte == 0xFF) {
bool final_stuff_bit = TRY(read_bit());
if (final_stuff_bit)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid bit-stuffing");
}
if (tile.cod.value_or(context.cod).shall_use_EPH_marker) {
// A.8.2 End of packet header (EPH)
// "If EPH markers are required (by signalling in the COD marker segment, see A.6.1), each packet header in any given tile-
// part shall be postpended with an EPH marker segment. If the packet headers are moved to a PPM or PPT marker segments
// (see A.7.4 and A.7.5), then the EPH markers shall appear after the packet headers in the PPM or PPT marker segments."
// Just skip this data if it's there.
// FIMXE: Tweak once we add support for PPM and PPT.
u16 marker = TRY(stream.read_value<BigEndian<u16>>());
if (marker != J2K_EPH)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Expected EPH marker");
}
// Done reading packet header. Set `data` on each codeblock on the packet.
u32 offset = stream.offset();
for (auto const& temporary_sub_band : temporary_sub_band_data) {
for (auto const& [code_block_index, temporary_code_block] : enumerate(temporary_sub_band.temporary_code_block_data)) {
DecodedCodeBlock::Layer layer;
for (auto [length, index, number_of_passes] : temporary_code_block.codeword_segments) {
auto segment_data = data.slice(offset, length);
offset += length;
layer.segments.append({ segment_data, index, number_of_passes });
}
TRY(temporary_sub_band.precinct->code_blocks[code_block_index].layers.try_append(layer));
}
}
return offset;
}
static ErrorOr<void> read_tile_part_packet_headers(JPEG2000LoadingContext& context, TileData& tile, TilePartData& tile_part)
{
if (!context.rgns.is_empty() || !tile.rgns.is_empty())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: RGN markers not yet supported");
auto data = tile_part.data;
while (!data.is_empty()) {
auto length = TRY(read_one_packet_header(context, tile, data));
data = data.slice(length);
}
return {};
}
static ErrorOr<void> read_tile_packet_headers(JPEG2000LoadingContext& context, TileData& tile)
{
for (auto& tile_part : tile.tile_parts)
TRY(read_tile_part_packet_headers(context, tile, tile_part));
return {};
}
static ErrorOr<void> read_packet_headers(JPEG2000LoadingContext& context)
{
for (auto& tile : context.tiles)
TRY(read_tile_packet_headers(context, tile));
return {};
}
static u8 get_exponent(QuantizationDefault const& quantization_parameters, JPEG2000::SubBand sub_band, int resolution_level, int N_L)
{
switch (quantization_parameters.quantization_style) {
case QuantizationDefault::QuantizationStyle::NoQuantization: {
auto const& steps = quantization_parameters.step_sizes.get<Vector<QuantizationDefault::ReversibleStepSize>>();
if (sub_band == JPEG2000::SubBand::HorizontalLowpassVerticalLowpass) {
VERIFY(resolution_level == 0);
return steps[0].exponent;
}
VERIFY(resolution_level > 0);
return steps[1 + (resolution_level - 1) * 3 + (int)sub_band - 1].exponent;
}
case QuantizationDefault::QuantizationStyle::ScalarDerived:
case QuantizationDefault::QuantizationStyle::ScalarExpounded: {
auto const& steps = quantization_parameters.step_sizes.get<Vector<QuantizationDefault::IrreversibleStepSize>>();
if (quantization_parameters.quantization_style == QuantizationDefault::QuantizationStyle::ScalarDerived) {
// Table F.1 – Decomposition level nb for sub-band b
// Note: The spec suggests that this ends with n_b = 1, but if N_L is 0, we have 0LL and nothing else.
int n_b = resolution_level == 0 ? N_L : (N_L + 1 - resolution_level);
// (E-5)
return steps[0].exponent - N_L + n_b;
// This is the same as `return resolution_level == 0 ? steps[0].exponent : steps[0].exponent - (resolution_level - 1);`
}
if (sub_band == JPEG2000::SubBand::HorizontalLowpassVerticalLowpass) {
VERIFY(resolution_level == 0);
return steps[0].exponent;
}
VERIFY(resolution_level > 0);
return steps[1 + (resolution_level - 1) * 3 + (int)sub_band - 1].exponent;
}
}
VERIFY_NOT_REACHED();
}
static int compute_M_b(JPEG2000LoadingContext& context, TileData& tile, int component_index, JPEG2000::SubBand sub_band_type, int r, int N_L)
{
// Annex E, E.1 Inverse quantization procedure:
// "Mb = G + exp_b - 1 (E-2)
// where the number of guard bits G and the exponent exp_b are specified in the QCD or QCC marker segments (see A.6.4 and A.6.5)."
auto quantization_parameters = context.quantization_parameters_for_component(tile, component_index);
auto exponent = get_exponent(quantization_parameters, sub_band_type, r, N_L);
return quantization_parameters.number_of_guard_bits + exponent - 1;
}
static ErrorOr<void> decode_bitplanes_to_coefficients(JPEG2000LoadingContext& context)
{
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 N_L) {
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 = input[y * w + x];
// E.1 Inverse quantization procedure
// The coefficients store qbar_b.
if (quantization_parameters.quantization_style != QuantizationDefault::QuantizationStyle::NoQuantization) {
// E.1.1 Irreversible transformation
auto R_I = context.siz.components[component_index].bit_depth();
// Table E.1 – Sub-band gains
auto log_2_gain_b = sub_band_type == JPEG2000::SubBand::HorizontalLowpassVerticalLowpass ? 0 : (sub_band_type == JPEG2000::SubBand::HorizontalHighpassVerticalLowpass || sub_band_type == JPEG2000::SubBand::HorizontalLowpassVerticalHighpass ? 1 : 2);
auto R_b = R_I + log_2_gain_b; // (E-4)
u16 mantissa;
if (quantization_parameters.quantization_style == QuantizationDefault::QuantizationStyle::ScalarDerived) {
// (E-5)
mantissa = quantization_parameters.step_sizes.get<Vector<QuantizationDefault::IrreversibleStepSize>>()[0].mantissa;
} else {
if (r == 0)
mantissa = quantization_parameters.step_sizes.get<Vector<QuantizationDefault::IrreversibleStepSize>>()[0].mantissa;
else
mantissa = quantization_parameters.step_sizes.get<Vector<QuantizationDefault::IrreversibleStepSize>>()[3 * (r - 1) + (int)sub_band_type].mantissa;
}
// (E-3)
auto exponent = get_exponent(quantization_parameters, sub_band_type, r, N_L);
float step_size = powf(2.0f, R_b - exponent) * (1.0f + mantissa / powf(2.0f, 11.0f));
// (E-6), with r chosen as 0 (see NOTE below (E-6)).
value *= step_size;
}
output.data[y * output.pitch + x] = value;
}
}
};
auto decode_bitplanes = [&](TileData& tile, JPEG2000::SubBand sub_band_type, DecodedSubBand& sub_band, int component_index, int r, int N_L) -> ErrorOr<void> {
TRY(sub_band.coefficients.try_resize(sub_band.rect.width() * sub_band.rect.height()));
auto const& coding_style = context.coding_style_parameters_for_component(tile, component_index);
JPEG2000::BitplaneDecodingOptions bitplane_decoding_options;
bitplane_decoding_options.uses_selective_arithmetic_coding_bypass = coding_style.uses_selective_arithmetic_coding_bypass();
bitplane_decoding_options.reset_context_probabilities_each_pass = coding_style.reset_context_probabilities();
bitplane_decoding_options.uses_termination_on_each_coding_pass = coding_style.uses_termination_on_each_coding_pass();
bitplane_decoding_options.uses_vertically_causal_context = coding_style.uses_vertically_causal_context();
bitplane_decoding_options.uses_segmentation_symbols = coding_style.uses_segmentation_symbols();
int M_b = compute_M_b(context, tile, component_index, sub_band_type, r, N_L);
// FIXME: Codeblocks all use independent arithmetic coders, so this could run in parallel.
for (auto& precinct : sub_band.precincts) {
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());
for (auto& code_block : precinct.code_blocks) {
int total_number_of_coding_passes = code_block.number_of_coding_passes();
ByteBuffer storage;
Vector<ReadonlyBytes, 1> combined_segments = TRY(code_block.segments_for_all_layers(storage));
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()));
TRY(JPEG2000::decode_code_block(output, sub_band_type, total_number_of_coding_passes, combined_segments, M_b, code_block.p, bitplane_decoding_options));
}
JPEG2000::Span2D<float> output;
output.size = clipped_precinct_rect.size();
output.pitch = sub_band.rect.width();
output.data = sub_band.coefficients.span().slice((clipped_precinct_rect.y() - sub_band.rect.y()) * output.pitch + (clipped_precinct_rect.x() - sub_band.rect.x()));
copy_and_dequantize_if_needed(output, precinct_coefficients, context.quantization_parameters_for_component(tile, component_index), sub_band_type, component_index, r, N_L);
}
return {};
};
for (auto& tile : context.tiles) {
for (auto [component_index, component] : enumerate(tile.components)) {
int N_L = component.decompositions.size();
TRY(decode_bitplanes(tile, JPEG2000::SubBand::HorizontalLowpassVerticalLowpass, component.nLL, component_index, 0, N_L));
for (auto const& [decomposition_index, decomposition] : enumerate(component.decompositions)) {
int r = decomposition_index + 1;
for (auto [sub_band_index, sub_band] : enumerate(DecodedTileComponent::SubBandOrder)) {
TRY(decode_bitplanes(tile, sub_band, decomposition[sub_band_index], component_index, r, N_L));
}
}
}
}
return {};
}
static ErrorOr<void> run_inverse_discrete_wavelet_transform(JPEG2000LoadingContext& context)
{
// FIXME: Could run these in parallel.
for (auto& tile : context.tiles) {
for (auto [component_index, component] : enumerate(tile.components)) {
int N_L = component.decompositions.size();
Gfx::JPEG2000::IDWTInput input;
input.transformation = context.coding_style_parameters_for_component(tile, component_index).transformation;
input.LL.rect = component.nLL.rect;
input.LL.data = { component.nLL.coefficients, component.nLL.rect.size(), component.nLL.rect.width() };
for (auto const& [decomposition_index, decomposition] : enumerate(component.decompositions)) {
int r = decomposition_index + 1;
JPEG2000::IDWTDecomposition idwt_decomposition;
idwt_decomposition.ll_rect = context.siz.reference_grid_coordinates_for_ll_band(tile.rect, component_index, r, N_L);
VERIFY(DecodedTileComponent::SubBandOrder[0] == JPEG2000::SubBand::HorizontalHighpassVerticalLowpass);
auto hl_rect = decomposition[0].rect;
idwt_decomposition.hl = { hl_rect, { decomposition[0].coefficients, hl_rect.size(), hl_rect.width() } };
VERIFY(DecodedTileComponent::SubBandOrder[1] == JPEG2000::SubBand::HorizontalLowpassVerticalHighpass);
auto lh_rect = decomposition[1].rect;
idwt_decomposition.lh = { lh_rect, { decomposition[1].coefficients, lh_rect.size(), lh_rect.width() } };
VERIFY(DecodedTileComponent::SubBandOrder[2] == JPEG2000::SubBand::HorizontalHighpassVerticalHighpass);
auto hh_rect = decomposition[2].rect;
idwt_decomposition.hh = { hh_rect, { decomposition[2].coefficients, hh_rect.size(), hh_rect.width() } };
input.decompositions.append(idwt_decomposition);
}
auto output = TRY(JPEG2000::IDWT(input));
VERIFY(component.rect == output.rect);
component.samples = move(output.data);
// FIXME: Could release coefficient data here, to reduce peak memory use.
}
}
return {};
}
static ErrorOr<void> postprocess_samples(JPEG2000LoadingContext& context)
{
auto undo_multiple_component_transformation = [&](TileData& tile) -> ErrorOr<void> {
VERIFY(context.siz.components.size() == tile.components.size());
if (tile.components.size() < 3)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple component transformation type but fewer than 3 components");
// T.800, I.5.3.6 Channel Definition box
// "If a multiple component transform is specified within the codestream, the image must be in an RGB colourspace and the
// red, green and blue colours as channels 0, 1 and 2 in the codestream, respectively."
// FIXME: Check this.
auto transformation0 = context.coding_style_parameters_for_component(tile, 0).transformation;
auto transformation1 = context.coding_style_parameters_for_component(tile, 1).transformation;
auto transformation2 = context.coding_style_parameters_for_component(tile, 2).transformation;
if (transformation0 != transformation1 || transformation1 != transformation2)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple component transformation type but components disagree on lossiness");
// "The three components [...] shall have the same separation on the reference grid and the same bit-depth."
if (context.siz.components[0].horizontal_separation != context.siz.components[1].horizontal_separation
|| context.siz.components[1].horizontal_separation != context.siz.components[2].horizontal_separation) {
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple component transformation type but components disagree on horizontal separation");
}
if (context.siz.components[0].vertical_separation != context.siz.components[1].vertical_separation
|| context.siz.components[1].vertical_separation != context.siz.components[2].vertical_separation) {
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple component transformation type but components disagree on vertical separation");
}
// Note: Spec says "bit-depth" but we check bit depth and sign. That must be what the spec means?
if (context.siz.components[0].depth_and_sign != context.siz.components[1].depth_and_sign
|| context.siz.components[1].depth_and_sign != context.siz.components[2].depth_and_sign) {
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple component transformation type but components disagree on bit depth");
}
if (tile.components[0].rect.size() != tile.components[1].rect.size()
|| tile.components[0].rect.size() != tile.components[1].rect.size()) {
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple component transformation type but components disagree on dimensions");
}
auto& c0 = tile.components[0].samples;
auto& c1 = tile.components[1].samples;
auto& c2 = tile.components[2].samples;
int w = tile.components[0].rect.width();
if (transformation0 == JPEG2000::Transformation::Reversible_5_3_Filter) {
// G.2 Reversible multiple component transformation (RCT)
// "The three components input into the RCT shall have the same separation on the reference grid and the same bit-depth."
// Same for RCT and ICT; checked above this branch.
for (int y = 0; y < tile.components[0].rect.height(); ++y) {
for (int x = 0; x < w; ++x) {
float Y = c0[y * w + x];
float Cb = c1[y * w + x];
float Cr = c2[y * w + x];
float G = Y - floorf((Cb + Cr) / 4); // (G-6)
float R = Cr + G; // (G-7)
float B = Cb + G; // (G-8)
c0[y * w + x] = R;
c1[y * w + x] = G;
c2[y * w + x] = B;
}
}
} else {
VERIFY(transformation0 == JPEG2000::Transformation::Irreversible_9_7_Filter);
// G.3 Irreversible multiple component transformation (ICT)
// "The three components input into the ICT shall have the same separation on the reference grid and the same bit-depth."
// Same for RCT and ICT; checked above this branch.
for (int y = 0; y < tile.components[0].rect.height(); ++y) {
for (int x = 0; x < w; ++x) {
float Y = c0[y * w + x];
float Cb = c1[y * w + x];
float Cr = c2[y * w + x];
float R = Y + 1.402f * Cr; // (G-12)
float G = Y - 0.34413f * Cb - 0.7141f * Cr; // (G-13)
float B = Y + 1.772f * Cb; // (G-14)
c0[y * w + x] = R;
c1[y * w + x] = G;
c2[y * w + x] = B;
}
}
}
return {};
};
auto undo_dc_level_shift = [&](TileData& tile) -> ErrorOr<void> {
VERIFY(context.siz.components.size() == tile.components.size());
// DC level shift
// G.1.2 Inverse DC level shifting of tile-components
for (auto [component_index, component] : enumerate(tile.components)) {
if (!context.siz.components[component_index].is_signed()) {
for (auto& coefficient : component.samples)
coefficient += 1u << (context.siz.components[component_index].bit_depth() - 1); // (G-2)
}
}
return {};
};
for (auto& tile : context.tiles) {
// Figure G.1 – Placement of the DC level shifting with component transformation
if (tile.cod.value_or(context.cod).multiple_component_transformation_type == CodingStyleDefault::MultipleComponentTransformationType::MultipleComponentTransformationUsed)
TRY(undo_multiple_component_transformation(tile));
TRY(undo_dc_level_shift(tile));
}
return {};
}
static ErrorOr<void> convert_to_bitmap(JPEG2000LoadingContext& context)
{
// determine_color_space() defers returning an error until here, so that JPEG2000ImageDecoderPlugin::create()
// can succeed even with unsupported color spaces.
if (context.color_space == ColorSpace::Unsupported)
return move(context.color_space_error.value());
// Map components to channels.
if (context.palette_box.has_value() && context.options.palette_handling != JPEG2000DecoderOptions::PaletteHandling::PaletteIndicesAsGrayscale) {
ISOBMFF::JPEG2000ComponentMappingBox cmap;
if (context.component_mapping_box.has_value()) {
cmap = context.component_mapping_box.value();
} else {
// The spec requires that cmap is present if pclr is, but in practice some (very few) files have pclr without cmap.
// Assume that everything maps through directly in this case.
for (size_t i = 0; i < context.palette_box->bit_depths.size(); ++i) {
ISOBMFF::JPEG2000ComponentMappingBox::Mapping mapping;
mapping.component_index = 0;
mapping.palette_component_index = i;
mapping.mapping_type = ISOBMFF::JPEG2000ComponentMappingBox::Mapping::Type::Palette;
cmap.component_mappings.append(mapping);
}
}
// I.5.3.4 Palette box
// "This value shall be in the range 1 to 1024"
if (context.palette_box->palette_entries.size() > 1024)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Only up to 1024 palette entries allowed");
for (auto& palette_entry : context.palette_box->palette_entries)
VERIFY(palette_entry.size() == context.palette_box->bit_depths.size()); // Enforced in JPEG2000PaletteBox::read_from_stream().
auto palette_channel_count = context.palette_box->bit_depths.size();
for (auto& tile : context.tiles) {
TRY(tile.channels.try_resize(cmap.component_mappings.size()));
for (auto const& [i, mapping] : enumerate(cmap.component_mappings)) {
if (mapping.component_index >= tile.components.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Component mapping index out of range");
if (mapping.mapping_type == ISOBMFF::JPEG2000ComponentMappingBox::Mapping::Type::Direct) {
tile.channels[mapping.component_index] = move(tile.components[mapping.component_index].samples);
TRY(tile.channel_information.try_append(context.siz.components[mapping.component_index]));
continue;
}
if (mapping.mapping_type != ISOBMFF::JPEG2000ComponentMappingBox::Mapping::Type::Palette)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Unknown mapping type");
if (context.siz.components[mapping.component_index].is_signed())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Don't know how to handle signed palette components");
if (mapping.palette_component_index >= palette_channel_count)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Palette component index out of range");
ImageAndTileSize::ComponentInformation component_information;
component_information.depth_and_sign = context.palette_box->bit_depths[mapping.palette_component_index].depth - 1;
if (context.palette_box->bit_depths[mapping.palette_component_index].is_signed)
component_information.depth_and_sign |= 0x80;
component_information.horizontal_separation = context.siz.components[mapping.component_index].horizontal_separation;
component_information.vertical_separation = context.siz.components[mapping.component_index].vertical_separation;
TRY(tile.channel_information.try_append(component_information));
auto const& component = tile.components[mapping.component_index];
TRY(tile.channels[i].try_ensure_capacity(component.samples.size()));
for (auto sample : component.samples) {
int index = static_cast<int>(sample);
if (index < 0 || static_cast<size_t>(index) >= context.palette_box->palette_entries.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Palette index out of range");
tile.channels[i].append(context.palette_box->palette_entries[index][mapping.palette_component_index]);
}
}
for (auto& component : tile.components)
component.samples.clear();
}
} else {
for (auto& tile : context.tiles) {
for (size_t i = 0; i < tile.components.size(); ++i) {
TRY(tile.channels.try_append(move(tile.components[i].samples)));
TRY(tile.channel_information.try_append(context.siz.components[i]));
}
}
}
auto const channel_count = context.tiles[0].channels.size();
bool has_alpha = false;
if (context.palette_box.has_value() && context.options.palette_handling == JPEG2000DecoderOptions::PaletteHandling::PaletteIndicesAsGrayscale) {
for (auto& tile : context.tiles) {
if (tile.channels.size() != 1)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Palette indices as grayscale require single component");
for (auto sample : tile.channels[0]) {
// The JPEG2000 spec allows palette indices up to 1023, but the PDF spec says that JPEG2000 images
// embedded in PDFs must have indices that fit in a one byte.
if (sample < 0 || sample >= 256)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Palette indices out of range");
}
}
} else {
if (context.channel_definition_box.has_value()) {
if (context.channel_definition_box->channels.size() != channel_count)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Channel definition box channel count doesn't match channel count");
Vector<bool, 4> channel_used;
channel_used.resize(context.channel_definition_box->channels.size());
// If you make this more flexible in the future and implement channel swapping,
// check if that should happen for JPEG2000 files in PDFs as well.
for (auto channel : context.channel_definition_box->channels) {
if (channel.channel_index >= channel_count)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Channel definition box channel index out of range");
if (channel_used[channel.channel_index])
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Channel definition box channel index used multiple times");
channel_used[channel.channel_index] = true;
if (channel.channel_type != ISOBMFF::JPEG2000ChannelDefinitionBox::Channel::Type::Color
&& channel.channel_type != ISOBMFF::JPEG2000ChannelDefinitionBox::Channel::Type::Opacity) {
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Only color and opacity channels supported yet");
}
if (channel.channel_type == ISOBMFF::JPEG2000ChannelDefinitionBox::Channel::Type::Color) {
if (channel.channel_index + 1 != channel.channel_association)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Only unshuffled color channel indices supported yet");
} else {
VERIFY(channel.channel_type == ISOBMFF::JPEG2000ChannelDefinitionBox::Channel::Type::Opacity);
if (channel.channel_index != channel_count - 1)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Only opacity channel as last channel supported yet");
if (channel.channel_association != 0)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Only full opacity channel supported yet");
has_alpha = true;
}
}
} else if (!context.color_box.has_value()) {
// Raw codestream. Go by number of channels.
has_alpha = channel_count == 2 || channel_count == 4;
}
unsigned expected_channel_count = [](ColorSpace color_space) {
switch (color_space) {
case ColorSpace::Gray:
return 1;
case ColorSpace::sRGB:
return 3;
case ColorSpace::CMYK:
return 4;
case ColorSpace::Unsupported: // Rejected above.
VERIFY_NOT_REACHED();
};
VERIFY_NOT_REACHED();
}(context.color_space);
if (has_alpha)
expected_channel_count++;
if (channel_count < expected_channel_count)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough channels for expected channel count");
if (channel_count > expected_channel_count)
dbgln("JPEG2000ImageDecoderPlugin: More channels ({}) than expected channel count ({}), ignoring superfluous channels", context.siz.components.size(), expected_channel_count);
// Convert to 8bpp.
for (auto& tile : context.tiles) {
for (auto const& [channel_index, channel] : enumerate(tile.channels)) {
if (tile.channel_information[channel_index].is_signed())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Only unsigned components supported yet");
if (tile.channel_information[channel_index].bit_depth() == 8)
continue;
// > 16bpp currently overflow the u16s internal to decode_code_block().
if (tile.channel_information[channel_index].bit_depth() > 16)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: More than 16 bits per component not supported yet");
for (float& sample : channel)
sample = (sample * 255.0f) / ((1 << tile.channel_information[channel_index].bit_depth()) - 1);
}
}
}
if (context.color_space == ColorSpace::CMYK) {
if (has_alpha)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: CMYK with alpha not yet supported");
auto bitmap = TRY(Gfx::CMYKBitmap::create_with_size({ context.siz.width, context.siz.height }));
for (auto& tile : context.tiles) {
// compute_decoding_metadata currently rejects images with horizontal_separation or vertical_separation != 1.
for (auto& component : tile.components) {
if (component.rect.size() != tile.components[0].rect.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Components with differing sizes not yet supported");
}
int w = tile.components[0].rect.width();
int h = tile.components[0].rect.height();
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
float C = round_to<u8>(clamp(tile.channels[0][y * w + x], 0.0f, 255.0f));
float M = round_to<u8>(clamp(tile.channels[1][y * w + x], 0.0f, 255.0f));
float Y = round_to<u8>(clamp(tile.channels[2][y * w + x], 0.0f, 255.0f));
float K = round_to<u8>(clamp(tile.channels[3][y * w + x], 0.0f, 255.0f));
bitmap->scanline(y + tile.components[0].rect.top())[x + tile.components[0].rect.left()] = { (u8)C, (u8)M, (u8)Y, (u8)K };
}
}
}
context.cmyk_bitmap = move(bitmap);
return {};
}
auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { context.siz.width, context.siz.height }));
for (auto& tile : context.tiles) {
// compute_decoding_metadata currently rejects images with horizontal_separation or vertical_separation != 1.
for (auto& component : tile.components) {
if (component.rect.size() != tile.components[0].rect.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Components with differing sizes not yet supported");
}
int w = tile.components[0].rect.width();
int h = tile.components[0].rect.height();
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
float value = tile.channels[0][y * w + x];
u8 byte_value = round_to<u8>(clamp(value, 0.0f, 255.0f));
u8 r = byte_value;
u8 g = byte_value;
u8 b = byte_value;
u8 a = 255;
if (tile.channels.size() == 2) {
a = round_to<u8>(clamp(tile.channels[1][y * w + x], 0.0f, 255.0f));
} else if (tile.channels.size() == 3) {
g = round_to<u8>(clamp(tile.channels[1][y * w + x], 0.0f, 255.0f));
b = round_to<u8>(clamp(tile.channels[2][y * w + x], 0.0f, 255.0f));
} else if (tile.channels.size() == 4) {
g = round_to<u8>(clamp(tile.channels[1][y * w + x], 0.0f, 255.0f));
b = round_to<u8>(clamp(tile.channels[2][y * w + x], 0.0f, 255.0f));
a = round_to<u8>(clamp(tile.channels[3][y * w + x], 0.0f, 255.0f));
}
Color pixel;
pixel.set_red(r);
pixel.set_green(g);
pixel.set_blue(b);
pixel.set_alpha(a);
bitmap->set_pixel(x + tile.components[0].rect.left(), y + tile.components[0].rect.top(), pixel);
}
}
}
// FIXME: Could release sample data here, to reduce peak memory use.
context.bitmap = move(bitmap);
return {};
}
static ErrorOr<void> decode_image(JPEG2000LoadingContext& context)
{
TRY(parse_codestream_tile_headers(context));
TRY(compute_decoding_metadata(context));
TRY(read_packet_headers(context));
TRY(decode_bitplanes_to_coefficients(context));
TRY(run_inverse_discrete_wavelet_transform(context));
TRY(postprocess_samples(context));
TRY(convert_to_bitmap(context));
return {};
}
bool JPEG2000ImageDecoderPlugin::sniff(ReadonlyBytes data)
{
return data.starts_with(jp2_id_string) || data.starts_with(marker_id_string);
}
JPEG2000ImageDecoderPlugin::JPEG2000ImageDecoderPlugin(JPEG2000DecoderOptions options)
{
m_context = make<JPEG2000LoadingContext>();
m_context->options = options;
}
JPEG2000ImageDecoderPlugin::~JPEG2000ImageDecoderPlugin() = default;
IntSize JPEG2000ImageDecoderPlugin::size()
{
return m_context->size;
}
static void determine_color_space(JPEG2000LoadingContext& context)
{
if (context.palette_box.has_value() && context.options.palette_handling == JPEG2000DecoderOptions::PaletteHandling::PaletteIndicesAsGrayscale) {
// context.color_box has the color space after palette expansion. But in this mode, we don't expand the palette.
context.color_space = ColorSpace::Gray;
return;
}
if (context.color_box.has_value()) {
if (context.color_box->method == ISOBMFF::JPEG2000ColorSpecificationBox::Method::Enumerated) {
if (context.color_box->enumerated_color_space == ISOBMFF::JPEG2000ColorSpecificationBox::EnumCS::sRGB) {
context.color_space = ColorSpace::sRGB;
} else if (context.color_box->enumerated_color_space == ISOBMFF::JPEG2000ColorSpecificationBox::EnumCS::Greyscale) {
context.color_space = ColorSpace::Gray;
} else if (context.color_box->enumerated_color_space == ISOBMFF::JPEG2000ColorSpecificationBox::EnumCS::CMYK) {
context.color_space = ColorSpace::CMYK;
} else {
context.color_space = ColorSpace::Unsupported;
context.color_space_error = Error::from_string_literal("JPEG2000ImageDecoderPlugin: Only sRGB, grayscale, and CMYK enumerated color spaces supported yet");
}
} else if (context.color_box->method == ISOBMFF::JPEG2000ColorSpecificationBox::Method::ICC_Restricted
|| context.color_box->method == ISOBMFF::JPEG2000ColorSpecificationBox::Method::ICC_Any) {
auto icc_header_or_error = ICC::Profile::read_header(context.color_box->icc_data.bytes());
if (icc_header_or_error.is_error()) {
context.color_space = ColorSpace::Unsupported;
context.color_space_error = icc_header_or_error.release_error();
return;
}
auto icc_header = icc_header_or_error.release_value();
if (icc_header.data_color_space == ICC::ColorSpace::RGB) {
context.color_space = ColorSpace::sRGB;
} else if (icc_header.data_color_space == ICC::ColorSpace::Gray) {
context.color_space = ColorSpace::Gray;
} else if (icc_header.data_color_space == ICC::ColorSpace::CMYK) {
context.color_space = ColorSpace::CMYK;
} else {
context.color_space = ColorSpace::Unsupported;
context.color_space_error = Error::from_string_literal("JPEG2000ImageDecoderPlugin: Only sRGB, grayscale, and CMYK ICC color spaces supported yet");
}
} else {
context.color_space = ColorSpace::Unsupported;
context.color_space_error = Error::from_string_literal("JPEG2000ImageDecoderPlugin: Can only handle enumerated and ICC color specification methods yet");
}
} else {
// Raw codestream. Go by number of components.
context.color_space = context.siz.components.size() < 3 ? ColorSpace::Gray : ColorSpace::sRGB;
}
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPEG2000ImageDecoderPlugin::create(ReadonlyBytes data)
{
return create_with_options(data, {});
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPEG2000ImageDecoderPlugin::create_with_options(ReadonlyBytes data, JPEG2000DecoderOptions options)
{
auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) JPEG2000ImageDecoderPlugin(options)));
TRY(decode_jpeg2000_header(*plugin->m_context, data));
determine_color_space(*plugin->m_context);
return plugin;
}
ErrorOr<ImageFrameDescriptor> JPEG2000ImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
{
if (index != 0)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Invalid frame index");
if (m_context->state == JPEG2000LoadingContext::State::Error)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Decoding failed");
if (m_context->state < JPEG2000LoadingContext::State::DecodedImage) {
TRY(decode_image(*m_context));
m_context->state = JPEG2000LoadingContext::State::DecodedImage;
}
if (m_context->cmyk_bitmap && !m_context->bitmap)
return ImageFrameDescriptor { TRY(m_context->cmyk_bitmap->to_low_quality_rgb()), 0 };
return ImageFrameDescriptor { m_context->bitmap, 0 };
}
ErrorOr<Optional<ReadonlyBytes>> JPEG2000ImageDecoderPlugin::icc_data()
{
if (m_context->color_box.has_value()
&& (m_context->color_box->method == ISOBMFF::JPEG2000ColorSpecificationBox::Method::ICC_Restricted
|| m_context->color_box->method == ISOBMFF::JPEG2000ColorSpecificationBox::Method::ICC_Any)) {
return m_context->color_box->icc_data.bytes();
}
return OptionalNone {};
}
NaturalFrameFormat JPEG2000ImageDecoderPlugin::natural_frame_format() const
{
if (m_context->state == JPEG2000LoadingContext::State::Error)
return NaturalFrameFormat::RGB;
switch (m_context->color_space) {
case ColorSpace::sRGB:
return NaturalFrameFormat::RGB;
case ColorSpace::Gray:
return NaturalFrameFormat::Grayscale;
case ColorSpace::CMYK:
return NaturalFrameFormat::CMYK;
case ColorSpace::Unsupported:
return NaturalFrameFormat::RGB;
}
VERIFY_NOT_REACHED();
}
ErrorOr<NonnullRefPtr<CMYKBitmap>> JPEG2000ImageDecoderPlugin::cmyk_frame()
{
VERIFY(natural_frame_format() == NaturalFrameFormat::CMYK);
if (m_context->state < JPEG2000LoadingContext::State::DecodedImage) {
if (auto result = decode_image(*m_context); result.is_error()) {
m_context->state = JPEG2000LoadingContext::State::Error;
return result.release_error();
}
m_context->state = JPEG2000LoadingContext::State::DecodedImage;
}
return *m_context->cmyk_bitmap;
}
}