| // 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 <cstdint> | |
| #include <cstring> | |
| 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<uint8_t*>(&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<uint16_t>(buf[1]) << 8 | | |
| static_cast<uint16_t>(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<uint32_t>(buf[3]) << 24 | | |
| static_cast<uint32_t>(buf[2]) << 16 | | |
| static_cast<uint32_t>(buf[1]) << 8 | | |
| static_cast<uint32_t>(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<uint64_t>(buf[7]) << 56 | | |
| static_cast<uint64_t>(buf[6]) << 48 | | |
| static_cast<uint64_t>(buf[5]) << 40 | | |
| static_cast<uint64_t>(buf[4]) << 32 | | |
| static_cast<uint64_t>(buf[3]) << 24 | | |
| static_cast<uint64_t>(buf[2]) << 16 | | |
| static_cast<uint64_t>(buf[1]) << 8 | | |
| static_cast<uint64_t>(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_ |