diff --git a/Libraries/LibGfx/ImageFormats/CCITTDecoder.cpp b/Libraries/LibGfx/ImageFormats/CCITTDecoder.cpp deleted file mode 100644 index d39e55ec1c..0000000000 --- a/Libraries/LibGfx/ImageFormats/CCITTDecoder.cpp +++ /dev/null @@ -1,638 +0,0 @@ -/* - * Copyright (c) 2023, Lucas Chollet - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -namespace Gfx::CCITT { - -namespace { - -struct Code { - u16 run_length {}; - u8 code_length {}; - u16 code {}; -}; - -// Table 2/T.4 – Terminating codes -constexpr Array white_terminating_codes = { - Code { 0, 8, 0b00110101 }, - Code { 1, 6, 0b000111 }, - Code { 2, 4, 0b0111 }, - Code { 3, 4, 0b1000 }, - Code { 4, 4, 0b1011 }, - Code { 5, 4, 0b1100 }, - Code { 6, 4, 0b1110 }, - Code { 7, 4, 0b1111 }, - Code { 8, 5, 0b10011 }, - Code { 9, 5, 0b10100 }, - Code { 10, 5, 0b00111 }, - Code { 11, 5, 0b01000 }, - Code { 12, 6, 0b001000 }, - Code { 13, 6, 0b000011 }, - Code { 14, 6, 0b110100 }, - Code { 15, 6, 0b110101 }, - Code { 16, 6, 0b101010 }, - Code { 17, 6, 0b101011 }, - Code { 18, 7, 0b0100111 }, - Code { 19, 7, 0b0001100 }, - Code { 20, 7, 0b0001000 }, - Code { 21, 7, 0b0010111 }, - Code { 22, 7, 0b0000011 }, - Code { 23, 7, 0b0000100 }, - Code { 24, 7, 0b0101000 }, - Code { 25, 7, 0b0101011 }, - Code { 26, 7, 0b0010011 }, - Code { 27, 7, 0b0100100 }, - Code { 28, 7, 0b0011000 }, - Code { 29, 8, 0b00000010 }, - Code { 30, 8, 0b00000011 }, - Code { 31, 8, 0b00011010 }, - Code { 32, 8, 0b00011011 }, - Code { 33, 8, 0b00010010 }, - Code { 34, 8, 0b00010011 }, - Code { 35, 8, 0b00010100 }, - Code { 36, 8, 0b00010101 }, - Code { 37, 8, 0b00010110 }, - Code { 38, 8, 0b00010111 }, - Code { 39, 8, 0b00101000 }, - Code { 40, 8, 0b00101001 }, - Code { 41, 8, 0b00101010 }, - Code { 42, 8, 0b00101011 }, - Code { 43, 8, 0b00101100 }, - Code { 44, 8, 0b00101101 }, - Code { 45, 8, 0b00000100 }, - Code { 46, 8, 0b00000101 }, - Code { 47, 8, 0b00001010 }, - Code { 48, 8, 0b00001011 }, - Code { 49, 8, 0b01010010 }, - Code { 50, 8, 0b01010011 }, - Code { 51, 8, 0b01010100 }, - Code { 52, 8, 0b01010101 }, - Code { 53, 8, 0b00100100 }, - Code { 54, 8, 0b00100101 }, - Code { 55, 8, 0b01011000 }, - Code { 56, 8, 0b01011001 }, - Code { 57, 8, 0b01011010 }, - Code { 58, 8, 0b01011011 }, - Code { 59, 8, 0b01001010 }, - Code { 60, 8, 0b01001011 }, - Code { 61, 8, 0b00110010 }, - Code { 62, 8, 0b00110011 }, - Code { 63, 8, 0b00110100 }, -}; - -// Table 2/T.4 – Terminating codes -constexpr Array black_terminating_codes = { - Code { 0, 10, 0b0000110111 }, - Code { 1, 3, 0b010 }, - Code { 2, 2, 0b11 }, - Code { 3, 2, 0b10 }, - Code { 4, 3, 0b011 }, - Code { 5, 4, 0b0011 }, - Code { 6, 4, 0b0010 }, - Code { 7, 5, 0b00011 }, - Code { 8, 6, 0b000101 }, - Code { 9, 6, 0b000100 }, - Code { 10, 7, 0b0000100 }, - Code { 11, 7, 0b0000101 }, - Code { 12, 7, 0b0000111 }, - Code { 13, 8, 0b00000100 }, - Code { 14, 8, 0b00000111 }, - Code { 15, 9, 0b000011000 }, - Code { 16, 10, 0b0000010111 }, - Code { 17, 10, 0b0000011000 }, - Code { 18, 10, 0b0000001000 }, - Code { 19, 11, 0b00001100111 }, - Code { 20, 11, 0b00001101000 }, - Code { 21, 11, 0b00001101100 }, - Code { 22, 11, 0b00000110111 }, - Code { 23, 11, 0b00000101000 }, - Code { 24, 11, 0b00000010111 }, - Code { 25, 11, 0b00000011000 }, - Code { 26, 12, 0b000011001010 }, - Code { 27, 12, 0b000011001011 }, - Code { 28, 12, 0b000011001100 }, - Code { 29, 12, 0b000011001101 }, - Code { 30, 12, 0b000001101000 }, - Code { 31, 12, 0b000001101001 }, - Code { 32, 12, 0b000001101010 }, - Code { 33, 12, 0b000001101011 }, - Code { 34, 12, 0b000011010010 }, - Code { 35, 12, 0b000011010011 }, - Code { 36, 12, 0b000011010100 }, - Code { 37, 12, 0b000011010101 }, - Code { 38, 12, 0b000011010110 }, - Code { 39, 12, 0b000011010111 }, - Code { 40, 12, 0b000001101100 }, - Code { 41, 12, 0b000001101101 }, - Code { 42, 12, 0b000011011010 }, - Code { 43, 12, 0b000011011011 }, - Code { 44, 12, 0b000001010100 }, - Code { 45, 12, 0b000001010101 }, - Code { 46, 12, 0b000001010110 }, - Code { 47, 12, 0b000001010111 }, - Code { 48, 12, 0b000001100100 }, - Code { 49, 12, 0b000001100101 }, - Code { 50, 12, 0b000001010010 }, - Code { 51, 12, 0b000001010011 }, - Code { 52, 12, 0b000000100100 }, - Code { 53, 12, 0b000000110111 }, - Code { 54, 12, 0b000000111000 }, - Code { 55, 12, 0b000000100111 }, - Code { 56, 12, 0b000000101000 }, - Code { 57, 12, 0b000001011000 }, - Code { 58, 12, 0b000001011001 }, - Code { 59, 12, 0b000000101011 }, - Code { 60, 12, 0b000000101100 }, - Code { 61, 12, 0b000001011010 }, - Code { 62, 12, 0b000001100110 }, - Code { 63, 12, 0b000001100111 }, -}; - -// Table 3a/T.4 – Make-up codes -constexpr Array white_make_up_codes = { - Code { 64, 5, 0b11011 }, - Code { 128, 5, 0b10010 }, - Code { 192, 6, 0b010111 }, - Code { 256, 7, 0b0110111 }, - Code { 320, 8, 0b00110110 }, - Code { 384, 8, 0b00110111 }, - Code { 448, 8, 0b01100100 }, - Code { 512, 8, 0b01100101 }, - Code { 576, 8, 0b01101000 }, - Code { 640, 8, 0b01100111 }, - Code { 704, 9, 0b011001100 }, - Code { 768, 9, 0b011001101 }, - Code { 832, 9, 0b011010010 }, - Code { 896, 9, 0b011010011 }, - Code { 960, 9, 0b011010100 }, - Code { 1024, 9, 0b011010101 }, - Code { 1088, 9, 0b011010110 }, - Code { 1152, 9, 0b011010111 }, - Code { 1216, 9, 0b011011000 }, - Code { 1280, 9, 0b011011001 }, - Code { 1344, 9, 0b011011010 }, - Code { 1408, 9, 0b011011011 }, - Code { 1472, 9, 0b010011000 }, - Code { 1536, 9, 0b010011001 }, - Code { 1600, 9, 0b010011010 }, - Code { 1664, 6, 0b011000 }, - Code { 1728, 9, 0b010011011 }, - Code { 0xE0L, 12, 0b000000000001 }, -}; - -// Table 3a/T.4 – Make-up codes -constexpr Array black_make_up_codes = { - Code { 64, 10, 0b0000001111 }, - Code { 128, 12, 0b000011001000 }, - Code { 192, 12, 0b000011001001 }, - Code { 256, 12, 0b000001011011 }, - Code { 320, 12, 0b000000110011 }, - Code { 384, 12, 0b000000110100 }, - Code { 448, 12, 0b000000110101 }, - Code { 512, 13, 0b0000001101100 }, - Code { 576, 13, 0b0000001101101 }, - Code { 640, 13, 0b0000001001010 }, - Code { 704, 13, 0b0000001001011 }, - Code { 768, 13, 0b0000001001100 }, - Code { 832, 13, 0b0000001001101 }, - Code { 896, 13, 0b0000001110010 }, - Code { 960, 13, 0b0000001110011 }, - Code { 1024, 13, 0b0000001110100 }, - Code { 1088, 13, 0b0000001110101 }, - Code { 1152, 13, 0b0000001110110 }, - Code { 1216, 13, 0b0000001110111 }, - Code { 1280, 13, 0b0000001010010 }, - Code { 1344, 13, 0b0000001010011 }, - Code { 1408, 13, 0b0000001010100 }, - Code { 1472, 13, 0b0000001010101 }, - Code { 1536, 13, 0b0000001011010 }, - Code { 1600, 13, 0b0000001011011 }, - Code { 1664, 13, 0b0000001100100 }, - Code { 1728, 13, 0b0000001100101 }, - Code { 0xE0L, 11, 0b00000000000 }, -}; - -// Table 3b/T.4 – Make-up codes -constexpr Array common_make_up_codes = { - Code { 1792, 11, 0b00000001000 }, - Code { 1856, 11, 0b00000001100 }, - Code { 1920, 11, 0b00000001101 }, - Code { 1984, 12, 0b000000010010 }, - Code { 2048, 12, 0b000000010011 }, - Code { 2112, 12, 0b000000010100 }, - Code { 2176, 12, 0b000000010101 }, - Code { 2240, 12, 0b000000010110 }, - Code { 2304, 12, 0b000000010111 }, - Code { 2368, 12, 0b000000011100 }, - Code { 2432, 12, 0b000000011101 }, - Code { 2496, 12, 0b000000011110 }, - Code { 2560, 12, 0b000000011111 }, -}; - -template -Optional get_code_from_table(Array const& array, u16 code_word, u8 code_size) -{ - for (auto const& code : array) { - if (code.code_length == code_size && code.code == code_word) - return code; - } - return OptionalNone {}; -} - -Optional get_markup_code(Color color, u16 code_word, u8 code_size) -{ - if (auto maybe_value = get_code_from_table(common_make_up_codes, code_word, code_size); maybe_value.has_value()) - return maybe_value.value(); - - if (color == Color::NamedColor::White) - return get_code_from_table(white_make_up_codes, code_word, code_size); - return get_code_from_table(black_make_up_codes, code_word, code_size); -} - -Optional get_terminal_code(Color color, u16 code_word, u8 code_size) -{ - if (color == Color::NamedColor::White) - return get_code_from_table(white_terminating_codes, code_word, code_size); - return get_code_from_table(black_terminating_codes, code_word, code_size); -} - -constexpr auto const ccitt_white = Color::NamedColor::White; -constexpr auto const ccitt_black = Color::NamedColor::Black; - -Color invert(Color current_color) -{ - return current_color == ccitt_white ? ccitt_black : ccitt_white; -} - -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 {}; - u32 run_length {}; - while (size < 14) { - potential_code <<= 1; - potential_code |= TRY(input_bit_stream.read_bit()); - size++; - - if (auto const maybe_markup = get_markup_code(current_color, potential_code, size); maybe_markup.has_value()) { - run_length += maybe_markup->run_length; - // OK, let's reset the loop to read a terminal code now - size = 0; - 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; - } - } - - if (size == 14) - return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid CCITT code"); - - if (column + run_length > image_width) - return Error::from_string_literal("TIFFImageDecoderPlugin: CCITT codes encode for more than a line"); - - return run_length; -} - -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 }; - u32 run_length = 0; - u32 column = 0; - - while (column < image_width) { - if (run_length > 0) { - run_length--; - TRY(decoded_bits.write_bits(current_color == ccitt_white ? 0u : 1u, 1)); - - ++column; - continue; - } - - current_color = invert(current_color); - - run_length += TRY(read_run_length(input_bit_stream, reference_line, current_color, image_width, column)); - } - - TRY(decoded_bits.align_to_byte_boundary()); - - return reference_line; -} - -static ErrorOr read_eol(BigEndianInputBitStream& bit_stream, Group3Options::UseFillBits use_fill_bits) -{ - constexpr u16 EOL = 0b0000'0000'0001; - - if (use_fill_bits == Group3Options::UseFillBits::Yes) { - // TIFF specification, description of the T4Options tag: - // "Fill bits have been added as necessary before EOL codes such that - // EOL always ends on a byte boundary, thus ensuring an EOL-sequence of 1 byte - // preceded by a zero nibble: xxxx-0000 0000-0001." - auto const to_skip = (12 + bit_stream.bits_until_next_byte_boundary()) % 8; - TRY(bit_stream.read_bits(to_skip)); - } - - auto const read = TRY(bit_stream.read_bits(12)); - if (read != EOL) - return Error::from_string_literal("CCITTDecoder: Invalid EndOfLine code"); - - 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 }, -}); - -using InvalidResult = u8; - -ErrorOr> read_mode(BigEndianInputBitStream& input_bit_stream) -{ - u8 size {}; - u8 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 Variant(InvalidResult { potential_code }); -} - -enum class Search : u8 { - B1, - B2, -}; - -struct CCITTStatus { - ReferenceLine current_line {}; - bool has_reached_eol { false }; -}; - -ErrorOr decode_single_ccitt_2d_line(BigEndianInputBitStream& input_bit_stream, BigEndianOutputBitStream& decoded_bits, ReferenceLine&& reference_line, u32 image_width) -{ - CCITTStatus status {}; - Color current_color { ccitt_white }; - u32 column {}; - u32 remainder_from_pass_mode {}; - - auto const next_change_on_reference_line = [&]() -> ErrorOr { - // 4.2.1.3.1 Definition of changing picture elements - Optional next_change {}; // This is referred to as b1 in the spec. - u32 offset {}; - while (!next_change.has_value()) { - if (reference_line.is_empty() || reference_line.size() <= offset) - return Error::from_string_literal("CCITTDecoder: Corrupted stream"); - auto const change = reference_line[0 + offset]; - // 4.2.1.3.4 Processing the first and last picture elements in a line - // "The first starting picture element a0 on each coding line is imaginarily set at a position just - // before the first picture element, and is regarded as a white picture element." - // To emulate this behavior we check for column == 0 here. - if (change.column <= column && column != 0) { - reference_line.take_first(); - continue; - } - if (change.color != current_color || change.column == image_width) - next_change = change; - else - offset++; - } - return *next_change; - }; - - auto const encode_for = [&](Change change, i8 offset = 0) -> ErrorOr { - auto const to_encode = remainder_from_pass_mode + change.column - column + offset; - for (u32 i {}; i < to_encode; ++i) - TRY(decoded_bits.write_bits(current_color == ccitt_white ? 0u : 1u, 1)); - - column = change.column + offset; - current_color = invert(current_color); - remainder_from_pass_mode = 0; - - TRY(status.current_line.try_empend(current_color, column)); - return {}; - }; - - while (column < image_width) { - auto const maybe_mode = TRY(read_mode(input_bit_stream)); - - if (maybe_mode.has()) { - auto const partially_read_eol = maybe_mode.get(); - - if (partially_read_eol != 0) - return Error::from_string_literal("CCITTDecoder: Unable to find the correct mode"); - - auto const remaining_eol = TRY(input_bit_stream.read_bits(5)); - if (remaining_eol != 1) - return Error::from_string_literal("CCITTDecoder: Unable to find the correct mode"); - - // We reached EOL - status.has_reached_eol = true; - break; - } - - // Behavior are described here 4.2.1.3.2 Coding modes. - switch (maybe_mode.get().mode) { - case Mode::Pass: { - auto const column_before = column; - // We search for b1. - auto change = TRY(next_change_on_reference_line()); - current_color = change.color; - column = change.column; - - // We search for b2, which is the same as searching for b1 after updating the state. - change = TRY(next_change_on_reference_line()); - current_color = change.color; - column = change.column; - - remainder_from_pass_mode += column - column_before; - 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"); - } - } - - TRY(decoded_bits.align_to_byte_boundary()); - - return status; -} - -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 - reference_line = TRY(decode_single_ccitt_2d_line(input_bit_stream, decoded_bits, move(reference_line), image_width)).current_line; - } - - return {}; -} - -} - -ErrorOr decode_ccitt_rle(ReadonlyBytes bytes, u32 image_width, u32 image_height) -{ - auto strip_stream = make(bytes); - auto bit_stream = make(MaybeOwned(*strip_stream)); - - // Note: We put image_height extra-space to handle at most one alignment to byte boundary per line. - ByteBuffer decoded_bytes = TRY(ByteBuffer::create_zeroed(ceil_div(image_width * image_height, 8) + image_height)); - auto output_stream = make(decoded_bytes.bytes()); - auto decoded_bits = make(MaybeOwned(*output_stream)); - - while (!bit_stream->is_eof()) { - TRY(decode_single_ccitt3_1d_line(*bit_stream, *decoded_bits, image_width)); - - bit_stream->align_to_byte_boundary(); - } - - 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)); - - // Note: We put image_height extra-space to handle at most one alignment to byte boundary per line. - ByteBuffer decoded_bytes = TRY(ByteBuffer::create_zeroed(ceil_div(image_width * image_height, 8) + image_height)); - 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) { - if (options.require_end_of_line == Group3Options::RequireEndOfLine::Yes) - TRY(read_eol(*bit_stream, options.use_fill_bits)); - TRY(decode_single_ccitt3_1d_line(*bit_stream, *decoded_bits, image_width)); - if (options.encoded_byte_aligned == Group3Options::EncodedByteAligned::Yes) - bit_stream->align_to_byte_boundary(); - } - - return decoded_bytes; - } - - if (options.require_end_of_line == Group3Options::RequireEndOfLine::No || options.encoded_byte_aligned == Group3Options::EncodedByteAligned::Yes) - return Error::from_string_literal("CCITTDecoder: Unsupported option for CCITT3 2D decoding"); - - TRY(decode_single_ccitt3_2d_block(*bit_stream, *decoded_bits, image_width, image_height, options.use_fill_bits)); - return decoded_bytes; -} - -ErrorOr decode_ccitt_group4(ReadonlyBytes bytes, u32 image_width, u32 image_height) -{ - auto strip_stream = make(bytes); - auto bit_stream = make(MaybeOwned(*strip_stream)); - - auto output_stream = make(); - auto decoded_bits = make(MaybeOwned(*output_stream)); - - // T.6 2.2.1 Principle of the coding scheme - // The reference line for the first coding line in a page is an imaginary white line. - CCITTStatus status; - TRY(status.current_line.try_empend(ccitt_black, image_width)); - - u32 i {}; - while (!status.has_reached_eol && (image_height == 0 || i < image_height)) { - status = TRY(decode_single_ccitt_2d_line(*bit_stream, *decoded_bits, move(status.current_line), image_width)); - ++i; - } - - return output_stream->read_until_eof(); -} - -} diff --git a/Libraries/LibGfx/ImageFormats/CCITTDecoder.h b/Libraries/LibGfx/ImageFormats/CCITTDecoder.h deleted file mode 100644 index 6b7f756e55..0000000000 --- a/Libraries/LibGfx/ImageFormats/CCITTDecoder.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2023, Lucas Chollet - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Gfx::CCITT { - -// You can find a great overview of CCITT compression schemes here: -// https://www.fileformat.info/mirror/egff/ch09_05.htm - -// The CCITT3 specification is accessible at this page: -// https://www.itu.int/rec/T-REC-T.4/en - -// And CCITT4's specification is available here: -// https://www.itu.int/rec/T-REC-T.6/en - -// The unidimensional scheme is originally described in: -// 4.1 One-dimensional coding scheme -// However, this function implements the TIFF variant (see TIFFLoader.h for a spec link), -// differences are detailed in section: -// Section 10: Modified Huffman Compression -ErrorOr decode_ccitt_rle(ReadonlyBytes bytes, u32 image_width, u32 image_height); - -// While this is named for a CCITT context, this struct holds data like TIFF's T4Options tag -struct Group3Options { - enum class Mode : u8 { - OneDimension, - TwoDimensions, - }; - - enum class Compression : u8 { - Uncompressed, - Compressed, - }; - - enum class UseFillBits : u8 { - No = 0, - Yes = 1, - }; - - // Addition from the PDF specification - enum class RequireEndOfLine : u8 { - No = 0, - Yes = 1, - }; - - enum class EncodedByteAligned : u8 { - No = 0, - Yes = 1, - }; - - Mode dimensions = Mode::OneDimension; - Compression compression = Compression::Compressed; - UseFillBits use_fill_bits = UseFillBits::No; - - // Default values are set to be compatible with the CCITT specification - RequireEndOfLine require_end_of_line = RequireEndOfLine::Yes; - EncodedByteAligned encoded_byte_aligned = EncodedByteAligned::No; -}; - -ErrorOr decode_ccitt_group3(ReadonlyBytes bytes, u32 image_width, u32 image_height, Group3Options const& options); - -ErrorOr decode_ccitt_group4(ReadonlyBytes bytes, u32 image_width, u32 image_height); - -} diff --git a/Libraries/LibGfx/ImageFormats/ImageDecoder.cpp b/Libraries/LibGfx/ImageFormats/ImageDecoder.cpp index d7c5b7a0de..fbae5ff9a5 100644 --- a/Libraries/LibGfx/ImageFormats/ImageDecoder.cpp +++ b/Libraries/LibGfx/ImageFormats/ImageDecoder.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include namespace Gfx { @@ -31,7 +30,6 @@ static ErrorOr> probe_and_sniff_for_appropriate_plugi { JPEGImageDecoderPlugin::sniff, JPEGImageDecoderPlugin::create }, { JPEGXLImageDecoderPlugin::sniff, JPEGXLImageDecoderPlugin::create }, { PNGImageDecoderPlugin::sniff, PNGImageDecoderPlugin::create }, - { TIFFImageDecoderPlugin::sniff, TIFFImageDecoderPlugin::create }, { WebPImageDecoderPlugin::sniff, WebPImageDecoderPlugin::create }, { AVIFImageDecoderPlugin::sniff, AVIFImageDecoderPlugin::create } }; diff --git a/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp b/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp deleted file mode 100644 index 4a787e3cb1..0000000000 --- a/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp +++ /dev/null @@ -1,802 +0,0 @@ -/* - * Copyright (c) 2023, Lucas Chollet - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "TIFFLoader.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Gfx { - -namespace { - -CCITT::Group3Options parse_t4_options(u32 bit_field) -{ - // Section 11: CCITT Bilevel Encodings - CCITT::Group3Options options {}; - - if (bit_field & 0b001) - options.dimensions = CCITT::Group3Options::Mode::TwoDimensions; - if (bit_field & 0b010) - options.compression = CCITT::Group3Options::Compression::Uncompressed; - if (bit_field & 0b100) - options.use_fill_bits = CCITT::Group3Options::UseFillBits::Yes; - - return options; -} - -bool is_bilevel(TIFF::PhotometricInterpretation interpretation) -{ - return interpretation == TIFF::PhotometricInterpretation::WhiteIsZero || interpretation == TIFF::PhotometricInterpretation::BlackIsZero; -} - -} - -namespace TIFF { - -class TIFFLoadingContext { -public: - enum class State { - NotDecoded = 0, - Error, - HeaderDecoded, - FrameDecoded, - }; - - TIFFLoadingContext(NonnullOwnPtr stream) - : m_stream(move(stream)) - { - } - - ErrorOr decode_image_header() - { - TRY(read_image_file_header()); - TRY(read_next_image_file_directory()); - - m_state = State::HeaderDecoded; - return {}; - } - - ErrorOr ensure_conditional_tags_are_correct() const - { - if (m_metadata.photometric_interpretation() == PhotometricInterpretation::RGBPalette && !m_metadata.color_map().has_value()) - return Error::from_string_literal("TIFFImageDecoderPlugin: RGBPalette image doesn't contain a color map"); - - if (m_metadata.tile_width() == 0u || m_metadata.tile_length() == 0u) - return Error::from_string_literal("TIFFImageDecoderPlugin: Null value in tile's dimensions"); - - return {}; - } - - Optional> segment_offsets() const - { - return m_metadata.strip_offsets().has_value() ? m_metadata.strip_offsets() : m_metadata.tile_offsets(); - } - - Optional> segment_byte_counts() const - { - return m_metadata.strip_byte_counts().has_value() ? m_metadata.strip_byte_counts() : m_metadata.tile_byte_counts(); - } - - bool is_tiled() const - { - return m_metadata.tile_width().has_value() && m_metadata.tile_length().has_value(); - } - - ErrorOr ensure_baseline_tags_are_correct() const - { - if (!segment_offsets().has_value()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Missing Offsets tag"); - - if (!segment_byte_counts().has_value()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Missing ByteCounts tag"); - - if (segment_offsets()->size() != segment_byte_counts()->size()) - return Error::from_string_literal("TIFFImageDecoderPlugin: StripsOffset and StripByteCount have different sizes"); - - if (!m_metadata.rows_per_strip().has_value() && segment_byte_counts()->size() != 1 && !is_tiled()) - return Error::from_string_literal("TIFFImageDecoderPlugin: RowsPerStrip is not provided and impossible to deduce"); - - if (!is_bilevel(*m_metadata.photometric_interpretation())) { - if (!m_metadata.bits_per_sample().has_value()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Tag BitsPerSample is missing"); - - if (!m_metadata.samples_per_pixel().has_value()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Tag SamplesPerPixel is missing"); - - if (any_of(*m_metadata.bits_per_sample(), [](auto bit_depth) { return bit_depth == 0 || bit_depth > 32; })) - return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid value in BitsPerSample"); - - if (m_metadata.bits_per_sample()->size() != m_metadata.samples_per_pixel()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid number of values in BitsPerSample"); - - if (*m_metadata.samples_per_pixel() < samples_for_photometric_interpretation()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Not enough values in BitsPerSample for given PhotometricInterpretation"); - } - - return {}; - } - - void cache_values() - { - if (m_metadata.photometric_interpretation().has_value()) - m_photometric_interpretation = m_metadata.photometric_interpretation().value(); - if (m_metadata.bits_per_sample().has_value()) - m_bits_per_sample = m_metadata.bits_per_sample().value(); - else if (is_bilevel(m_photometric_interpretation)) - m_bits_per_sample.append(1); - if (m_metadata.image_width().has_value()) - m_image_width = m_metadata.image_width().value(); - if (m_metadata.predictor().has_value()) - m_predictor = m_metadata.predictor().value(); - m_alpha_channel_index = alpha_channel_index(); - } - - ErrorOr decode_frame() - { - TRY(ensure_baseline_tags_are_present(m_metadata)); - TRY(ensure_baseline_tags_are_correct()); - TRY(ensure_conditional_tags_are_correct()); - cache_values(); - auto maybe_error = decode_frame_impl(); - - if (maybe_error.is_error()) { - m_state = State::Error; - return maybe_error.release_error(); - } - - return {}; - } - - IntSize size() const - { - return ExifOrientedBitmap::oriented_size({ *m_metadata.image_width(), *m_metadata.image_length() }, *m_metadata.orientation()); - } - - ExifMetadata const& metadata() const - { - return m_metadata; - } - - State state() const - { - return m_state; - } - - RefPtr cmyk_bitmap() const - { - return m_cmyk_bitmap; - } - - RefPtr bitmap() const - { - return m_bitmap; - } - -private: - enum class ByteOrder { - LittleEndian, - BigEndian, - }; - - static ErrorOr read_component(BigEndianInputBitStream& stream, u8 bits) - { - // FIXME: This function truncates everything to 8-bits - auto const value = TRY(stream.read_bits(bits)); - - if (bits > 8) - return value >> (bits - 8); - return NumericLimits::max() * value / ((1 << bits) - 1); - } - - u8 samples_for_photometric_interpretation() const - { - switch (m_photometric_interpretation) { - case PhotometricInterpretation::WhiteIsZero: - case PhotometricInterpretation::BlackIsZero: - case PhotometricInterpretation::RGBPalette: - return 1; - case PhotometricInterpretation::RGB: - return 3; - case PhotometricInterpretation::CMYK: - return 4; - default: - TODO(); - } - } - - Optional alpha_channel_index() const - { - if (m_metadata.extra_samples().has_value()) { - auto const extra_samples = m_metadata.extra_samples().value(); - for (u8 i = 0; i < extra_samples.size(); ++i) { - if (extra_samples[i] == ExtraSample::UnassociatedAlpha) - return i + samples_for_photometric_interpretation(); - } - } - return OptionalNone {}; - } - - ErrorOr manage_extra_channels(BigEndianInputBitStream& stream) const - { - // Section 7: Additional Baseline TIFF Requirements - // Some TIFF files may have more components per pixel than you think. A Baseline TIFF reader must skip over - // them gracefully, using the values of the SamplesPerPixel and BitsPerSample fields. - - // Both unknown and alpha channels are considered as extra channels, so let's iterate over - // them, conserve the alpha value (if any) and discard everything else. - - auto const number_base_channels = samples_for_photometric_interpretation(); - - Optional alpha {}; - - for (u8 i = number_base_channels; i < m_bits_per_sample.size(); ++i) { - if (m_alpha_channel_index == i) - alpha = TRY(read_component(stream, m_bits_per_sample[i])); - else - TRY(read_component(stream, m_bits_per_sample[i])); - } - - return alpha.value_or(NumericLimits::max()); - } - - ErrorOr read_color(BigEndianInputBitStream& stream) - { - if (m_photometric_interpretation == PhotometricInterpretation::RGB) { - auto const first_component = TRY(read_component(stream, m_bits_per_sample[0])); - auto const second_component = TRY(read_component(stream, m_bits_per_sample[1])); - auto const third_component = TRY(read_component(stream, m_bits_per_sample[2])); - - auto const alpha = TRY(manage_extra_channels(stream)); - return Color(first_component, second_component, third_component, alpha); - } - - if (m_photometric_interpretation == PhotometricInterpretation::RGBPalette) { - auto const index = TRY(stream.read_bits(m_bits_per_sample[0])); - auto const alpha = TRY(manage_extra_channels(stream)); - - // SamplesPerPixel == 1 is a requirement for RGBPalette - // From description of PhotometricInterpretation in Section 8: Baseline Field Reference Guide - // "In a TIFF ColorMap, all the Red values come first, followed by the Green values, - // then the Blue values." - u64 const size = 1ul << m_bits_per_sample[0]; - u64 const red_offset = 0 * size; - u64 const green_offset = 1 * size; - u64 const blue_offset = 2 * size; - - auto const color_map = *m_metadata.color_map(); - - if (blue_offset + index >= color_map.size()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Color index is out of range"); - - // FIXME: ColorMap's values are always 16-bits, stop truncating them when we support 16 bits bitmaps - return Color( - color_map[red_offset + index] >> 8, - color_map[green_offset + index] >> 8, - color_map[blue_offset + index] >> 8, - alpha); - } - - if (m_photometric_interpretation == PhotometricInterpretation::WhiteIsZero - || m_photometric_interpretation == PhotometricInterpretation::BlackIsZero) { - auto luminosity = TRY(read_component(stream, m_bits_per_sample[0])); - - if (m_photometric_interpretation == PhotometricInterpretation::WhiteIsZero) - luminosity = ~luminosity; - - auto const alpha = TRY(manage_extra_channels(stream)); - return Color(luminosity, luminosity, luminosity, alpha); - } - - return Error::from_string_literal("Unsupported value for PhotometricInterpretation"); - } - - ErrorOr read_color_cmyk(BigEndianInputBitStream& stream) - { - VERIFY(m_photometric_interpretation == PhotometricInterpretation::CMYK); - - auto const first_component = TRY(read_component(stream, m_bits_per_sample[0])); - auto const second_component = TRY(read_component(stream, m_bits_per_sample[1])); - auto const third_component = TRY(read_component(stream, m_bits_per_sample[2])); - auto const fourth_component = TRY(read_component(stream, m_bits_per_sample[3])); - - // FIXME: We probably won't encounter CMYK images with an alpha channel, but if - // we do: the first step to support them is not dropping the value here! - [[maybe_unused]] auto const alpha = TRY(manage_extra_channels(stream)); - return CMYK { first_component, second_component, third_component, fourth_component }; - } - - template, u32, IntSize> SegmentDecoder> - ErrorOr loop_over_pixels(SegmentDecoder&& segment_decoder) - { - auto const offsets = *segment_offsets(); - auto const byte_counts = *segment_byte_counts(); - - auto const segment_length = m_metadata.tile_length().value_or(m_metadata.rows_per_strip().value_or(*m_metadata.image_length())); - auto const segment_width = m_metadata.tile_width().value_or(m_image_width); - auto const segment_per_rows = m_metadata.tile_width().map([&](u32 w) { return ceil_div(m_image_width, w); }).value_or(1); - - Variant oriented_bitmap = TRY(([&]() -> ErrorOr> { - if (m_photometric_interpretation == PhotometricInterpretation::CMYK) - return ExifOrientedCMYKBitmap::create(*metadata().orientation(), { m_image_width, *metadata().image_length() }); - return ExifOrientedBitmap::create(*metadata().orientation(), { m_image_width, *metadata().image_length() }, BitmapFormat::BGRA8888); - }())); - - for (u32 segment_index = 0; segment_index < offsets.size(); ++segment_index) { - TRY(m_stream->seek(offsets[segment_index])); - - auto const rows_in_segment = segment_index < offsets.size() - 1 ? segment_length : *m_metadata.image_length() - segment_length * segment_index; - auto const decoded_bytes = TRY(segment_decoder(byte_counts[segment_index], { segment_width, rows_in_segment })); - auto decoded_segment = make(decoded_bytes); - auto decoded_stream = make(move(decoded_segment)); - - for (u32 row = 0; row < segment_length; row++) { - auto const image_row = row + segment_length * (segment_index / segment_per_rows); - if (image_row >= *m_metadata.image_length()) - break; - - Optional last_color {}; - - for (u32 column = 0; column < segment_width; ++column) { - // If image_length % segment_length != 0, the last tile will be padded. - // This variable helps us to skip these last columns. Note that we still - // need to read the sample from the stream. - auto const image_column = column + segment_width * (segment_index % segment_per_rows); - - if (m_photometric_interpretation == PhotometricInterpretation::CMYK) { - auto const cmyk = TRY(read_color_cmyk(*decoded_stream)); - if (image_column >= m_image_width) - continue; - oriented_bitmap.get().set_pixel(image_column, image_row, cmyk); - } else { - auto color = TRY(read_color(*decoded_stream)); - - // FIXME: We should do the differencing at the byte-stream level, that would make it - // compatible with both LibPDF and all color formats. - if (m_predictor == Predictor::HorizontalDifferencing && last_color.has_value()) { - color.set_red(last_color->red() + color.red()); - color.set_green(last_color->green() + color.green()); - color.set_blue(last_color->blue() + color.blue()); - if (m_alpha_channel_index.has_value()) - color.set_alpha(last_color->alpha() + color.alpha()); - } - - last_color = color; - if (image_column >= m_image_width) - continue; - oriented_bitmap.get().set_pixel(image_column, image_row, color.value()); - } - } - - decoded_stream->align_to_byte_boundary(); - } - } - - if (m_photometric_interpretation == PhotometricInterpretation::CMYK) - m_cmyk_bitmap = oriented_bitmap.get().bitmap(); - else - m_bitmap = oriented_bitmap.get().bitmap(); - - return {}; - } - - ErrorOr ensure_tags_are_correct_for_ccitt() const - { - // Section 8: Baseline Field Reference Guide - // BitsPerSample must be 1, since this type of compression is defined only for bilevel images. - if (m_bits_per_sample.size() > 1) - return Error::from_string_literal("TIFFImageDecoderPlugin: CCITT image with BitsPerSample greater than one"); - if (!is_bilevel(*m_metadata.photometric_interpretation())) - return Error::from_string_literal("TIFFImageDecoderPlugin: CCITT compression is used on a non bilevel image"); - - return {}; - } - - ErrorOr read_bytes_considering_fill_order(u32 bytes_to_read) const - { - auto const reverse_byte = [](u8 b) { - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - return b; - }; - - auto const bytes = TRY(m_stream->read_in_place(bytes_to_read)); - auto copy = TRY(ByteBuffer::copy(bytes)); - if (m_metadata.fill_order() == FillOrder::RightToLeft) { - for (auto& byte : copy.bytes()) - byte = reverse_byte(byte); - } - - return copy; - } - - ErrorOr decode_frame_impl() - { - switch (*m_metadata.compression()) { - case Compression::NoCompression: { - auto identity = [&](u32 num_bytes, IntSize) { - return m_stream->read_in_place(num_bytes); - }; - - TRY(loop_over_pixels(move(identity))); - break; - } - case Compression::CCITTRLE: { - TRY(ensure_tags_are_correct_for_ccitt()); - - ByteBuffer decoded_bytes {}; - auto decode_ccitt_rle_segment = [&](u32 num_bytes, IntSize segment_size) -> ErrorOr { - auto const encoded_bytes = TRY(read_bytes_considering_fill_order(num_bytes)); - decoded_bytes = TRY(CCITT::decode_ccitt_rle(encoded_bytes, segment_size.width(), segment_size.height())); - return decoded_bytes; - }; - - TRY(loop_over_pixels(move(decode_ccitt_rle_segment))); - 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_segment = [&](u32 num_bytes, IntSize segment_size) -> ErrorOr { - auto const encoded_bytes = TRY(read_bytes_considering_fill_order(num_bytes)); - decoded_bytes = TRY(CCITT::decode_ccitt_group3(encoded_bytes, segment_size.width(), segment_size.height(), parameters)); - return decoded_bytes; - }; - - TRY(loop_over_pixels(move(decode_group3_segment))); - break; - } - case Compression::Group4Fax: { - TRY(ensure_tags_are_correct_for_ccitt()); - - // FIXME: We need to parse T6 options - ByteBuffer decoded_bytes {}; - auto decode_group3_segment = [&](u32 num_bytes, IntSize segment_size) -> ErrorOr { - auto const encoded_bytes = TRY(read_bytes_considering_fill_order(num_bytes)); - decoded_bytes = TRY(CCITT::decode_ccitt_group4(encoded_bytes, segment_size.width(), segment_size.height())); - return decoded_bytes; - }; - - TRY(loop_over_pixels(move(decode_group3_segment))); - break; - } - case Compression::LZW: { - ByteBuffer decoded_bytes {}; - auto decode_lzw_segment = [&](u32 num_bytes, IntSize) -> ErrorOr { - auto const encoded_bytes = TRY(m_stream->read_in_place(num_bytes)); - - if (encoded_bytes.is_empty()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Unable to read from empty LZW segment"); - - // Note: AFAIK, there are two common ways to use LZW compression: - // - With a LittleEndian stream and no Early-Change, this is used in the GIF format - // - With a BigEndian stream and an EarlyChange of 1, this is used in the PDF format - // The fun begins when they decided to change from the former to the latter when moving - // from TIFF 5.0 to 6.0, and without including a way for files to be identified. - // Fortunately, as the first byte of a LZW stream is a constant we can guess the endianness - // and deduce the version from it. The first code is 0x100 (9-bits). - if (encoded_bytes[0] == 0x00) - decoded_bytes = TRY(Compress::LzwDecompressor::decompress_all(encoded_bytes, 8, 0)); - else - decoded_bytes = TRY(Compress::LzwDecompressor::decompress_all(encoded_bytes, 8, -1)); - - return decoded_bytes; - }; - - TRY(loop_over_pixels(move(decode_lzw_segment))); - break; - } - case Compression::AdobeDeflate: - case Compression::PixarDeflate: { - // This is an extension from the Technical Notes from 2002: - // https://web.archive.org/web/20160305055905/http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf - ByteBuffer decoded_bytes {}; - auto decode_zlib = [&](u32 num_bytes, IntSize) -> ErrorOr { - auto stream = make(MaybeOwned(*m_stream), num_bytes); - auto decompressed_stream = TRY(Compress::ZlibDecompressor::create(move(stream))); - decoded_bytes = TRY(decompressed_stream->read_until_eof(4096)); - return decoded_bytes; - }; - - TRY(loop_over_pixels(move(decode_zlib))); - break; - } - case Compression::PackBits: { - // Section 9: PackBits Compression - ByteBuffer decoded_bytes {}; - - auto decode_packbits_segment = [&](u32 num_bytes, IntSize) -> ErrorOr { - auto const encoded_bytes = TRY(m_stream->read_in_place(num_bytes)); - decoded_bytes = TRY(Compress::PackBits::decode_all(encoded_bytes)); - return decoded_bytes; - }; - - TRY(loop_over_pixels(move(decode_packbits_segment))); - break; - } - default: - return Error::from_string_literal("This compression type is not supported yet :^)"); - } - - return {}; - } - - template - ErrorOr read_value() - { - if (m_byte_order == ByteOrder::LittleEndian) - return TRY(m_stream->read_value>()); - if (m_byte_order == ByteOrder::BigEndian) - return TRY(m_stream->read_value>()); - VERIFY_NOT_REACHED(); - } - - ErrorOr set_next_ifd(u32 ifd_offset) - { - if (ifd_offset != 0) { - if (ifd_offset < TRY(m_stream->tell())) - return Error::from_string_literal("TIFFImageDecoderPlugin: Can not accept an IFD pointing to previous data"); - - m_next_ifd = Optional { ifd_offset }; - } else { - m_next_ifd = OptionalNone {}; - } - return {}; - } - - ErrorOr read_next_idf_offset() - { - auto const next_block_position = TRY(read_value()); - TRY(set_next_ifd(next_block_position)); - - return {}; - } - - ErrorOr read_image_file_header() - { - // Section 2: TIFF Structure - Image File Header - - auto const byte_order = TRY(m_stream->read_value()); - - switch (byte_order) { - case 0x4949: - m_byte_order = ByteOrder::LittleEndian; - break; - case 0x4D4D: - m_byte_order = ByteOrder::BigEndian; - break; - default: - return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid byte order"); - } - - auto const magic_number = TRY(read_value()); - - if (magic_number != 42) - return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid magic number"); - - TRY(read_next_idf_offset()); - - return {}; - } - - ErrorOr read_next_image_file_directory() - { - // Section 2: TIFF Structure - Image File Directory - - if (!m_next_ifd.has_value()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Missing an Image File Directory"); - - dbgln_if(TIFF_DEBUG, "Reading image file directory at offset {}", m_next_ifd); - - TRY(m_stream->seek(m_next_ifd.value())); - - auto const number_of_field = TRY(read_value()); - auto next_tag_offset = TRY(m_stream->tell()); - - for (u16 i = 0; i < number_of_field; ++i) { - if (auto maybe_error = read_tag(); maybe_error.is_error() && TIFF_DEBUG) - dbgln("Unable to decode tag {}/{}", i + 1, number_of_field); - - // Section 2: TIFF Structure - // IFD Entry - // Size of tag(u16) + type(u16) + count(u32) + value_or_offset(u32) = 12 - next_tag_offset += 12; - TRY(m_stream->seek(next_tag_offset)); - } - - TRY(read_next_idf_offset()); - return {}; - } - - ErrorOr> read_tiff_value(Type type, u32 count, u32 offset) - { - auto const old_offset = TRY(m_stream->tell()); - ScopeGuard reset_offset { [this, old_offset]() { MUST(m_stream->seek(old_offset)); } }; - - TRY(m_stream->seek(offset)); - - if (size_of_type(type) * count > m_stream->remaining()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Tag size claims to be bigger that remaining bytes"); - - auto const read_every_values = [this, count]() -> ErrorOr> { - Vector result {}; - TRY(result.try_ensure_capacity(count)); - if constexpr (IsSpecializationOf) { - for (u32 i = 0; i < count; ++i) - result.empend(T { TRY(read_value()), TRY(read_value()) }); - } else { - for (u32 i = 0; i < count; ++i) - result.empend(typename TypePromoter::Type(TRY(read_value()))); - } - return result; - }; - - switch (type) { - case Type::Byte: - case Type::Undefined: { - Vector result; - auto buffer = TRY(ByteBuffer::create_uninitialized(count)); - TRY(m_stream->read_until_filled(buffer)); - result.append(move(buffer)); - return result; - } - case Type::ASCII: - case Type::UTF8: { - Vector result; - // NOTE: No need to include the null terminator - if (count > 0) - --count; - auto string_data = TRY(ByteBuffer::create_uninitialized(count)); - TRY(m_stream->read_until_filled(string_data)); - result.empend(TRY(String::from_utf8(StringView { string_data.bytes() }))); - return result; - } - case Type::UnsignedShort: - return read_every_values.template operator()(); - case Type::IFD: - case Type::UnsignedLong: - return read_every_values.template operator()(); - case Type::UnsignedRational: - return read_every_values.template operator()>(); - case Type::SignedLong: - return read_every_values.template operator()(); - case Type::SignedRational: - return read_every_values.template operator()>(); - case Type::Float: - return read_every_values.template operator()(); - case Type::Double: - return read_every_values.template operator()(); - default: - VERIFY_NOT_REACHED(); - } - } - - ErrorOr read_tag() - { - auto const tag = TRY(read_value()); - auto const raw_type = TRY(read_value()); - auto const type = TRY(tiff_type_from_u16(raw_type)); - auto const count = TRY(read_value()); - - Checked checked_size = size_of_type(type); - checked_size *= count; - - if (checked_size.has_overflow()) - return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag with too large data"); - - auto tiff_value = TRY(([=, this]() -> ErrorOr> { - if (checked_size.value() <= 4) { - auto value = TRY(read_tiff_value(type, count, TRY(m_stream->tell()))); - TRY(m_stream->discard(4)); - return value; - } - auto const offset = TRY(read_value()); - return read_tiff_value(type, count, offset); - }())); - - auto subifd_handler = [&](u32 ifd_offset) -> ErrorOr { - if (auto result = set_next_ifd(ifd_offset); result.is_error()) { - dbgln("{}", result.error()); - return {}; - } - TRY(read_next_image_file_directory()); - return {}; - }; - - TRY(handle_tag(move(subifd_handler), m_metadata, tag, type, count, move(tiff_value))); - - return {}; - } - - NonnullOwnPtr m_stream; - State m_state {}; - RefPtr m_bitmap {}; - RefPtr m_cmyk_bitmap {}; - - ByteOrder m_byte_order {}; - Optional m_next_ifd {}; - - ExifMetadata m_metadata {}; - - // These are caches for m_metadata values - PhotometricInterpretation m_photometric_interpretation {}; - Vector m_bits_per_sample {}; - u32 m_image_width {}; - Predictor m_predictor {}; - - Optional m_alpha_channel_index {}; -}; - -} - -TIFFImageDecoderPlugin::TIFFImageDecoderPlugin(NonnullOwnPtr stream) -{ - m_context = make(move(stream)); -} - -TIFFImageDecoderPlugin::~TIFFImageDecoderPlugin() = default; - -bool TIFFImageDecoderPlugin::sniff(ReadonlyBytes bytes) -{ - if (bytes.size() < 4) - return false; - bool const valid_little_endian = bytes[0] == 0x49 && bytes[1] == 0x49 && bytes[2] == 0x2A && bytes[3] == 0x00; - bool const valid_big_endian = bytes[0] == 0x4D && bytes[1] == 0x4D && bytes[2] == 0x00 && bytes[3] == 0x2A; - return valid_little_endian || valid_big_endian; -} - -IntSize TIFFImageDecoderPlugin::size() -{ - return m_context->size(); -} - -ErrorOr> TIFFImageDecoderPlugin::create(ReadonlyBytes data) -{ - auto stream = TRY(try_make(data)); - auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) TIFFImageDecoderPlugin(move(stream)))); - TRY(plugin->m_context->decode_image_header()); - return plugin; -} - -ErrorOr TIFFImageDecoderPlugin::frame(size_t index, Optional) -{ - if (index > 0) - return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid frame index"); - - if (m_context->state() == TIFF::TIFFLoadingContext::State::Error) - return Error::from_string_literal("TIFFImageDecoderPlugin: Decoding failed"); - - if (m_context->state() < TIFF::TIFFLoadingContext::State::FrameDecoded) - TRY(m_context->decode_frame()); - - if (m_context->cmyk_bitmap()) - return ImageFrameDescriptor { TRY(m_context->cmyk_bitmap()->to_low_quality_rgb()), 0 }; - - return ImageFrameDescriptor { *m_context->bitmap(), 0 }; -} - -Optional TIFFImageDecoderPlugin::metadata() -{ - return m_context->metadata(); -} - -ErrorOr> TIFFImageDecoderPlugin::icc_data() -{ - return m_context->metadata().icc_profile().map([](auto const& buffer) -> ReadonlyBytes { return buffer.bytes(); }); -} - -} diff --git a/Libraries/LibGfx/ImageFormats/TIFFLoader.h b/Libraries/LibGfx/ImageFormats/TIFFLoader.h deleted file mode 100644 index 0302dd4219..0000000000 --- a/Libraries/LibGfx/ImageFormats/TIFFLoader.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2023, Lucas Chollet - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace Gfx { - -// This is a link to the main TIFF specification from 1992 -// https://www.itu.int/itudoc/itu-t/com16/tiff-fx/docs/tiff6.pdf - -// First TIFF Technical notes from 1995 -// https://www.awaresystems.be/imaging/tiff/specification/TIFFPM6.pdf - -// Second TIFF Technical notes from 1995 -// The document is the second (and current) specification for embedded JPEGs in TIFF images. -// https://www.awaresystems.be/imaging/tiff/specification/TIFFTechNote2.txt - -// This is also compatible with Exif as it is, basically, another set of TIFF tags: -// The spec is named "Exchangeable image file format for digital still cameras: Exif Version 3.0" -// And it can be found at https://www.cipa.jp/e/std/std-sec.html - -namespace TIFF { - -class TIFFLoadingContext; - -} - -class TIFFImageDecoderPlugin : public ImageDecoderPlugin { -public: - static bool sniff(ReadonlyBytes); - static ErrorOr> create(ReadonlyBytes); - - virtual ~TIFFImageDecoderPlugin() override; - - virtual IntSize size() override; - - virtual ErrorOr frame(size_t index, Optional ideal_size = {}) override; - - virtual Optional metadata() override; - virtual ErrorOr> icc_data() override; - -private: - TIFFImageDecoderPlugin(NonnullOwnPtr); - - OwnPtr m_context; -}; - -} diff --git a/Libraries/LibImageDecoders/CMakeLists.txt b/Libraries/LibImageDecoders/CMakeLists.txt index 9da09ebbf6..cd06caf427 100644 --- a/Libraries/LibImageDecoders/CMakeLists.txt +++ b/Libraries/LibImageDecoders/CMakeLists.txt @@ -3,7 +3,6 @@ set(LIBGFX_IMAGE_FORMATS_DIR "${LADYBIRD_SOURCE_DIR}/Libraries/LibGfx/ImageForma set(SOURCES ${LIBGFX_IMAGE_FORMATS_DIR}/AVIFLoader.cpp ${LIBGFX_IMAGE_FORMATS_DIR}/BMPLoader.cpp - ${LIBGFX_IMAGE_FORMATS_DIR}/CCITTDecoder.cpp ${LIBGFX_IMAGE_FORMATS_DIR}/ExifReader.cpp ${LIBGFX_IMAGE_FORMATS_DIR}/GIFLoader.cpp ${LIBGFX_IMAGE_FORMATS_DIR}/ICOLoader.cpp @@ -11,7 +10,6 @@ set(SOURCES ${LIBGFX_IMAGE_FORMATS_DIR}/JPEGLoader.cpp ${LIBGFX_IMAGE_FORMATS_DIR}/JPEGXLLoader.cpp ${LIBGFX_IMAGE_FORMATS_DIR}/PNGLoader.cpp - ${LIBGFX_IMAGE_FORMATS_DIR}/TIFFLoader.cpp ${LIBGFX_IMAGE_FORMATS_DIR}/WebPLoader.cpp ) diff --git a/Libraries/LibWeb/HTML/SupportedImageTypes.cpp b/Libraries/LibWeb/HTML/SupportedImageTypes.cpp index 1ada008799..5a20ee5abc 100644 --- a/Libraries/LibWeb/HTML/SupportedImageTypes.cpp +++ b/Libraries/LibWeb/HTML/SupportedImageTypes.cpp @@ -26,7 +26,6 @@ bool is_supported_image_type(StringView type) || type.equals_ignoring_ascii_case("image/png"sv) || type.equals_ignoring_ascii_case("image/apng"sv) || type.equals_ignoring_ascii_case("image/x-png"sv) - || type.equals_ignoring_ascii_case("image/tiff"sv) || type.equals_ignoring_ascii_case("image/webp"sv) || type.equals_ignoring_ascii_case("image/svg+xml"sv); } diff --git a/Meta/Lagom/Fuzzers/FuzzTIFFLoader.cpp b/Meta/Lagom/Fuzzers/FuzzTIFFLoader.cpp deleted file mode 100644 index 335d9dd400..0000000000 --- a/Meta/Lagom/Fuzzers/FuzzTIFFLoader.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2023, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) -{ - auto decoder_or_error = Gfx::TIFFImageDecoderPlugin::create({ data, size }); - if (decoder_or_error.is_error()) - return 0; - auto decoder = decoder_or_error.release_value(); - (void)decoder->frame(0); - return 0; -} diff --git a/Meta/Lagom/Fuzzers/fuzzers.cmake b/Meta/Lagom/Fuzzers/fuzzers.cmake index a79085c121..5a8f8bc5bc 100644 --- a/Meta/Lagom/Fuzzers/fuzzers.cmake +++ b/Meta/Lagom/Fuzzers/fuzzers.cmake @@ -12,7 +12,6 @@ set(FUZZER_TARGETS RegexECMA262 RSAKeyParsing TextDecoder - TIFFLoader URL WasmParser WOFF @@ -37,7 +36,6 @@ set(FUZZER_DEPENDENCIES_Poly1305 LibCrypto) set(FUZZER_DEPENDENCIES_RegexECMA262 LibRegex) set(FUZZER_DEPENDENCIES_RSAKeyParsing LibCrypto) set(FUZZER_DEPENDENCIES_TextDecoder LibTextCodec) -set(FUZZER_DEPENDENCIES_TIFFLoader LibGfx LibImageDecoders) set(FUZZER_DEPENDENCIES_TTF LibGfx) set(FUZZER_DEPENDENCIES_URL LibURL) set(FUZZER_DEPENDENCIES_WasmParser LibWasm) diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index 95776a6b30..84575ccea5 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -593,276 +592,6 @@ TEST_CASE(test_png_large_dimensions) EXPECT_EQ(plugin_decoder->frame(0).value().image->get_pixel(0, 0), Gfx::Color::NamedColor::Red); } -TEST_CASE(test_tiff_uncompressed) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/uncompressed.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::Red); -} - -TEST_CASE(test_tiff_ccitt_rle) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/ccitt_rle.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) -{ - 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_ccitt3_no_tags) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/ccitt3_no_tags.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, { 6, 4 })); - - EXPECT_EQ(frame.image->get_pixel(0, 0), Gfx::Color::NamedColor::White); - EXPECT_EQ(frame.image->get_pixel(3, 0), Gfx::Color::NamedColor::Black); - EXPECT_EQ(frame.image->get_pixel(2, 2), Gfx::Color::NamedColor::White); - EXPECT_EQ(frame.image->get_pixel(5, 3), Gfx::Color::NamedColor::White); -} - -TEST_CASE(test_tiff_ccitt3_fill) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/ccitt3_1d_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, { 6, 4 })); - - EXPECT_EQ(frame.image->get_pixel(0, 0), Gfx::Color::NamedColor::White); - EXPECT_EQ(frame.image->get_pixel(3, 0), Gfx::Color::NamedColor::Black); - EXPECT_EQ(frame.image->get_pixel(2, 2), Gfx::Color::NamedColor::White); - 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_ccitt4) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/ccitt4.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))); - 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::Red); -} - -TEST_CASE(test_tiff_deflate) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/deflate.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::Red); -} - -TEST_CASE(test_tiff_krita) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/krita.tif"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::Red); -} - -TEST_CASE(test_tiff_orientation) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/orientation.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, { 300, 400 })); - - // Orientation is Rotate90Clockwise - EXPECT_EQ(frame.image->get_pixel(0, 0), Gfx::Color::NamedColor::White); - EXPECT_EQ(frame.image->get_pixel(300 - 75, 60), Gfx::Color::NamedColor::Red); -} - -TEST_CASE(test_tiff_packed_bits) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/packed_bits.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::Red); -} - -TEST_CASE(test_tiff_grayscale) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/grayscale.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(130, 130, 130)); -} - -TEST_CASE(test_tiff_grayscale_alpha) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/grayscale_alpha.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).alpha(), 0); - EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color(130, 130, 130)); -} - -TEST_CASE(test_tiff_rgb_alpha) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/rgb_alpha.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).alpha(), 0); - EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red); -} - -TEST_CASE(test_tiff_palette_alpha) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/rgb_palette_alpha.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).alpha(), 0); - EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red); -} - -TEST_CASE(test_tiff_alpha_predictor) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/alpha_predictor.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).alpha(), 255); - EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red); -} - -TEST_CASE(test_tiff_16_bits) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/16_bits.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::Red); -} - -TEST_CASE(test_tiff_cmyk) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/cmyk.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); - // I stripped the ICC profile from the image, so we can't test for equality with Red here. - EXPECT_NE(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::White); -} - -TEST_CASE(test_tiff_tiled) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/tiled.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::Red); -} - -TEST_CASE(test_tiff_invalid_tag) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/invalid_tag.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, { 10, 10 })); - - EXPECT_EQ(frame.image->get_pixel(0, 0), Gfx::Color::NamedColor::Black); - EXPECT_EQ(frame.image->get_pixel(0, 9), Gfx::Color::NamedColor::White); -} - TEST_CASE(test_webp_simple_lossy) { auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("webp/simple-vp8.webp"sv))); diff --git a/Tests/LibGfx/test-inputs/tiff/16_bits.tiff b/Tests/LibGfx/test-inputs/tiff/16_bits.tiff deleted file mode 100644 index 73b1368703..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/16_bits.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/alpha_predictor.tiff b/Tests/LibGfx/test-inputs/tiff/alpha_predictor.tiff deleted file mode 100644 index b727ba2c30..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/alpha_predictor.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/ccitt3.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt3.tiff deleted file mode 100644 index bbd046f4d8..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/ccitt3.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/ccitt3_1d_fill.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt3_1d_fill.tiff deleted file mode 100644 index e1ce7e062c..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/ccitt3_1d_fill.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/ccitt3_2d.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt3_2d.tiff deleted file mode 100644 index 2187c57225..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/ccitt3_2d.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/ccitt3_2d_fill.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt3_2d_fill.tiff deleted file mode 100644 index 706db669a7..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/ccitt3_2d_fill.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/ccitt3_no_tags.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt3_no_tags.tiff deleted file mode 100644 index e3ba390735..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/ccitt3_no_tags.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/ccitt4.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt4.tiff deleted file mode 100644 index 418b5cc244..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/ccitt4.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/ccitt_rle.tiff b/Tests/LibGfx/test-inputs/tiff/ccitt_rle.tiff deleted file mode 100644 index 40b883343e..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/ccitt_rle.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/cmyk.tiff b/Tests/LibGfx/test-inputs/tiff/cmyk.tiff deleted file mode 100644 index 973db00118..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/cmyk.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/deflate.tiff b/Tests/LibGfx/test-inputs/tiff/deflate.tiff deleted file mode 100644 index 4c471cb9a4..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/deflate.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/grayscale.tiff b/Tests/LibGfx/test-inputs/tiff/grayscale.tiff deleted file mode 100644 index 2a025e3fac..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/grayscale.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/grayscale_alpha.tiff b/Tests/LibGfx/test-inputs/tiff/grayscale_alpha.tiff deleted file mode 100644 index 8d779b8586..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/grayscale_alpha.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/invalid_tag.tiff b/Tests/LibGfx/test-inputs/tiff/invalid_tag.tiff deleted file mode 100644 index 501070fb1a..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/invalid_tag.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/krita.tif b/Tests/LibGfx/test-inputs/tiff/krita.tif deleted file mode 100644 index 182657a0b7..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/krita.tif and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/lzw.tiff b/Tests/LibGfx/test-inputs/tiff/lzw.tiff deleted file mode 100644 index 9040f769a3..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/lzw.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/orientation.tiff b/Tests/LibGfx/test-inputs/tiff/orientation.tiff deleted file mode 100644 index 4ff6e25b02..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/orientation.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/packed_bits.tiff b/Tests/LibGfx/test-inputs/tiff/packed_bits.tiff deleted file mode 100644 index c66c918374..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/packed_bits.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/rgb_alpha.tiff b/Tests/LibGfx/test-inputs/tiff/rgb_alpha.tiff deleted file mode 100644 index 4338e82504..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/rgb_alpha.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/rgb_palette_alpha.tiff b/Tests/LibGfx/test-inputs/tiff/rgb_palette_alpha.tiff deleted file mode 100644 index bbd37e86c0..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/rgb_palette_alpha.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/tiled.tiff b/Tests/LibGfx/test-inputs/tiff/tiled.tiff deleted file mode 100644 index 3a63b6ea05..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/tiled.tiff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/tiff/uncompressed.tiff b/Tests/LibGfx/test-inputs/tiff/uncompressed.tiff deleted file mode 100644 index f16883dd31..0000000000 Binary files a/Tests/LibGfx/test-inputs/tiff/uncompressed.tiff and /dev/null differ diff --git a/Tests/LibWeb/Text/expected/HTML/lzw-image-without-EOI-should-still-load.txt b/Tests/LibWeb/Text/expected/HTML/lzw-image-without-EOI-should-still-load.txt index ae6ab609aa..442eb2d51a 100644 --- a/Tests/LibWeb/Text/expected/HTML/lzw-image-without-EOI-should-still-load.txt +++ b/Tests/LibWeb/Text/expected/HTML/lzw-image-without-EOI-should-still-load.txt @@ -1,2 +1 @@ GIF: loaded, width=1, height=1 -TIFF: loaded, width=1, height=1 diff --git a/Tests/LibWeb/Text/input/HTML/lzw-image-without-EOI-should-still-load.html b/Tests/LibWeb/Text/input/HTML/lzw-image-without-EOI-should-still-load.html index 6d667d0b73..2c7ceeba08 100644 --- a/Tests/LibWeb/Text/input/HTML/lzw-image-without-EOI-should-still-load.html +++ b/Tests/LibWeb/Text/input/HTML/lzw-image-without-EOI-should-still-load.html @@ -4,7 +4,6 @@ asyncTest(async done => { const sources = [ { format: "GIF", src: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" }, - { format: "TIFF", src: "data:image/tiff;base64,SUkqAAwAAACAACAgCQAAAQMAAQAAAAEAAAABAQMAAQAAAAEAAAACAQMAAQAAAAgAAAADAQMAAQAAAAUAAAAGAQMAAQAAAAEAAAARAQQAAQAAAAgAAAAWAQMAAQAAAAEAAAAXAQQAAQAAAAMAAAAcAQMAAQAAAAEAAAAAAAAA" }, ]; for (const { format, src } of sources) { await new Promise(resolve => {