mirror of
https://github.com/RGBCube/serenity
synced 2025-05-23 18:55:08 +00:00

With `#define JBIG2_DEBUG 1` at the top of the file: % Build/lagom/bin/image --no-output \ .../JBIG2_ConformanceData-A20180829/F01_200_TT10.jb2 JBIG2LoadingContext: Organization: 0 (Sequential) JBIG2LoadingContext: Number of pages: 1 Segment number: 0 Segment type: 48 Referred to segment count: 0 Segment page association: 1 Segment data length: 19 Segment number: 1 Segment type: 39 Referred to segment count: 0 Segment page association: 1 Segment data length: 12666 Segment number: 2 Segment type: 49 Referred to segment count: 0 Segment page association: 1 Segment data length: 0 Runtime error: JBIG2ImageDecoderPlugin: Draw the rest of the owl
235 lines
8.5 KiB
C++
235 lines
8.5 KiB
C++
/*
|
|
* Copyright (c) 2024, Nico Weber <thakis@chromium.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Debug.h>
|
|
#include <LibGfx/ImageFormats/JBIG2Loader.h>
|
|
|
|
// Spec: ITU-T_T_88__08_2018.pdf in the zip file here:
|
|
// https://www.itu.int/rec/T-REC-T.88-201808-I
|
|
// Annex H has a datastream example.
|
|
|
|
namespace Gfx {
|
|
|
|
// JBIG2 spec, Annex D, D.4.1 ID string
|
|
static constexpr u8 id_string[] = { 0x97, 0x4A, 0x42, 0x32, 0x0D, 0x0A, 0x1A, 0x0A };
|
|
|
|
// 7.3 Segment types
|
|
enum SegmentType {
|
|
SymbolDictionary = 0,
|
|
IntermediateTextRegion = 4,
|
|
ImmediateTextRegion = 6,
|
|
ImmediateLosslessTextRegion = 7,
|
|
PatternDictionary = 16,
|
|
IntermediateHalftoneRegion = 20,
|
|
ImmediateHalftoneRegion = 22,
|
|
ImmediateLosslessHalftoneRegion = 23,
|
|
IntermediateGenericRegion = 36,
|
|
ImmediateGenericRegion = 38,
|
|
ImmediateLosslessGenericRegion = 39,
|
|
IntermediateGenericRefinementRegion = 40,
|
|
ImmediateGenericRefinementRegion = 42,
|
|
ImmediateLosslessGenericRefinementRegion = 43,
|
|
PageInformation = 48,
|
|
EndOfPage = 49,
|
|
EndOfStripe = 50,
|
|
EndOfFile = 51,
|
|
Profiles = 52,
|
|
Tables = 53,
|
|
ColorPalette = 54,
|
|
Extension = 62,
|
|
};
|
|
|
|
// Annex D
|
|
enum class Organization {
|
|
// D.1 Sequential organization
|
|
Sequential,
|
|
|
|
// D.2 Random-access organization
|
|
RandomAccess,
|
|
|
|
// D.3 Embedded organization
|
|
Embedded,
|
|
};
|
|
|
|
struct SegmentHeader {
|
|
u32 segment_number;
|
|
SegmentType type;
|
|
Vector<u32> referred_to_segment_numbers;
|
|
u32 page_association;
|
|
Optional<u32> data_length;
|
|
};
|
|
|
|
struct JBIG2LoadingContext {
|
|
enum class State {
|
|
NotDecoded = 0,
|
|
Error,
|
|
};
|
|
State state { State::NotDecoded };
|
|
ReadonlyBytes data;
|
|
|
|
Organization organization { Organization::Sequential };
|
|
IntSize size;
|
|
|
|
Optional<u32> number_of_pages;
|
|
};
|
|
|
|
static ErrorOr<void> decode_jbig2_header(JBIG2LoadingContext& context)
|
|
{
|
|
if (!JBIG2ImageDecoderPlugin::sniff(context.data))
|
|
return Error::from_string_literal("JBIG2LoadingContext: Invalid JBIG2 header");
|
|
|
|
FixedMemoryStream stream(context.data.slice(sizeof(id_string)));
|
|
|
|
// D.4.2 File header flags
|
|
u8 header_flags = TRY(stream.read_value<u8>());
|
|
if (header_flags & 0b11110000)
|
|
return Error::from_string_literal("JBIG2LoadingContext: Invalid header flags");
|
|
context.organization = (header_flags & 1) ? Organization::Sequential : Organization::RandomAccess;
|
|
dbgln_if(JBIG2_DEBUG, "JBIG2LoadingContext: Organization: {} ({})", (int)context.organization, context.organization == Organization::Sequential ? "Sequential" : "Random-access");
|
|
bool has_known_number_of_pages = (header_flags & 2) ? false : true;
|
|
bool uses_templates_with_12_AT_pixels = (header_flags & 4) ? true : false;
|
|
bool contains_colored_region_segments = (header_flags & 8) ? true : false;
|
|
|
|
// FIXME: Do something with these?
|
|
(void)uses_templates_with_12_AT_pixels;
|
|
(void)contains_colored_region_segments;
|
|
|
|
// D.4.3 Number of pages
|
|
if (has_known_number_of_pages) {
|
|
context.number_of_pages = TRY(stream.read_value<BigEndian<u32>>());
|
|
dbgln_if(JBIG2_DEBUG, "JBIG2LoadingContext: Number of pages: {}", context.number_of_pages.value());
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
static ErrorOr<SegmentHeader> decode_segment_header(SeekableStream& stream)
|
|
{
|
|
// 7.2.2 Segment number
|
|
u32 segment_number = TRY(stream.read_value<BigEndian<u32>>());
|
|
dbgln_if(JBIG2_DEBUG, "Segment number: {}", segment_number);
|
|
|
|
// 7.2.3 Segment header flags
|
|
u8 flags = TRY(stream.read_value<u8>());
|
|
SegmentType type = static_cast<SegmentType>(flags & 0b11'1111);
|
|
dbgln_if(JBIG2_DEBUG, "Segment type: {}", (int)type);
|
|
bool segment_page_association_size_is_32_bits = (flags & 0b100'0000) != 0;
|
|
bool segment_retained_only_by_itself_and_extension_segments = (flags & 0b1000'00000) != 0;
|
|
|
|
// FIXME: Do something with these.
|
|
(void)segment_page_association_size_is_32_bits;
|
|
(void)segment_retained_only_by_itself_and_extension_segments;
|
|
|
|
// 7.2.4 Referred-to segment count and retention flags
|
|
u8 referred_to_segment_count_and_retention_flags = TRY(stream.read_value<u8>());
|
|
u32 count_of_referred_to_segments = referred_to_segment_count_and_retention_flags >> 5;
|
|
if (count_of_referred_to_segments == 5 || count_of_referred_to_segments == 6)
|
|
return Error::from_string_literal("JBIG2ImageDecoderPlugin: Invalid count_of_referred_to_segments");
|
|
u32 extra_count = 0;
|
|
if (count_of_referred_to_segments == 7) {
|
|
TRY(stream.seek(-1, SeekMode::FromCurrentPosition));
|
|
count_of_referred_to_segments = TRY(stream.read_value<BigEndian<u32>>()) & 0x1FFF'FFFF;
|
|
extra_count = ceil_div(count_of_referred_to_segments + 1, 8);
|
|
TRY(stream.seek(extra_count, SeekMode::FromCurrentPosition));
|
|
}
|
|
dbgln_if(JBIG2_DEBUG, "Referred-to segment count: {}", count_of_referred_to_segments);
|
|
|
|
// 7.2.5 Referred-to segment numbers
|
|
Vector<u32> referred_to_segment_numbers;
|
|
for (u32 i = 0; i < count_of_referred_to_segments; ++i) {
|
|
u32 referred_to_segment_number;
|
|
if (segment_number <= 256)
|
|
referred_to_segment_number = TRY(stream.read_value<u8>());
|
|
else if (segment_number <= 65536)
|
|
referred_to_segment_number = TRY(stream.read_value<BigEndian<u16>>());
|
|
else
|
|
referred_to_segment_number = TRY(stream.read_value<BigEndian<u32>>());
|
|
referred_to_segment_numbers.append(referred_to_segment_number);
|
|
dbgln_if(JBIG2_DEBUG, "Referred-to segment number: {}", referred_to_segment_number);
|
|
}
|
|
|
|
// 7.2.6 Segment page association
|
|
u32 segment_page_association;
|
|
if (segment_page_association_size_is_32_bits) {
|
|
segment_page_association = TRY(stream.read_value<BigEndian<u32>>());
|
|
} else {
|
|
segment_page_association = TRY(stream.read_value<u8>());
|
|
}
|
|
dbgln_if(JBIG2_DEBUG, "Segment page association: {}", segment_page_association);
|
|
|
|
// 7.2.7 Segment data length
|
|
u32 data_length = TRY(stream.read_value<BigEndian<u32>>());
|
|
dbgln_if(JBIG2_DEBUG, "Segment data length: {}", data_length);
|
|
|
|
// FIXME: Add some validity checks:
|
|
// - data_length can only be 0xffff'ffff for type ImmediateGenericRegion
|
|
// - check type is valid
|
|
// - check referred_to_segment_numbers are smaller than segment_number
|
|
// - 7.3.1 Rules for segment references
|
|
// - 7.3.2 Rules for page associations
|
|
|
|
Optional<u32> opt_data_length;
|
|
if (data_length != 0xffff'ffff)
|
|
opt_data_length = data_length;
|
|
|
|
return SegmentHeader { segment_number, type, move(referred_to_segment_numbers), segment_page_association, opt_data_length };
|
|
}
|
|
|
|
static ErrorOr<void> decode_segment_headers(JBIG2LoadingContext& context)
|
|
{
|
|
FixedMemoryStream stream(context.data.slice(sizeof(id_string) + sizeof(u8) + (context.number_of_pages.has_value() ? sizeof(u32) : 0)));
|
|
while (!stream.is_eof()) {
|
|
auto segment_header = TRY(decode_segment_header(stream));
|
|
if (context.organization != Organization::RandomAccess) {
|
|
if (!segment_header.data_length.has_value())
|
|
return Error::from_string_literal("JBIG2ImageDecoderPlugin: Can't handle non-random-access organization segment without data length yet");
|
|
TRY(stream.seek(segment_header.data_length.value(), SeekMode::FromCurrentPosition));
|
|
}
|
|
|
|
// Required per spec for files with RandomAccess organization.
|
|
if (segment_header.type == SegmentType::EndOfFile)
|
|
break;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
JBIG2ImageDecoderPlugin::JBIG2ImageDecoderPlugin(ReadonlyBytes data)
|
|
{
|
|
m_context = make<JBIG2LoadingContext>();
|
|
m_context->data = data;
|
|
}
|
|
|
|
IntSize JBIG2ImageDecoderPlugin::size()
|
|
{
|
|
return m_context->size;
|
|
}
|
|
|
|
bool JBIG2ImageDecoderPlugin::sniff(ReadonlyBytes data)
|
|
{
|
|
return data.starts_with(id_string);
|
|
}
|
|
|
|
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JBIG2ImageDecoderPlugin::create(ReadonlyBytes data)
|
|
{
|
|
auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) JBIG2ImageDecoderPlugin(data)));
|
|
TRY(decode_jbig2_header(*plugin->m_context));
|
|
TRY(decode_segment_headers(*plugin->m_context));
|
|
return plugin;
|
|
}
|
|
|
|
ErrorOr<ImageFrameDescriptor> JBIG2ImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
|
|
{
|
|
// FIXME: Use this for multi-page JBIG2 files?
|
|
if (index != 0)
|
|
return Error::from_string_literal("JBIG2ImageDecoderPlugin: Invalid frame index");
|
|
|
|
if (m_context->state == JBIG2LoadingContext::State::Error)
|
|
return Error::from_string_literal("JBIG2ImageDecoderPlugin: Decoding failed");
|
|
|
|
return Error::from_string_literal("JBIG2ImageDecoderPlugin: Draw the rest of the owl");
|
|
}
|
|
|
|
}
|