diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index 02b8480138..596964dd27 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -555,6 +555,18 @@ TEST_CASE(test_tiff_ccitt_rle) EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Black); } +TEST_CASE(test_tiff_ccitt3) +{ + auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/ccitt3.tiff"sv))); + EXPECT(Gfx::TIFFImageDecoderPlugin::sniff(file->bytes())); + auto plugin_decoder = TRY_OR_FAIL(Gfx::TIFFImageDecoderPlugin::create(file->bytes())); + + auto frame = TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 400, 300 })); + + EXPECT_EQ(frame.image->get_pixel(0, 0), Gfx::Color::NamedColor::White); + EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Black); +} + TEST_CASE(test_tiff_lzw) { auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/lzw.tiff"sv))); diff --git a/Tests/LibGfx/test-inputs/tiff/ccitt3.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt3.tiff new file mode 100644 index 0000000000..bbd046f4d8 Binary files /dev/null and b/Tests/LibGfx/test-inputs/tiff/ccitt3.tiff differ diff --git a/Userland/Libraries/LibGfx/ImageFormats/CCITTDecoder.cpp b/Userland/Libraries/LibGfx/ImageFormats/CCITTDecoder.cpp index 3019833289..ace6134fd8 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/CCITTDecoder.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/CCITTDecoder.cpp @@ -316,6 +316,16 @@ ErrorOr decode_single_ccitt3_1d_line(BigEndianInputBitStream& input_bit_st return {}; } +static ErrorOr read_eol(BigEndianInputBitStream& bit_stream) +{ + constexpr u16 EOL = 0b0000'0000'0001; + auto const read = TRY(bit_stream.read_bits(12)); + if (read != EOL) + return Error::from_string_literal("CCITTDecoder: Invalid EndOfLine code"); + + return {}; +} + } ErrorOr decode_ccitt_rle(ReadonlyBytes bytes, u32 image_width, u32 image_height) @@ -337,4 +347,32 @@ ErrorOr decode_ccitt_rle(ReadonlyBytes bytes, u32 image_width, u32 i return decoded_bytes; } +ErrorOr decode_ccitt_group3(ReadonlyBytes bytes, u32 image_width, u32 image_height, Group3Options const& options) +{ + auto strip_stream = make(bytes); + auto bit_stream = make(MaybeOwned(*strip_stream)); + + ByteBuffer decoded_bytes = TRY(ByteBuffer::create_zeroed(ceil_div(image_width * image_height, 8))); + auto output_stream = make(decoded_bytes.bytes()); + auto decoded_bits = make(MaybeOwned(*output_stream)); + + if (options.dimensions == Group3Options::Mode::OneDimension) { + // 4.1.2 End-of-line (EOL) + // This code word follows each line of data. It is a unique code word that can never be found within a + // valid line of data; therefore, resynchronization after an error burst is possible. + // In addition, this signal will occur prior to the first data line of a page. + // --- + // NOTE: For whatever reason, the last EOL doesn't seem to be included + + for (u32 i = 0; i < image_height; ++i) { + TRY(read_eol(*bit_stream)); + TRY(decode_single_ccitt3_1d_line(*bit_stream, *decoded_bits, image_width)); + } + + return decoded_bytes; + } + + return Error::from_string_literal("CCITT3 2D is not implemented yet :^("); +} + } diff --git a/Userland/Libraries/LibGfx/ImageFormats/CCITTDecoder.h b/Userland/Libraries/LibGfx/ImageFormats/CCITTDecoder.h index 108204ece1..968a5472d8 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/CCITTDecoder.h +++ b/Userland/Libraries/LibGfx/ImageFormats/CCITTDecoder.h @@ -45,4 +45,6 @@ struct Group3Options { UseFillBits use_fill_bits = UseFillBits::No; }; +ErrorOr decode_ccitt_group3(ReadonlyBytes bytes, u32 image_width, u32 image_height, Group3Options const& options); + } diff --git a/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp index f334910965..9efe784d19 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp @@ -314,6 +314,20 @@ private: TRY(loop_over_pixels(move(decode_ccitt_rle_strip))); break; } + case Compression::Group3Fax: { + TRY(ensure_tags_are_correct_for_ccitt()); + + auto const parameters = parse_t4_options(*m_metadata.t4_options()); + ByteBuffer decoded_bytes {}; + auto decode_group3_strip = [&](u32 num_bytes, u32 strip_height) -> ErrorOr { + auto const encoded_bytes = TRY(m_stream->read_in_place(num_bytes)); + decoded_bytes = TRY(CCITT::decode_ccitt_group3(encoded_bytes, *m_metadata.image_width(), strip_height, parameters)); + return decoded_bytes; + }; + + TRY(loop_over_pixels(move(decode_group3_strip))); + break; + } case Compression::LZW: { ByteBuffer decoded_bytes {}; auto decode_lzw_strip = [&](u32 num_bytes, u32) -> ErrorOr {