|
@@ -0,0 +1,547 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
|
|
|
+ *
|
|
|
+ * SPDX-License-Identifier: BSD-2-Clause
|
|
|
+ */
|
|
|
+
|
|
|
+#include "TIFFLoader.h"
|
|
|
+#include <AK/Debug.h>
|
|
|
+#include <AK/Endian.h>
|
|
|
+#include <AK/String.h>
|
|
|
+
|
|
|
+namespace Gfx {
|
|
|
+
|
|
|
+class TIFFLoadingContext {
|
|
|
+public:
|
|
|
+ enum class State {
|
|
|
+ NotDecoded = 0,
|
|
|
+ Error,
|
|
|
+ HeaderDecoded,
|
|
|
+ FrameDecoded,
|
|
|
+ };
|
|
|
+
|
|
|
+ template<OneOf<u32, i32> x32>
|
|
|
+ struct Rational {
|
|
|
+ using Type = x32;
|
|
|
+ x32 numerator;
|
|
|
+ x32 denominator;
|
|
|
+ };
|
|
|
+
|
|
|
+ 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> decode_frame()
|
|
|
+ {
|
|
|
+ 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 m_size;
|
|
|
+ }
|
|
|
+
|
|
|
+ State state() const
|
|
|
+ {
|
|
|
+ return m_state;
|
|
|
+ }
|
|
|
+
|
|
|
+ RefPtr<Bitmap> bitmap() const
|
|
|
+ {
|
|
|
+ return m_bitmap;
|
|
|
+ }
|
|
|
+
|
|
|
+private:
|
|
|
+ enum class ByteOrder {
|
|
|
+ LittleEndian,
|
|
|
+ BigEndian,
|
|
|
+ };
|
|
|
+
|
|
|
+ enum class Type {
|
|
|
+ Byte = 1,
|
|
|
+ ASCII = 2,
|
|
|
+ UnsignedShort = 3,
|
|
|
+ UnsignedLong = 4,
|
|
|
+ UnsignedRational = 5,
|
|
|
+ Undefined = 7,
|
|
|
+ SignedLong = 9,
|
|
|
+ SignedRational = 10,
|
|
|
+ Float = 11,
|
|
|
+ Double = 12,
|
|
|
+ UTF8 = 129,
|
|
|
+ };
|
|
|
+
|
|
|
+ using Value = Variant<u8, String, u16, u32, Rational<u32>, i32, Rational<i32>>;
|
|
|
+
|
|
|
+ // This enum is progessively defined across sections but summarized in:
|
|
|
+ // Appendix A: TIFF Tags Sorted by Number
|
|
|
+ enum class Compression {
|
|
|
+ NoCompression = 1,
|
|
|
+ CCITT = 2,
|
|
|
+ Group3Fax = 3,
|
|
|
+ Group4Fax = 4,
|
|
|
+ LZW = 5,
|
|
|
+ JPEG = 6,
|
|
|
+ PackBits = 32773,
|
|
|
+ };
|
|
|
+
|
|
|
+ ErrorOr<void> decode_frame_impl()
|
|
|
+ {
|
|
|
+ if (m_compression != Compression::NoCompression)
|
|
|
+ return Error::from_string_literal("Compressed TIFF are not supported yet :^)");
|
|
|
+
|
|
|
+ m_bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, m_size));
|
|
|
+
|
|
|
+ for (u32 strip_index = 0; strip_index < m_strip_offsets.size(); ++strip_index) {
|
|
|
+ TRY(m_stream->seek(m_strip_offsets[strip_index]));
|
|
|
+ for (u32 row = 0; row < m_rows_per_strip; row++) {
|
|
|
+ auto const scanline = row + m_rows_per_strip * strip_index;
|
|
|
+ if (scanline >= static_cast<u32>(m_size.height()))
|
|
|
+ break;
|
|
|
+
|
|
|
+ for (u32 column = 0; column < static_cast<u32>(m_size.width()); ++column) {
|
|
|
+ Color const color { TRY(read_value<u8>()), TRY(read_value<u8>()), TRY(read_value<u8>()) };
|
|
|
+ m_bitmap->set_pixel(column, scanline, color);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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> read_next_idf_offset()
|
|
|
+ {
|
|
|
+ auto const next_block_position = TRY(read_value<u32>());
|
|
|
+
|
|
|
+ if (next_block_position != 0)
|
|
|
+ m_next_ifd = Optional<u32> { next_block_position };
|
|
|
+ else
|
|
|
+ m_next_ifd = OptionalNone {};
|
|
|
+ dbgln_if(TIFF_DEBUG, "Setting image file directory pointer to {}", m_next_ifd);
|
|
|
+ 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");
|
|
|
+
|
|
|
+ TRY(m_stream->seek(m_next_ifd.value()));
|
|
|
+
|
|
|
+ auto const number_of_field = TRY(read_value<u16>());
|
|
|
+
|
|
|
+ for (u16 i = 0; i < number_of_field; ++i)
|
|
|
+ TRY(read_tag());
|
|
|
+
|
|
|
+ TRY(read_next_idf_offset());
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ ErrorOr<Type> read_type()
|
|
|
+ {
|
|
|
+ switch (TRY(read_value<u16>())) {
|
|
|
+ case to_underlying(Type::Byte):
|
|
|
+ return Type::Byte;
|
|
|
+ case to_underlying(Type::ASCII):
|
|
|
+ return Type::ASCII;
|
|
|
+ case to_underlying(Type::UnsignedShort):
|
|
|
+ return Type::UnsignedShort;
|
|
|
+ case to_underlying(Type::UnsignedLong):
|
|
|
+ return Type::UnsignedLong;
|
|
|
+ case to_underlying(Type::UnsignedRational):
|
|
|
+ return Type::UnsignedRational;
|
|
|
+ case to_underlying(Type::Undefined):
|
|
|
+ return Type::Undefined;
|
|
|
+ case to_underlying(Type::SignedLong):
|
|
|
+ return Type::SignedLong;
|
|
|
+ case to_underlying(Type::SignedRational):
|
|
|
+ return Type::SignedRational;
|
|
|
+ case to_underlying(Type::UTF8):
|
|
|
+ return Type::UTF8;
|
|
|
+ default:
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Unknown type");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static constexpr u8 size_of_type(Type type)
|
|
|
+ {
|
|
|
+ switch (type) {
|
|
|
+ case Type::Byte:
|
|
|
+ return 1;
|
|
|
+ case Type::ASCII:
|
|
|
+ return 1;
|
|
|
+ case Type::UnsignedShort:
|
|
|
+ return 2;
|
|
|
+ case Type::UnsignedLong:
|
|
|
+ return 4;
|
|
|
+ case Type::UnsignedRational:
|
|
|
+ return 8;
|
|
|
+ case Type::Undefined:
|
|
|
+ return 1;
|
|
|
+ case Type::SignedLong:
|
|
|
+ return 4;
|
|
|
+ case Type::SignedRational:
|
|
|
+ return 8;
|
|
|
+ case Type::Float:
|
|
|
+ return 4;
|
|
|
+ case Type::Double:
|
|
|
+ return 8;
|
|
|
+ case Type::UTF8:
|
|
|
+ return 1;
|
|
|
+ default:
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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(TRY(read_value<T>()));
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ };
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case Type::Byte:
|
|
|
+ case Type::Undefined:
|
|
|
+ return read_every_values.template operator()<u8>();
|
|
|
+ case Type::ASCII:
|
|
|
+ case Type::UTF8: {
|
|
|
+ Vector<Value, 1> result;
|
|
|
+ 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::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>>();
|
|
|
+ default:
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ErrorOr<void> read_tag()
|
|
|
+ {
|
|
|
+ auto const tag = TRY(read_value<u16>());
|
|
|
+ auto const type = TRY(read_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 const 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);
|
|
|
+ }()));
|
|
|
+
|
|
|
+ if constexpr (TIFF_DEBUG) {
|
|
|
+ if (tiff_value.size() == 1) {
|
|
|
+ tiff_value[0].visit(
|
|
|
+ [&](auto const& value) {
|
|
|
+ dbgln("Read tag({}), type({}): {}", tag, to_underlying(type), value);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ dbg("Read tag({}), type({}): [", tag, to_underlying(type));
|
|
|
+ for (u32 i = 0; i < tiff_value.size(); ++i) {
|
|
|
+ tiff_value[i].visit(
|
|
|
+ [&](auto const& value) {
|
|
|
+ dbg("{}", value);
|
|
|
+ });
|
|
|
+ if (i != tiff_value.size() - 1)
|
|
|
+ dbg(", ");
|
|
|
+ }
|
|
|
+ dbgln("]");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ TRY(handle_tag(tag, type, count, tiff_value));
|
|
|
+
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ ErrorOr<void> handle_tag(u16 tag, Type type, u32 count, Vector<Value> const& value)
|
|
|
+ {
|
|
|
+ // FIXME: Make that easy to extend
|
|
|
+ switch (tag) {
|
|
|
+ case 256:
|
|
|
+ // ImageWidth
|
|
|
+ if ((type != Type::UnsignedShort && type != Type::UnsignedLong) || count != 1)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag 256");
|
|
|
+
|
|
|
+ value[0].visit(
|
|
|
+ [this]<OneOf<u16, u32> T>(T const& width) {
|
|
|
+ m_size.set_width(width);
|
|
|
+ },
|
|
|
+ [&](auto const&) {
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ });
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 257:
|
|
|
+ // ImageLength
|
|
|
+ if ((type != Type::UnsignedShort && type != Type::UnsignedLong) || count != 1)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag 257");
|
|
|
+
|
|
|
+ value[0].visit(
|
|
|
+ [this]<OneOf<u16, u32> T>(T const& width) {
|
|
|
+ m_size.set_height(width);
|
|
|
+ },
|
|
|
+ [&](auto const&) {
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ });
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 258:
|
|
|
+ // BitsPerSample
|
|
|
+ if (type != Type::UnsignedShort || count != 3)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag 258");
|
|
|
+
|
|
|
+ for (u8 i = 0; i < m_bits_per_sample.size(); ++i) {
|
|
|
+ value[i].visit(
|
|
|
+ [this, i](u16 const& bits_per_sample) {
|
|
|
+ m_bits_per_sample[i] = bits_per_sample;
|
|
|
+ },
|
|
|
+ [&](auto const&) {
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 259:
|
|
|
+ // Compression
|
|
|
+ if (type != Type::UnsignedShort || count != 1)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag 259");
|
|
|
+
|
|
|
+ TRY(value[0].visit(
|
|
|
+ [this](u16 const& compression) -> ErrorOr<void> {
|
|
|
+ if (compression > 6 && compression != to_underlying(Compression::PackBits))
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid compression value");
|
|
|
+
|
|
|
+ m_compression = static_cast<Compression>(compression);
|
|
|
+ return {};
|
|
|
+ },
|
|
|
+ [&](auto const&) -> ErrorOr<void> {
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ }));
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 273:
|
|
|
+ // StripOffsets
|
|
|
+ if (type != Type::UnsignedShort && type != Type::UnsignedLong)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag 273");
|
|
|
+
|
|
|
+ TRY(m_strip_offsets.try_ensure_capacity(count));
|
|
|
+ for (u32 i = 0; i < count; ++i) {
|
|
|
+ value[i].visit(
|
|
|
+ [this]<OneOf<u16, u32> T>(T const& offset) {
|
|
|
+ m_strip_offsets.append(offset);
|
|
|
+ },
|
|
|
+ [&](auto const&) {
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 277:
|
|
|
+ // SamplesPerPixel
|
|
|
+ if (type != Type::UnsignedShort || count != 1)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag 277");
|
|
|
+ TRY(value[0].visit(
|
|
|
+ [](u16 const& samples_per_pixels) -> ErrorOr<void> {
|
|
|
+ if (samples_per_pixels != 3)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag 277");
|
|
|
+ return {};
|
|
|
+ },
|
|
|
+ [&](auto const&) -> ErrorOr<void> {
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ }));
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 278:
|
|
|
+ // RowsPerStrip
|
|
|
+ if ((type != Type::UnsignedShort && type != Type::UnsignedLong) || count != 1)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag 278");
|
|
|
+
|
|
|
+ value[0].visit(
|
|
|
+ [this]<OneOf<u16, u32> T>(T const& rows_per_strip) {
|
|
|
+ m_rows_per_strip = rows_per_strip;
|
|
|
+ },
|
|
|
+ [&](auto const&) {
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ });
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 279:
|
|
|
+ // StripByteCounts
|
|
|
+ if (type != Type::UnsignedShort && type != Type::UnsignedLong)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid tag 279");
|
|
|
+
|
|
|
+ TRY(m_strip_bytes_count.try_ensure_capacity(count));
|
|
|
+ for (u32 i = 0; i < count; ++i) {
|
|
|
+ value[i].visit(
|
|
|
+ [this]<OneOf<u16, u32> T>(T const& offset) {
|
|
|
+ m_strip_bytes_count.append(offset);
|
|
|
+ },
|
|
|
+ [&](auto const&) {
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ dbgln_if(TIFF_DEBUG, "Unknown tag: {}", tag);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ NonnullOwnPtr<FixedMemoryStream> m_stream;
|
|
|
+ IntSize m_size {};
|
|
|
+ State m_state {};
|
|
|
+ RefPtr<Bitmap> m_bitmap {};
|
|
|
+
|
|
|
+ ByteOrder m_byte_order {};
|
|
|
+ Optional<u32> m_next_ifd {};
|
|
|
+
|
|
|
+ Array<u16, 3> m_bits_per_sample {};
|
|
|
+ Compression m_compression {};
|
|
|
+ Vector<u32> m_strip_offsets {};
|
|
|
+ u32 m_rows_per_strip {};
|
|
|
+ Vector<u32> m_strip_bytes_count {};
|
|
|
+};
|
|
|
+
|
|
|
+TIFFImageDecoderPlugin::TIFFImageDecoderPlugin(NonnullOwnPtr<FixedMemoryStream> stream)
|
|
|
+{
|
|
|
+ m_context = make<TIFFLoadingContext>(move(stream));
|
|
|
+}
|
|
|
+
|
|
|
+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() == TIFFLoadingContext::State::Error)
|
|
|
+ return Error::from_string_literal("TIFFImageDecoderPlugin: Decoding failed");
|
|
|
+
|
|
|
+ if (m_context->state() < TIFFLoadingContext::State::FrameDecoded)
|
|
|
+ TRY(m_context->decode_frame());
|
|
|
+
|
|
|
+ return ImageFrameDescriptor { m_context->bitmap(), 0 };
|
|
|
+}
|
|
|
+}
|
|
|
+
|
|
|
+template<typename T>
|
|
|
+struct AK::Formatter<Gfx::TIFFLoadingContext::Rational<T>> : Formatter<FormatString> {
|
|
|
+ ErrorOr<void> format(FormatBuilder& builder, Gfx::TIFFLoadingContext::Rational<T> value)
|
|
|
+ {
|
|
|
+ return Formatter<FormatString>::format(builder, "{} ({}/{})"sv, static_cast<double>(value.numerator) / value.denominator, value.numerator, value.denominator);
|
|
|
+ }
|
|
|
+};
|