diff --git a/encoder/3rdparty/exr_reader.hh b/encoder/3rdparty/exr_reader.hh new file mode 100644 index 0000000..8ea0d66 --- /dev/null +++ b/encoder/3rdparty/exr_reader.hh @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2025, Syoyo Fujita and many contributors. +// All rights reserved. +// +// EXR Reader: Reader class with error stack for safe memory reading + +#ifndef TINYEXR_EXR_READER_HH_ +#define TINYEXR_EXR_READER_HH_ + +#include +#include +#include "streamreader.hh" + +namespace tinyexr { + +// Reader class that wraps StreamReader and accumulates errors +class Reader { +public: + Reader(const uint8_t* data, size_t length, Endian endian = Endian::Little) + : stream_(data, length, endian), has_error_(false) {} + + // Check if any errors have occurred + bool has_error() const { return has_error_; } + + // Get all accumulated errors + const std::vector& errors() const { return errors_; } + + // Get the most recent error + std::string last_error() const { + return errors_.empty() ? "" : errors_.back(); + } + + // Get all errors as a single string + std::string all_errors() const { + std::string result; + for (size_t i = 0; i < errors_.size(); i++) { + if (i > 0) result += "\n"; + result += errors_[i]; + } + return result; + } + + // Clear error stack + void clear_errors() { + errors_.clear(); + has_error_ = false; + } + + // Read n bytes into destination buffer + bool read(size_t n, uint8_t* dst) { + if (!stream_.read(n, dst)) { + add_error("Failed to read " + std::to_string(n) + " bytes at position " + + std::to_string(stream_.tell())); + return false; + } + return true; + } + + // Read 1 byte + bool read1(uint8_t* dst) { + if (!stream_.read1(dst)) { + add_error("Failed to read 1 byte at position " + + std::to_string(stream_.tell())); + return false; + } + return true; + } + + // Read 2 bytes with endian swap + bool read2(uint16_t* dst) { + if (!stream_.read2(dst)) { + add_error("Failed to read 2 bytes at position " + + std::to_string(stream_.tell())); + return false; + } + return true; + } + + // Read 4 bytes with endian swap + bool read4(uint32_t* dst) { + if (!stream_.read4(dst)) { + add_error("Failed to read 4 bytes at position " + + std::to_string(stream_.tell())); + return false; + } + return true; + } + + // Read 8 bytes with endian swap + bool read8(uint64_t* dst) { + if (!stream_.read8(dst)) { + add_error("Failed to read 8 bytes at position " + + std::to_string(stream_.tell())); + return false; + } + return true; + } + + // Read a null-terminated string up to max_len bytes + // Returns false if no null terminator found within max_len + bool read_string(std::string* str, size_t max_len = 256) { + if (!str) { + add_error("Null pointer passed to read_string"); + return false; + } + + str->clear(); + size_t start_pos = stream_.tell(); + + for (size_t i = 0; i < max_len; i++) { + uint8_t c; + if (!stream_.read1(&c)) { + add_error("Failed to read string at position " + std::to_string(start_pos)); + return false; + } + if (c == '\0') { + return true; + } + str->push_back(static_cast(c)); + } + + add_error("String not null-terminated within " + std::to_string(max_len) + + " bytes at position " + std::to_string(start_pos)); + return false; + } + + // Seek to absolute position + bool seek(size_t pos) { + if (!stream_.seek(pos)) { + add_error("Failed to seek to position " + std::to_string(pos)); + return false; + } + return true; + } + + // Seek relative to current position + bool seek_relative(int64_t offset) { + size_t current = stream_.tell(); + int64_t new_pos = static_cast(current) + offset; + + if (new_pos < 0) { + add_error("Seek would move before start of stream"); + return false; + } + + return seek(static_cast(new_pos)); + } + + // Rewind to beginning + void rewind() { + stream_.rewind(); + } + + // Get current position + size_t tell() const { + return stream_.tell(); + } + + // Get remaining bytes + size_t remaining() const { + return stream_.remaining(); + } + + // Check if at end + bool eof() const { + return stream_.eof(); + } + + // Get total length + size_t length() const { + return stream_.length(); + } + + // Add a custom error message + void add_error(const std::string& msg) { + errors_.push_back(msg); + has_error_ = true; + } + + // Get direct access to underlying StreamReader (use with caution) + const StreamReader& stream() const { return stream_; } + +private: + StreamReader stream_; + std::vector errors_; + bool has_error_; +}; + +} // namespace tinyexr + +#endif // TINYEXR_EXR_READER_HH_ diff --git a/encoder/3rdparty/streamreader.hh b/encoder/3rdparty/streamreader.hh new file mode 100644 index 0000000..3dbf278 --- /dev/null +++ b/encoder/3rdparty/streamreader.hh @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2025, Syoyo Fujita and many contributors. +// All rights reserved. +// +// StreamReader: Simple header-only stream reader with endian support +// +// Part of TinyEXR V2 API (EXPERIMENTAL) + +#ifndef TINYEXR_STREAMREADER_HH_ +#define TINYEXR_STREAMREADER_HH_ + +#include +#include + +namespace tinyexr { + +enum class Endian { + Little, + Big, + Native +}; + +class StreamReader { +public: + // Constructor: takes memory address, length, and endianness + StreamReader(const uint8_t* data, size_t length, Endian endian = Endian::Little) + : data_(data), length_(length), pos_(0), endian_(endian) { + // Detect native endian + uint16_t test = 0x0001; + native_is_little_ = (*reinterpret_cast(&test) == 0x01); + + // Determine if we need to swap bytes + if (endian_ == Endian::Native) { + needs_swap_ = false; + } else { + bool data_is_little = (endian_ == Endian::Little); + needs_swap_ = (data_is_little != native_is_little_); + } + } + + // Read n bytes into destination buffer + // Returns false on out-of-bounds or error + bool read(size_t n, uint8_t* dst) { + if (!dst || n == 0) { + return false; + } + if (pos_ + n > length_) { + return false; // Out of bounds + } + std::memcpy(dst, data_ + pos_, n); + pos_ += n; + return true; + } + + // Read 1 byte (uint8_t) + bool read1(uint8_t* dst) { + return read(1, dst); + } + + // Read 2 bytes (uint16_t) with endian swap if needed + bool read2(uint16_t* dst) { + if (!dst) { + return false; + } + uint8_t buf[2]; + if (!read(2, buf)) { + return false; + } + + if (needs_swap_) { + *dst = static_cast(buf[1]) << 8 | + static_cast(buf[0]); + } else { + std::memcpy(dst, buf, 2); + } + return true; + } + + // Read 4 bytes (uint32_t) with endian swap if needed + bool read4(uint32_t* dst) { + if (!dst) { + return false; + } + uint8_t buf[4]; + if (!read(4, buf)) { + return false; + } + + if (needs_swap_) { + *dst = static_cast(buf[3]) << 24 | + static_cast(buf[2]) << 16 | + static_cast(buf[1]) << 8 | + static_cast(buf[0]); + } else { + std::memcpy(dst, buf, 4); + } + return true; + } + + // Read 8 bytes (uint64_t) with endian swap if needed + bool read8(uint64_t* dst) { + if (!dst) { + return false; + } + uint8_t buf[8]; + if (!read(8, buf)) { + return false; + } + + if (needs_swap_) { + *dst = static_cast(buf[7]) << 56 | + static_cast(buf[6]) << 48 | + static_cast(buf[5]) << 40 | + static_cast(buf[4]) << 32 | + static_cast(buf[3]) << 24 | + static_cast(buf[2]) << 16 | + static_cast(buf[1]) << 8 | + static_cast(buf[0]); + } else { + std::memcpy(dst, buf, 8); + } + return true; + } + + // Seek to absolute position + // Returns false if position is out of bounds + bool seek(size_t pos) { + if (pos > length_) { + return false; + } + pos_ = pos; + return true; + } + + // Rewind to beginning + void rewind() { + pos_ = 0; + } + + // Get current position + size_t tell() const { + return pos_; + } + + // Get remaining bytes + size_t remaining() const { + return length_ - pos_; + } + + // Check if at end + bool eof() const { + return pos_ >= length_; + } + + // Get total length + size_t length() const { + return length_; + } + +private: + const uint8_t* data_; + size_t length_; + size_t pos_; + Endian endian_; + bool native_is_little_; + bool needs_swap_; +}; + +} // namespace tinyexr + +#endif // TINYEXR_STREAMREADER_HH_