LibGfx/JPEG2000: Add parsing loop for main header

We don't interpret any of the marker data yet.
This commit is contained in:
Nico Weber
2024-04-07 22:12:51 -04:00
committed by Andreas Kling
parent 6c9d3f8c46
commit 9eae6b3a90
Notes: sideshowbarker 2024-07-17 00:23:42 +09:00

View File

@@ -66,6 +66,7 @@ struct JPEG2000LoadingContext {
};
State state { State::NotDecoded };
ReadonlyBytes codestream_data;
size_t codestream_cursor { 0 };
Optional<ReadonlyBytes> icc_data;
IntSize size;
@@ -73,6 +74,90 @@ struct JPEG2000LoadingContext {
ISOBMFF::BoxList boxes;
};
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(JPEG2000LoadingContext& context)
{
if (context.codestream_cursor + 2 > context.codestream_data.size())
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Not enough data for marker");
return *reinterpret_cast<AK::BigEndian<u16> const*>(context.codestream_data.data() + 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<AK::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");
// FIXME: Parse SIZ marker.
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: {
// FIXME: These are valid main header markers. Parse contents.
auto marker = TRY(read_marker_at_cursor(context));
dbgln("JPEG2000ImageDecoderPlugin: marker {:#04x} not yet implemented", marker.marker);
break;
}
case J2K_SOT:
return {};
default:
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Unexpected marker in main header");
}
}
}
static ErrorOr<void> decode_jpeg2000_header(JPEG2000LoadingContext& context, ReadonlyBytes data)
{
if (!JPEG2000ImageDecoderPlugin::sniff(data))
@@ -163,6 +248,8 @@ static ErrorOr<void> decode_jpeg2000_header(JPEG2000LoadingContext& context, Rea
if (color_header_box.method == 2 || color_header_box.method == 3)
context.icc_data = color_header_box.icc_data.bytes();
TRY(parse_codestream_main_header(context));
return {};
}