From 75d87ccf5ff99962246629e4a3523c8b195f4630 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Mon, 15 Jan 2024 00:56:21 -0500 Subject: [PATCH] LibGfx/TIFF+CCITT: Start to decode CCITT Group 3 images We currently only support 1D Group 3, but that's a start. The test case was generated with GIMP (it happens to be 1D by chance). --- Tests/LibGfx/TestImageDecoder.cpp | 12 ++++++ Tests/LibGfx/test-inputs/tiff/ccitt3.tiff | Bin 0 -> 2197 bytes .../LibGfx/ImageFormats/CCITTDecoder.cpp | 38 ++++++++++++++++++ .../LibGfx/ImageFormats/CCITTDecoder.h | 2 + .../LibGfx/ImageFormats/TIFFLoader.cpp | 14 +++++++ 5 files changed, 66 insertions(+) create mode 100644 Tests/LibGfx/test-inputs/tiff/ccitt3.tiff 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 0000000000000000000000000000000000000000..bbd046f4d869dd63ca95f3cd729ae08af35de103 GIT binary patch literal 2197 zcmebD)MA*&&cMJRT-CsGZvmtE3I^X-!=JN&{~Jd%(>4VLw*wz`i?Q4|;M>C>lu%(H zbdRlefmu!hLksg^2k*26F&Cu1888GlK1h&U$`&#EirF^>hHSP+x(C-V7EOq{VZgXX zR`KoG5b0@+`;IlRZDc#2m*}|B$Lw1OgXWolZ_lD6uQ9I9TEMiex>$=zs5$K{18dH= zX8}BtbG9;wZoWNZf#G(`EC%iL?S=-mA5RA`M%(V}Y`x1D)xdQz_l$?{grX}9VcmI2 z&iC4N9auBAJyVIk@^l5mTDb>D`b%ZDGKem|BQf#a;%N(*wp2F@*gAdvN4@DafeiI}$VB zfsEQv-7IX2%c$yR5r~}JgQNUleeF98oxx@%zT*ji*oUl0?lQ>m_8o@qTb}DWuqJ$a zrdI6-c4zX8g>NlS2QWt1_9eALB`v!k>YHyo)2R02*JBWcuq2;YFszX~Zs>fgAoHdI zgn8pY29#+Kro+}diGl0jn_^)m$K(g+H#vYcs41(>o~5)n0`T=vGwzgsCSM4Hkkd`a zw~D74m^`92Y-d>dFnDsOvTkYDY~WOUv*O!?rv{8Fwo~QK$n0V8cNs+lYc7$(V`>Q8XjgGlh621$hHV{7U%{Yz z>RejU-3IQWMKc$CPhh&U;zfq;0fy~bR+8*H7{s3XJac@tcl3}rJjFfgz|*$fN}41$a- z3?Q)v4h9Axs5nTyC=y!?%5Gp_U=W9@FX3QdkYZ#7+nd6{z#t7}=WsAE$UxPD%#mee zV&G(8VCdmsV9u0^-40+(ii|@6&Wf3 literal 0 HcmV?d00001 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 {