mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 08:58:11 +00:00

They're in different places for Sequential/Embedded (right after the header) and RandomAccess (which has all headers first, followed by all data bits next). We don't do anything with the data yet, but now everything's in place to actually process segment data.
280 lines
10 KiB
C++
280 lines
10 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 SegmentData {
|
|
SegmentHeader header;
|
|
ReadonlyBytes data;
|
|
};
|
|
|
|
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;
|
|
|
|
Vector<SegmentData> segments;
|
|
};
|
|
|
|
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)
|
|
{
|
|
ReadonlyBytes data = context.data;
|
|
if (context.organization != Organization::Embedded)
|
|
data = data.slice(sizeof(id_string) + sizeof(u8) + (context.number_of_pages.has_value() ? sizeof(u32) : 0));
|
|
FixedMemoryStream stream(data);
|
|
|
|
Vector<ReadonlyBytes> segment_datas;
|
|
auto store_and_skip_segment_data = [&](SegmentHeader const& segment_header) -> ErrorOr<void> {
|
|
if (!segment_header.data_length.has_value())
|
|
return Error::from_string_literal("JBIG2ImageDecoderPlugin: Can't handle segment without data length yet");
|
|
|
|
size_t start_offset = TRY(stream.tell());
|
|
if (start_offset + segment_header.data_length.value() > data.size())
|
|
return Error::from_string_literal("JBIG2ImageDecoderPlugin: Segment data length exceeds file size");
|
|
ReadonlyBytes segment_data = data.slice(start_offset, segment_header.data_length.value());
|
|
segment_datas.append(segment_data);
|
|
|
|
TRY(stream.seek(segment_header.data_length.value(), SeekMode::FromCurrentPosition));
|
|
return {};
|
|
};
|
|
|
|
Vector<SegmentHeader> segment_headers;
|
|
while (!stream.is_eof()) {
|
|
auto segment_header = TRY(decode_segment_header(stream));
|
|
segment_headers.append(segment_header);
|
|
|
|
if (context.organization != Organization::RandomAccess)
|
|
TRY(store_and_skip_segment_data(segment_header));
|
|
|
|
// Required per spec for files with RandomAccess organization.
|
|
if (segment_header.type == SegmentType::EndOfFile)
|
|
break;
|
|
}
|
|
|
|
if (context.organization == Organization::RandomAccess) {
|
|
for (auto const& segment_header : segment_headers)
|
|
TRY(store_and_skip_segment_data(segment_header));
|
|
}
|
|
|
|
if (segment_headers.size() != segment_datas.size())
|
|
return Error::from_string_literal("JBIG2ImageDecoderPlugin: Segment headers and segment datas have different sizes");
|
|
for (size_t i = 0; i < segment_headers.size(); ++i)
|
|
context.segments.append({ segment_headers[i], segment_datas[i] });
|
|
|
|
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");
|
|
}
|
|
|
|
ErrorOr<ByteBuffer> JBIG2ImageDecoderPlugin::decode_embedded(ReadonlyBytes data)
|
|
{
|
|
auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) JBIG2ImageDecoderPlugin(data)));
|
|
plugin->m_context->organization = Organization::Embedded;
|
|
TRY(decode_segment_headers(*plugin->m_context));
|
|
return Error::from_string_literal("JBIG2ImageDecoderPlugin: Cannot decode embedded JBIG2 yet");
|
|
}
|
|
|
|
}
|