diff --git a/Libraries/LibGfx/ImageFormats/ExifReader.cpp b/Libraries/LibGfx/ImageFormats/ExifReader.cpp new file mode 100644 index 0000000000..11862e76cc --- /dev/null +++ b/Libraries/LibGfx/ImageFormats/ExifReader.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2023, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gfx::TIFF { + +namespace { + +class ExifReader { +public: + explicit ExifReader(FixedMemoryStream stream) + : m_stream(move(stream)) + { + } + + ErrorOr> read_metadata() + { + TRY(read_image_file_header()); + TRY(read_next_image_file_directory()); + return try_make(move(m_metadata)); + } + +private: + enum class ByteOrder { + LittleEndian, + BigEndian, + }; + + template + ErrorOr read_value() + { + if (m_byte_order == ByteOrder::LittleEndian) + return TRY(m_stream.read_value>()); + if (m_byte_order == ByteOrder::BigEndian) + return TRY(m_stream.read_value>()); + VERIFY_NOT_REACHED(); + } + + ErrorOr set_next_ifd(u32 ifd_offset) + { + if (ifd_offset != 0) { + if (ifd_offset < TRY(m_stream.tell())) + return Error::from_string_literal("ExifReader: Can not accept an IFD pointing to previous data"); + + m_next_ifd = Optional { ifd_offset }; + } else { + m_next_ifd = OptionalNone {}; + } + return {}; + } + + ErrorOr read_next_ifd_offset() + { + auto const next_block_position = TRY(read_value()); + TRY(set_next_ifd(next_block_position)); + + return {}; + } + + ErrorOr read_image_file_header() + { + // Section 2: TIFF Structure - Image File Header + + auto const byte_order = TRY(m_stream.read_value()); + + switch (byte_order) { + case 0x4949: + m_byte_order = ByteOrder::LittleEndian; + break; + case 0x4D4D: + m_byte_order = ByteOrder::BigEndian; + break; + default: + return Error::from_string_literal("ExifReader: Invalid byte order"); + } + + auto const magic_number = TRY(read_value()); + + if (magic_number != 42) + return Error::from_string_literal("ExifReader: Invalid magic number"); + + TRY(read_next_ifd_offset()); + + return {}; + } + + ErrorOr read_next_image_file_directory() + { + // Section 2: TIFF Structure - Image File Directory + + if (!m_next_ifd.has_value()) + return Error::from_string_literal("ExifReader: Missing an Image File Directory"); + + dbgln_if(TIFF_DEBUG, "Reading image file directory at offset {}", m_next_ifd); + + TRY(m_stream.seek(m_next_ifd.value())); + + auto const number_of_field = TRY(read_value()); + auto next_tag_offset = TRY(m_stream.tell()); + + for (u16 i = 0; i < number_of_field; ++i) { + if (auto maybe_error = read_tag(); maybe_error.is_error() && TIFF_DEBUG) + dbgln("Unable to decode tag {}/{}", i + 1, number_of_field); + + // Section 2: TIFF Structure + // IFD Entry + // Size of tag(u16) + type(u16) + count(u32) + value_or_offset(u32) = 12 + next_tag_offset += 12; + TRY(m_stream.seek(next_tag_offset)); + } + + TRY(read_next_ifd_offset()); + return {}; + } + + ErrorOr> read_tiff_value(Type type, u32 count, u32 offset) + { + auto const old_offset = TRY(m_stream.tell()); + ScopeGuard reset_offset { [this, old_offset]() { MUST(m_stream.seek(old_offset)); } }; + + TRY(m_stream.seek(offset)); + + if (size_of_type(type) * count > m_stream.remaining()) + return Error::from_string_literal("ExifReader: Tag size claims to be bigger that remaining bytes"); + + auto const read_every_values = [this, count]() -> ErrorOr> { + Vector result {}; + TRY(result.try_ensure_capacity(count)); + if constexpr (IsSpecializationOf) { + for (u32 i = 0; i < count; ++i) + result.empend(T { TRY(read_value()), TRY(read_value()) }); + } else { + for (u32 i = 0; i < count; ++i) + result.empend(typename TypePromoter::Type(TRY(read_value()))); + } + return result; + }; + + switch (type) { + case Type::Byte: + case Type::Undefined: { + Vector result; + auto buffer = TRY(ByteBuffer::create_uninitialized(count)); + TRY(m_stream.read_until_filled(buffer)); + result.append(move(buffer)); + return result; + } + case Type::ASCII: + case Type::UTF8: { + Vector result; + // NOTE: No need to include the null terminator + if (count > 0) + --count; + auto string_data = TRY(ByteBuffer::create_uninitialized(count)); + TRY(m_stream.read_until_filled(string_data)); + result.empend(TRY(String::from_utf8(StringView { string_data.bytes() }))); + return result; + } + case Type::UnsignedShort: + return read_every_values.template operator()(); + case Type::IFD: + case Type::UnsignedLong: + return read_every_values.template operator()(); + case Type::UnsignedRational: + return read_every_values.template operator()>(); + case Type::SignedLong: + return read_every_values.template operator()(); + case Type::SignedRational: + return read_every_values.template operator()>(); + case Type::Float: + return read_every_values.template operator()(); + case Type::Double: + return read_every_values.template operator()(); + default: + VERIFY_NOT_REACHED(); + } + } + + ErrorOr read_tag() + { + auto const tag = TRY(read_value()); + auto const raw_type = TRY(read_value()); + auto const type = TRY(tiff_type_from_u16(raw_type)); + auto const count = TRY(read_value()); + + Checked checked_size = size_of_type(type); + checked_size *= count; + + if (checked_size.has_overflow()) + return Error::from_string_literal("ExifReader: Invalid tag with too large data"); + + auto tiff_value = TRY(([=, this]() -> ErrorOr> { + if (checked_size.value() <= 4) { + auto value = TRY(read_tiff_value(type, count, TRY(m_stream.tell()))); + TRY(m_stream.discard(4)); + return value; + } + auto const offset = TRY(read_value()); + return read_tiff_value(type, count, offset); + }())); + + auto subifd_handler = [&](u32 ifd_offset) -> ErrorOr { + if (auto result = set_next_ifd(ifd_offset); result.is_error()) { + dbgln("{}", result.error()); + return {}; + } + TRY(read_next_image_file_directory()); + return {}; + }; + + TRY(handle_tag(move(subifd_handler), m_metadata, tag, type, count, move(tiff_value))); + + return {}; + } + + FixedMemoryStream m_stream; + ByteOrder m_byte_order {}; + Optional m_next_ifd {}; + + ExifMetadata m_metadata {}; +}; + +} + +ErrorOr> read_exif_metadata(ReadonlyBytes bytes) +{ + ExifReader reader { FixedMemoryStream { bytes } }; + return reader.read_metadata(); +} + +} diff --git a/Libraries/LibGfx/ImageFormats/ExifReader.h b/Libraries/LibGfx/ImageFormats/ExifReader.h new file mode 100644 index 0000000000..ff300714b9 --- /dev/null +++ b/Libraries/LibGfx/ImageFormats/ExifReader.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Gfx { + +class ExifMetadata; + +namespace TIFF { + +// Exif data is encoded as a TIFF structure: a set of TIFF tags, without any associated image data. +// +// This is a link to the main TIFF specification from 1992 +// https://www.itu.int/itudoc/itu-t/com16/tiff-fx/docs/tiff6.pdf +// +// The Exif specification is named "Exchangeable image file format for digital still cameras: Exif Version 3.0" +// and can be found at https://www.cipa.jp/e/std/std-sec.html +ErrorOr> read_exif_metadata(ReadonlyBytes); + +} + +} diff --git a/Libraries/LibGfx/ImageFormats/PNGLoader.cpp b/Libraries/LibGfx/ImageFormats/PNGLoader.cpp index 738632a068..2cb1108471 100644 --- a/Libraries/LibGfx/ImageFormats/PNGLoader.cpp +++ b/Libraries/LibGfx/ImageFormats/PNGLoader.cpp @@ -8,8 +8,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -242,7 +242,7 @@ ErrorOr PNGImageDecoderPlugin::initialize() u32 exif_length = 0; int const num_exif_chunks = png_get_eXIf_1(m_context->png_ptr, m_context->info_ptr, &exif_length, &exif_data); if (num_exif_chunks > 0) - m_context->exif_metadata = TRY(TIFFImageDecoderPlugin::read_exif_metadata({ exif_data, exif_length })); + m_context->exif_metadata = TRY(TIFF::read_exif_metadata({ exif_data, exif_length })); return {}; } diff --git a/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp b/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp index 24c095ec1d..4a787e3cb1 100644 --- a/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp +++ b/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp @@ -799,12 +799,4 @@ ErrorOr> TIFFImageDecoderPlugin::icc_data() return m_context->metadata().icc_profile().map([](auto const& buffer) -> ReadonlyBytes { return buffer.bytes(); }); } -ErrorOr> TIFFImageDecoderPlugin::read_exif_metadata(ReadonlyBytes data) -{ - auto stream = TRY(try_make(data)); - auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) TIFFImageDecoderPlugin(move(stream)))); - TRY(plugin->m_context->decode_image_header()); - return try_make(plugin->m_context->metadata()); -} - } diff --git a/Libraries/LibGfx/ImageFormats/TIFFLoader.h b/Libraries/LibGfx/ImageFormats/TIFFLoader.h index b530d394b3..0302dd4219 100644 --- a/Libraries/LibGfx/ImageFormats/TIFFLoader.h +++ b/Libraries/LibGfx/ImageFormats/TIFFLoader.h @@ -12,8 +12,6 @@ namespace Gfx { -class ExifMetadata; - // This is a link to the main TIFF specification from 1992 // https://www.itu.int/itudoc/itu-t/com16/tiff-fx/docs/tiff6.pdf @@ -38,7 +36,6 @@ class TIFFImageDecoderPlugin : public ImageDecoderPlugin { public: static bool sniff(ReadonlyBytes); static ErrorOr> create(ReadonlyBytes); - static ErrorOr> read_exif_metadata(ReadonlyBytes); virtual ~TIFFImageDecoderPlugin() override; diff --git a/Libraries/LibImageDecoders/CMakeLists.txt b/Libraries/LibImageDecoders/CMakeLists.txt index 8d1c5b4afb..9da09ebbf6 100644 --- a/Libraries/LibImageDecoders/CMakeLists.txt +++ b/Libraries/LibImageDecoders/CMakeLists.txt @@ -4,6 +4,7 @@ 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 ${LIBGFX_IMAGE_FORMATS_DIR}/ImageDecoder.cpp