LibGfx+LibWeb: Remove TIFF image decoding
This is no longer widely supported by other engines.
This commit is contained in:
parent
6224045e5f
commit
3cddcc8461
34 changed files with 0 additions and 1863 deletions
|
|
@ -1,638 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/BitStream.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/ImageFormats/CCITTDecoder.h>
|
||||
|
||||
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<typename T, size_t Size>
|
||||
Optional<T> get_code_from_table(Array<T, Size> 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<Code> 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<Code> 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<Change>;
|
||||
|
||||
ErrorOr<u32> read_run_length(BigEndianInputBitStream& input_bit_stream, Optional<ReferenceLine&> 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<ReferenceLine> 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<void> 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<u16>(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<ModeCode>({
|
||||
{ 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<Variant<ModeCode, InvalidResult>> 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<ModeCode, InvalidResult>(InvalidResult { potential_code });
|
||||
}
|
||||
|
||||
enum class Search : u8 {
|
||||
B1,
|
||||
B2,
|
||||
};
|
||||
|
||||
struct CCITTStatus {
|
||||
ReferenceLine current_line {};
|
||||
bool has_reached_eol { false };
|
||||
};
|
||||
|
||||
ErrorOr<CCITTStatus> 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<Change> {
|
||||
// 4.2.1.3.1 Definition of changing picture elements
|
||||
Optional<Change> 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<void> {
|
||||
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<InvalidResult>()) {
|
||||
auto const partially_read_eol = maybe_mode.get<InvalidResult>();
|
||||
|
||||
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<ModeCode>().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<void> 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<ByteBuffer> decode_ccitt_rle(ReadonlyBytes bytes, u32 image_width, u32 image_height)
|
||||
{
|
||||
auto strip_stream = make<FixedMemoryStream>(bytes);
|
||||
auto bit_stream = make<BigEndianInputBitStream>(MaybeOwned<Stream>(*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<FixedMemoryStream>(decoded_bytes.bytes());
|
||||
auto decoded_bits = make<BigEndianOutputBitStream>(MaybeOwned<Stream>(*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<ByteBuffer> decode_ccitt_group3(ReadonlyBytes bytes, u32 image_width, u32 image_height, Group3Options const& options)
|
||||
{
|
||||
auto strip_stream = make<FixedMemoryStream>(bytes);
|
||||
auto bit_stream = make<BigEndianInputBitStream>(MaybeOwned<Stream>(*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<FixedMemoryStream>(decoded_bytes.bytes());
|
||||
auto decoded_bits = make<BigEndianOutputBitStream>(MaybeOwned<Stream>(*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<ByteBuffer> decode_ccitt_group4(ReadonlyBytes bytes, u32 image_width, u32 image_height)
|
||||
{
|
||||
auto strip_stream = make<FixedMemoryStream>(bytes);
|
||||
auto bit_stream = make<BigEndianInputBitStream>(MaybeOwned<Stream>(*strip_stream));
|
||||
|
||||
auto output_stream = make<AllocatingMemoryStream>();
|
||||
auto decoded_bits = make<BigEndianOutputBitStream>(MaybeOwned<Stream>(*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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
|
||||
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<ByteBuffer> 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<ByteBuffer> decode_ccitt_group3(ReadonlyBytes bytes, u32 image_width, u32 image_height, Group3Options const& options);
|
||||
|
||||
ErrorOr<ByteBuffer> decode_ccitt_group4(ReadonlyBytes bytes, u32 image_width, u32 image_height);
|
||||
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@
|
|||
#include <LibGfx/ImageFormats/JPEGLoader.h>
|
||||
#include <LibGfx/ImageFormats/JPEGXLLoader.h>
|
||||
#include <LibGfx/ImageFormats/PNGLoader.h>
|
||||
#include <LibGfx/ImageFormats/TIFFLoader.h>
|
||||
#include <LibGfx/ImageFormats/WebPLoader.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
|
@ -31,7 +30,6 @@ static ErrorOr<OwnPtr<ImageDecoderPlugin>> 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 }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,802 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "TIFFLoader.h"
|
||||
#include <AK/ConstrainedStream.h>
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCompress/Lzw.h>
|
||||
#include <LibCompress/PackBitsDecoder.h>
|
||||
#include <LibCompress/Zlib.h>
|
||||
#include <LibGfx/CMYKBitmap.h>
|
||||
#include <LibGfx/ImageFormats/CCITTDecoder.h>
|
||||
#include <LibGfx/ImageFormats/ExifOrientedBitmap.h>
|
||||
#include <LibGfx/ImageFormats/TIFFMetadata.h>
|
||||
|
||||
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<FixedMemoryStream> stream)
|
||||
: m_stream(move(stream))
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<void> decode_image_header()
|
||||
{
|
||||
TRY(read_image_file_header());
|
||||
TRY(read_next_image_file_directory());
|
||||
|
||||
m_state = State::HeaderDecoded;
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> 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<Vector<u32>> segment_offsets() const
|
||||
{
|
||||
return m_metadata.strip_offsets().has_value() ? m_metadata.strip_offsets() : m_metadata.tile_offsets();
|
||||
}
|
||||
|
||||
Optional<Vector<u32>> 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<void> 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<void> 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<CMYKBitmap> cmyk_bitmap() const
|
||||
{
|
||||
return m_cmyk_bitmap;
|
||||
}
|
||||
|
||||
RefPtr<Bitmap> bitmap() const
|
||||
{
|
||||
return m_bitmap;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class ByteOrder {
|
||||
LittleEndian,
|
||||
BigEndian,
|
||||
};
|
||||
|
||||
static ErrorOr<u8> read_component(BigEndianInputBitStream& stream, u8 bits)
|
||||
{
|
||||
// FIXME: This function truncates everything to 8-bits
|
||||
auto const value = TRY(stream.read_bits<u32>(bits));
|
||||
|
||||
if (bits > 8)
|
||||
return value >> (bits - 8);
|
||||
return NumericLimits<u8>::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<u8> 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<u8> 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<u8> 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<u8>::max());
|
||||
}
|
||||
|
||||
ErrorOr<Color> 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<u16>(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<CMYK> 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<CallableAs<ErrorOr<ReadonlyBytes>, u32, IntSize> SegmentDecoder>
|
||||
ErrorOr<void> 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<ExifOrientedBitmap, ExifOrientedCMYKBitmap> oriented_bitmap = TRY(([&]() -> ErrorOr<Variant<ExifOrientedBitmap, ExifOrientedCMYKBitmap>> {
|
||||
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<FixedMemoryStream>(decoded_bytes);
|
||||
auto decoded_stream = make<BigEndianInputBitStream>(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<Color> 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<ExifOrientedCMYKBitmap>().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<ExifOrientedBitmap>().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<ExifOrientedCMYKBitmap>().bitmap();
|
||||
else
|
||||
m_bitmap = oriented_bitmap.get<ExifOrientedBitmap>().bitmap();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> 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<ByteBuffer> 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<u8 const>(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<void> decode_frame_impl()
|
||||
{
|
||||
switch (*m_metadata.compression()) {
|
||||
case Compression::NoCompression: {
|
||||
auto identity = [&](u32 num_bytes, IntSize) {
|
||||
return m_stream->read_in_place<u8 const>(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<ReadonlyBytes> {
|
||||
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<ReadonlyBytes> {
|
||||
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<ReadonlyBytes> {
|
||||
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<ReadonlyBytes> {
|
||||
auto const encoded_bytes = TRY(m_stream->read_in_place<u8 const>(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<LittleEndianInputBitStream>::decompress_all(encoded_bytes, 8, 0));
|
||||
else
|
||||
decoded_bytes = TRY(Compress::LzwDecompressor<BigEndianInputBitStream>::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<ReadonlyBytes> {
|
||||
auto stream = make<ConstrainedStream>(MaybeOwned<Stream>(*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<ReadonlyBytes> {
|
||||
auto const encoded_bytes = TRY(m_stream->read_in_place<u8 const>(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<typename T>
|
||||
ErrorOr<T> read_value()
|
||||
{
|
||||
if (m_byte_order == ByteOrder::LittleEndian)
|
||||
return TRY(m_stream->read_value<LittleEndian<T>>());
|
||||
if (m_byte_order == ByteOrder::BigEndian)
|
||||
return TRY(m_stream->read_value<BigEndian<T>>());
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
ErrorOr<void> 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<u32> { ifd_offset };
|
||||
} else {
|
||||
m_next_ifd = OptionalNone {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> read_next_idf_offset()
|
||||
{
|
||||
auto const next_block_position = TRY(read_value<u32>());
|
||||
TRY(set_next_ifd(next_block_position));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> read_image_file_header()
|
||||
{
|
||||
// Section 2: TIFF Structure - Image File Header
|
||||
|
||||
auto const byte_order = TRY(m_stream->read_value<u16>());
|
||||
|
||||
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<u16>());
|
||||
|
||||
if (magic_number != 42)
|
||||
return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid magic number");
|
||||
|
||||
TRY(read_next_idf_offset());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> 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<u16>());
|
||||
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<Vector<Value, 1>> 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]<typename T>() -> ErrorOr<Vector<Value>> {
|
||||
Vector<Value, 1> result {};
|
||||
TRY(result.try_ensure_capacity(count));
|
||||
if constexpr (IsSpecializationOf<T, Rational>) {
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
result.empend(T { TRY(read_value<typename T::Type>()), TRY(read_value<typename T::Type>()) });
|
||||
} else {
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
result.empend(typename TypePromoter<T>::Type(TRY(read_value<T>())));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case Type::Byte:
|
||||
case Type::Undefined: {
|
||||
Vector<Value, 1> 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<Value, 1> 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()<u16>();
|
||||
case Type::IFD:
|
||||
case Type::UnsignedLong:
|
||||
return read_every_values.template operator()<u32>();
|
||||
case Type::UnsignedRational:
|
||||
return read_every_values.template operator()<Rational<u32>>();
|
||||
case Type::SignedLong:
|
||||
return read_every_values.template operator()<i32>();
|
||||
case Type::SignedRational:
|
||||
return read_every_values.template operator()<Rational<i32>>();
|
||||
case Type::Float:
|
||||
return read_every_values.template operator()<float>();
|
||||
case Type::Double:
|
||||
return read_every_values.template operator()<double>();
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
ErrorOr<void> read_tag()
|
||||
{
|
||||
auto const tag = TRY(read_value<u16>());
|
||||
auto const raw_type = TRY(read_value<u16>());
|
||||
auto const type = TRY(tiff_type_from_u16(raw_type));
|
||||
auto const count = TRY(read_value<u32>());
|
||||
|
||||
Checked<u32> 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<Vector<Value>> {
|
||||
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<u32>());
|
||||
return read_tiff_value(type, count, offset);
|
||||
}()));
|
||||
|
||||
auto subifd_handler = [&](u32 ifd_offset) -> ErrorOr<void> {
|
||||
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<FixedMemoryStream> m_stream;
|
||||
State m_state {};
|
||||
RefPtr<Bitmap> m_bitmap {};
|
||||
RefPtr<CMYKBitmap> m_cmyk_bitmap {};
|
||||
|
||||
ByteOrder m_byte_order {};
|
||||
Optional<u32> m_next_ifd {};
|
||||
|
||||
ExifMetadata m_metadata {};
|
||||
|
||||
// These are caches for m_metadata values
|
||||
PhotometricInterpretation m_photometric_interpretation {};
|
||||
Vector<u32, 4> m_bits_per_sample {};
|
||||
u32 m_image_width {};
|
||||
Predictor m_predictor {};
|
||||
|
||||
Optional<u8> m_alpha_channel_index {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
TIFFImageDecoderPlugin::TIFFImageDecoderPlugin(NonnullOwnPtr<FixedMemoryStream> stream)
|
||||
{
|
||||
m_context = make<TIFF::TIFFLoadingContext>(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<NonnullOwnPtr<ImageDecoderPlugin>> TIFFImageDecoderPlugin::create(ReadonlyBytes data)
|
||||
{
|
||||
auto stream = TRY(try_make<FixedMemoryStream>(data));
|
||||
auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) TIFFImageDecoderPlugin(move(stream))));
|
||||
TRY(plugin->m_context->decode_image_header());
|
||||
return plugin;
|
||||
}
|
||||
|
||||
ErrorOr<ImageFrameDescriptor> TIFFImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
|
||||
{
|
||||
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<Metadata const&> TIFFImageDecoderPlugin::metadata()
|
||||
{
|
||||
return m_context->metadata();
|
||||
}
|
||||
|
||||
ErrorOr<Optional<ReadonlyBytes>> TIFFImageDecoderPlugin::icc_data()
|
||||
{
|
||||
return m_context->metadata().icc_profile().map([](auto const& buffer) -> ReadonlyBytes { return buffer.bytes(); });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibGfx/ImageFormats/ImageDecoder.h>
|
||||
|
||||
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<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
|
||||
|
||||
virtual ~TIFFImageDecoderPlugin() override;
|
||||
|
||||
virtual IntSize size() override;
|
||||
|
||||
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) override;
|
||||
|
||||
virtual Optional<Metadata const&> metadata() override;
|
||||
virtual ErrorOr<Optional<ReadonlyBytes>> icc_data() override;
|
||||
|
||||
private:
|
||||
TIFFImageDecoderPlugin(NonnullOwnPtr<FixedMemoryStream>);
|
||||
|
||||
OwnPtr<TIFF::TIFFLoadingContext> m_context;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/ImageFormats/TIFFLoader.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
#include <LibGfx/ImageFormats/JPEGLoader.h>
|
||||
#include <LibGfx/ImageFormats/JPEGXLLoader.h>
|
||||
#include <LibGfx/ImageFormats/PNGLoader.h>
|
||||
#include <LibGfx/ImageFormats/TIFFLoader.h>
|
||||
#include <LibGfx/ImageFormats/TIFFMetadata.h>
|
||||
#include <LibGfx/ImageFormats/WebPLoader.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
|
|
@ -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)));
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,2 +1 @@
|
|||
GIF: loaded, width=1, height=1
|
||||
TIFF: loaded, width=1, height=1
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue