|
@@ -9,12 +9,39 @@
|
|
|
|
|
|
// 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
|
|
@@ -27,6 +54,14 @@ enum class 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,
|
|
@@ -53,6 +88,7 @@ static ErrorOr<void> decode_jbig2_header(JBIG2LoadingContext& context)
|
|
|
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;
|
|
@@ -70,6 +106,81 @@ static ErrorOr<void> decode_jbig2_header(JBIG2LoadingContext& context)
|
|
|
return {};
|
|
|
}
|
|
|
|
|
|
+static ErrorOr<SegmentHeader> decode_segment_header(JBIG2LoadingContext& context)
|
|
|
+{
|
|
|
+ ReadonlyBytes data = context.data.slice(sizeof(id_string) + sizeof(u8) + (context.number_of_pages.has_value() ? sizeof(u32) : 0));
|
|
|
+ FixedMemoryStream stream(data);
|
|
|
+
|
|
|
+ // 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 };
|
|
|
+}
|
|
|
+
|
|
|
JBIG2ImageDecoderPlugin::JBIG2ImageDecoderPlugin(ReadonlyBytes data)
|
|
|
{
|
|
|
m_context = make<JBIG2LoadingContext>();
|
|
@@ -90,6 +201,7 @@ ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JBIG2ImageDecoderPlugin::create(Reado
|
|
|
{
|
|
|
auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) JBIG2ImageDecoderPlugin(data)));
|
|
|
TRY(decode_jbig2_header(*plugin->m_context));
|
|
|
+ TRY(decode_segment_header(*plugin->m_context));
|
|
|
return plugin;
|
|
|
}
|
|
|
|