ladybird/Libraries/LibGfx/ImageFormats/ExifReader.cpp
2026-06-12 22:37:49 +02:00

242 lines
7.6 KiB
C++

/*
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <AK/Debug.h>
#include <AK/Endian.h>
#include <AK/MemoryStream.h>
#include <AK/ScopeGuard.h>
#include <AK/String.h>
#include <LibGfx/ImageFormats/ExifReader.h>
#include <LibGfx/ImageFormats/TIFFMetadata.h>
namespace Gfx::TIFF {
namespace {
class ExifReader {
public:
explicit ExifReader(FixedMemoryStream stream)
: m_stream(move(stream))
{
}
ErrorOr<NonnullOwnPtr<ExifMetadata>> read_metadata()
{
TRY(read_image_file_header());
TRY(read_next_image_file_directory());
return try_make<ExifMetadata>(move(m_metadata));
}
private:
enum class ByteOrder {
LittleEndian,
BigEndian,
};
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("ExifReader: 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_ifd_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("ExifReader: Invalid byte order");
}
auto const magic_number = TRY(read_value<u16>());
if (magic_number != 42)
return Error::from_string_literal("ExifReader: Invalid magic number");
TRY(read_next_ifd_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("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<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_ifd_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("ExifReader: 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("ExifReader: 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 {};
}
FixedMemoryStream m_stream;
ByteOrder m_byte_order {};
Optional<u32> m_next_ifd {};
ExifMetadata m_metadata {};
};
}
ErrorOr<NonnullOwnPtr<ExifMetadata>> read_exif_metadata(ReadonlyBytes bytes)
{
ExifReader reader { FixedMemoryStream { bytes } };
return reader.read_metadata();
}
}