From be9ec591e7c69086368fd1de02a0b43b21734614 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Mon, 12 Feb 2024 09:36:46 -0500 Subject: [PATCH] LibGfx/CCITT: Add support for Group3 2D The two test images were created with: tiffcp ccit3.tiff -c g3:2d ccit3_2d.tiff tiffcp ccit3.tiff -c g3:2d:fill ccit3_2d_fill.tiff --- Tests/LibGfx/TestImageDecoder.cpp | 24 +++ Tests/LibGfx/test-inputs/tiff/ccitt3_2d.tiff | Bin 0 -> 1389 bytes .../test-inputs/tiff/ccitt3_2d_fill.tiff | Bin 0 -> 1489 bytes .../LibGfx/ImageFormats/CCITTDecoder.cpp | 169 +++++++++++++++++- 4 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 Tests/LibGfx/test-inputs/tiff/ccitt3_2d.tiff create mode 100644 Tests/LibGfx/test-inputs/tiff/ccitt3_2d_fill.tiff diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index 7f7e15836b..7e8a63048f 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -630,6 +630,30 @@ TEST_CASE(test_tiff_ccitt3_fill) EXPECT_EQ(frame.image->get_pixel(5, 3), Gfx::Color::NamedColor::White); } +TEST_CASE(test_tiff_ccitt3_2d) +{ + auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/ccitt3_2d.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_ccitt3_2d_fill) +{ + auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/ccitt3_2d_fill.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_2d.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt3_2d.tiff new file mode 100644 index 0000000000000000000000000000000000000000..2187c57225c1d514a8b13072b71017790c89bf3b GIT binary patch literal 1389 zcmebD)MChDVPIg8-0Q&9z{Q}!aP}302txp)16zIo!vc_yaa98=SZMYY1`Uu9&%FhV z4j`eluM8j^3z*GUfON3Z(2!0C`MFJkH$)i(rI`=S?PikgVQJu)%zUs($ojy!vWA-s z5)L*dEZeeJ&oZu0W?%_!e2{1~MKZ;Au5%uPisO#vJk>eLnq^C_Vzj1-# zwLs3dXCl%fhx1b%m=g8~^1Nn|+O~>8@q-J8m*VEM2Br^Wzm zh2L)&;#m){>|#B@wk5h)NbZYe7K8pe2Cdf(-2WI%7vGVX_-^sE1x%N`8#wD5xb`>j zrhI2psb2GR1;hRaO#i`(CW91hp0P_Yz3=0 zVY7oF{zUo&kZFBM?OPsP-sHeo_kpoWVNGNM_Y+Rm&TkA-2j3J6GZzLs#I9-J{*%D; zzk&0o(b5BKo4#3g%AKCG1!Pb-#I|{7eLTL!JY4{C57Z+!S9aPqTV^om$1&)G+{qki zJ8QfWjjl6?Rv zz}i&*fz7U9xW=EhOr@Fw*r?l+Db*z`zD&GcYhP2r{xTfW#tL85o41;vn^+NNh1E zyMcj$K^&^yhn0arijfs;uMH~$gEW-w!pguP162<)N0yO^fs=uOA%&HJL4}b8tR5t; z0ae4qz`zg;WrM^)ZUVU-#D>vd7#SG2m>3w685kHi7#JA-FfuT(u`n>?RF&(MWTvGt F007c3op%5L literal 0 HcmV?d00001 diff --git a/Tests/LibGfx/test-inputs/tiff/ccitt3_2d_fill.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt3_2d_fill.tiff new file mode 100644 index 0000000000000000000000000000000000000000..706db669a737e46592264068de4431ed7f317b4c GIT binary patch literal 1489 zcmebD)M7Zt!oa}5IQt3%qXQTYEtXJ&y)}#S1}z3g!3{Q=H#R8VIN-s+D3nlP;N)H* zdG3O=0%HsFVF&ND1u++-z8Nquiae;0@L9?hDV!c~yMa-O_lR_%HbX2&0HcSMdy~SQ zEsgt*H85UNxcT;shM8vDy;BX025}s1rA@rip$v@tO$ttdyt7Rh7%Lo>JYB)SxOU#1 zql~>fPX{noDg1uJz*r;Bz^K<)z_?cI&XM-Ld{GUIwFQjfF$|3L2N>7NJvh=|DzlY= zF;n#bqy7iR@DGe@=7GfKYy~T*KLApO5K@^A7N|eKxJK^5k@Kaf%Fu*@;u#p%*dFYX zdu^G8Y?|%CF5Bf$p}0K^jF~kIjBDhM8#>=Ap0)sFU%kSo2aNTK^PC%?;su#E6&M)n zJ}_1(tch%3tZ7u(Xm}a^Lh^7c3P20c7Z! zd1rk*zQsIU02YIJ?5vOH8_ClSj8$MYAb-s}>yz`12SXV`tQ4$t&AhV_6Tw>R4=`p! zgk~=6lsh)>u#mtL39!!k1K?<| z?Mrgs(5?WEmihxA&rh0n+)(*e^F*+!+&^Gy(!4vz&S#zT0CPZ|Dqx(n_l`=nT9E|< zWAX>a+5?Q$2N);qoigu|%nAlZ^$(2I1&rkf7$?b{>d)j0W&m;P89>TFA_?w#42<;* zAT7D342+W`HyAJ$FcvVzFfdLM+*rV9%D~72%9PtfqdgcHS!5U(3mDZI7`F$>U10z* zAUsyZ^9ctSRVQgMFsj@tU<^ONxLwCidEN&GM(q!b;RTF(9~if5SxIphfGr9?z$o^> zBkBR;c0m9##ehA*eV=y(kh}49ad`U| z1_p*;C>x|6 read_run_length(BigEndianInputBitStream& input_bit_stream, Color current_color, u32 image_width, u32 column) +struct Change { + Color color; + u32 column; +}; + +using ReferenceLine = Vector; + +ErrorOr read_run_length(BigEndianInputBitStream& input_bit_stream, Optional reference_line, Color current_color, u32 image_width, u32 column) { u8 size {}; u16 potential_code {}; @@ -289,6 +296,8 @@ ErrorOr read_run_length(BigEndianInputBitStream& input_bit_stream, Color cu potential_code = 0; } else if (auto const maybe_terminal = get_terminal_code(current_color, potential_code, size); maybe_terminal.has_value()) { run_length += maybe_terminal->run_length; + if (reference_line.has_value()) + TRY(reference_line->try_append({ invert(current_color), column + run_length })); break; } } @@ -302,8 +311,11 @@ ErrorOr read_run_length(BigEndianInputBitStream& input_bit_stream, Color cu return run_length; } -ErrorOr decode_single_ccitt3_1d_line(BigEndianInputBitStream& input_bit_stream, BigEndianOutputBitStream& decoded_bits, u32 image_width) +ErrorOr decode_single_ccitt3_1d_line(BigEndianInputBitStream& input_bit_stream, BigEndianOutputBitStream& decoded_bits, u32 image_width) { + // This is only useful for the 2D decoder. + ReferenceLine reference_line; + // We always flip the color when entering the loop, so let's initialize the // color with black to make the first marker actually be white. Color current_color { ccitt_black }; @@ -321,12 +333,12 @@ ErrorOr decode_single_ccitt3_1d_line(BigEndianInputBitStream& input_bit_st current_color = invert(current_color); - run_length += TRY(read_run_length(input_bit_stream, current_color, image_width, column)); + run_length += TRY(read_run_length(input_bit_stream, reference_line, current_color, image_width, column)); } TRY(decoded_bits.align_to_byte_boundary()); - return {}; + return reference_line; } static ErrorOr read_eol(BigEndianInputBitStream& bit_stream, Group3Options::UseFillBits use_fill_bits) @@ -349,6 +361,152 @@ static ErrorOr read_eol(BigEndianInputBitStream& bit_stream, Group3Options return {}; } +enum class Mode : u8 { + Pass, + Horizontal, + Vertical_0, + Vertical_R1, + Vertical_R2, + Vertical_R3, + Vertical_L1, + Vertical_L2, + Vertical_L3, +}; + +struct ModeCode { + u8 code_length {}; + Mode mode {}; + u8 code {}; +}; + +// Table 4/T.4 – Two-dimensional code table +constexpr Array node_codes = to_array({ + { 4, Mode::Pass, 0b0001 }, + { 3, Mode::Horizontal, 0b001 }, + { 1, Mode::Vertical_0, 0b1 }, + { 3, Mode::Vertical_R1, 0b011 }, + { 6, Mode::Vertical_R2, 0b000011 }, + { 7, Mode::Vertical_R3, 0b0000011 }, + { 3, Mode::Vertical_L1, 0b010 }, + { 6, Mode::Vertical_L2, 0b000010 }, + { 7, Mode::Vertical_L3, 0b0000010 }, +}); + +ErrorOr read_mode(BigEndianInputBitStream& input_bit_stream) +{ + u8 size {}; + u16 potential_code {}; + while (size < 7) { + potential_code <<= 1; + potential_code |= TRY(input_bit_stream.read_bit()); + ++size; + + if (auto const maybe_mode = get_code_from_table(node_codes, potential_code, size); maybe_mode.has_value()) + return *maybe_mode; + } + + return Error::from_string_literal("CCITTDecoder: Unable to find the correct mode"); +} + +enum class Search : u8 { + B1, + B2, +}; + +ErrorOr decode_single_ccitt3_2d_block(BigEndianInputBitStream& input_bit_stream, BigEndianOutputBitStream& decoded_bits, u32 image_width, u32 image_height, Group3Options::UseFillBits use_fill_bits) +{ + ReferenceLine reference_line; + for (u32 i = 0; i < image_height; ++i) { + TRY(read_eol(input_bit_stream, use_fill_bits)); + bool const next_is_1D = TRY(input_bit_stream.read_bit()) == 1; + + if (next_is_1D) { + reference_line = TRY(decode_single_ccitt3_1d_line(input_bit_stream, decoded_bits, image_width)); + } else { + ReferenceLine current_line {}; + Color current_color { ccitt_white }; + u32 column {}; + + auto const next_change_on_reference_line = [&](Search search = Search::B1) -> ErrorOr { + // 4.2.1.3.1 Definition of changing picture elements + Optional next_change {}; // This is referred to as b1 in the spec. + while (!next_change.has_value()) { + if (reference_line.is_empty()) + return Error::from_string_literal("CCITTDecoder: Corrupted stream"); + auto const change = reference_line.take_first(); + if (change.column < column) + continue; + if ((search == Search::B1 && change.color != current_color) + || (search == Search::B2 && change.color == current_color)) + next_change = change; + } + return *next_change; + }; + + auto const encode_for = [&](Change change, i8 offset = 0) -> ErrorOr { + auto const to_encode = change.column - column + offset; + for (u32 i {}; i < to_encode; ++i) + TRY(decoded_bits.write_bits(current_color == ccitt_white ? 0u : 1u, 1)); + + column += to_encode; + current_color = change.color; + + TRY(current_line.try_empend(change.color, change.column + offset)); + return {}; + }; + + while (column < image_width) { + auto const mode = TRY(read_mode(input_bit_stream)); + + // Behavior are described here 4.2.1.3.2 Coding modes. + switch (mode.mode) { + case Mode::Pass: + TRY(next_change_on_reference_line(Search::B2)); + break; + case Mode::Horizontal: { + // a0a1 + auto run_length = TRY(read_run_length(input_bit_stream, OptionalNone {}, current_color, image_width, column)); + TRY(encode_for({ invert(current_color), column + run_length })); + + // a1a2 + run_length = TRY(read_run_length(input_bit_stream, OptionalNone {}, current_color, image_width, column)); + TRY(encode_for({ invert(current_color), column + run_length })); + break; + } + case Mode::Vertical_0: + TRY(encode_for(TRY(next_change_on_reference_line()))); + break; + case Mode::Vertical_R1: + TRY(encode_for(TRY(next_change_on_reference_line()), 1)); + break; + case Mode::Vertical_R2: + TRY(encode_for(TRY(next_change_on_reference_line()), 2)); + break; + case Mode::Vertical_R3: + TRY(encode_for(TRY(next_change_on_reference_line()), 3)); + break; + case Mode::Vertical_L1: + TRY(encode_for(TRY(next_change_on_reference_line()), -1)); + break; + case Mode::Vertical_L2: + TRY(encode_for(TRY(next_change_on_reference_line()), -2)); + break; + case Mode::Vertical_L3: + TRY(encode_for(TRY(next_change_on_reference_line()), -3)); + break; + default: + return Error::from_string_literal("CCITTDecoder: Unsupported mode for 2D decoding"); + } + } + reference_line = move(current_line); + } + } + + TRY(decoded_bits.align_to_byte_boundary()); + + return {}; +} + } ErrorOr decode_ccitt_rle(ReadonlyBytes bytes, u32 image_width, u32 image_height) @@ -396,7 +554,8 @@ ErrorOr decode_ccitt_group3(ReadonlyBytes bytes, u32 image_width, u3 return decoded_bytes; } - return Error::from_string_literal("CCITT3 2D is not implemented yet :^("); + TRY(decode_single_ccitt3_2d_block(*bit_stream, *decoded_bits, image_width, image_height, options.use_fill_bits)); + return decoded_bytes; } }