Random-Access Input¶
OpenMeta’s random-access input contract is designed for image-processing and transcoding hosts that already own storage, scheduling, and I/O policy. It is not tied to a particular image library or file abstraction.
The positional source primitive, top-level snapshot assembly, and structured read diagnostics are stable in Host Adoption Profile. Low-level format-specific scanner, payload, and decoder APIs remain experimental.
Current scope¶
Version 0.4.100 added the allocation-free source primitive in
openmeta/random_access_source.h. Version 0.4.101 added source ranges,
caller-owned read windows, and the first decoder conversion. Version 0.4.102
added source-backed nested TIFF offset resolution. Version 0.4.103 extends the
conversion to Olympus, Panasonic, and Samsung MakerNotes. Version 0.4.104 adds
bounded Fujifilm and General Imaging MakerNote source layouts. Version 0.4.105
adds Kodak fixed-layout and outer-TIFF-relative MakerNotes. Version 0.4.106 adds
Ricoh mixed-base and vendor subdirectory decoding plus Nintendo, Casio, Minolta,
and FLIR callback parity. Version 0.4.107 adds bounded JPEG segment scanning.
Version 0.4.108 adds positional PNG/WebP chunk scanning and bounded JP2/JXL/
ISO-BMFF box and metadata-item traversal. Version 0.4.109 adds positional GIF
extension scanning, EXR header traversal, logical metadata payload extraction,
and decoded source-snapshot assembly. Version 0.4.111 adds native RAF, X3F, and
CRW/CIFF positional metadata traversal plus structured snapshot-read
diagnostics. Version 0.4.112 adds bounded RAF preview-JPEG and FujiIFD traversal
plus X3F section-JPEG traversal. Version 0.4.113 stabilizes the source primitive,
top-level snapshot assembly, and diagnostics through Host Adoption Profile v1.
It provides:
a fixed source size and synchronous
read_at(offset, destination)callbacka non-owning descriptor for caller-owned contiguous memory
distinct exact-read, short-read, I/O, cancellation, and source-change results
per-operation request-count, total-byte, and single-read ceilings
independent sticky accounting state with first-failure diagnostics
borrowed source subranges and caller-owned read-ahead windows
direct zero-copy views for contiguous sources
decode_exif_tiff_random_access(...) now traverses classic TIFF, BigTIFF,
DNG, Panasonic RW2, and Olympus ORF headers, IFDs, pointer directories, and
validated metadata values without materializing the complete source. PrintIM,
GeoTIFF, Pentax DNG private data, and selected self-contained MakerNotes are
also decoded from caller scratch. Nikon embedded TIFF/type 1, Sony outer-TIFF
IFDs, contained Canon adjusted-base payloads, Olympus nested IFDs, Panasonic
binary tables, Samsung STMN/Type2 derived tables, Fujifilm self-relative IFDs,
General Imaging Type 2 source windows, and Kodak Type 8, Type 10, and Type 11
IFDs with vendor subtables use the same bounded source. Ricoh classic notes
retain ImageInfo, CameraInfo, FaceInfo, SerialInfo, and Theta expansion, while
Nintendo CameraInfo, Casio QVC/DCI, Minolta binary tables, and FLIR
outer-TIFF-relative values retain their contiguous behavior.
Callback decoding still reports Canon derived subtables whose values extend
outside the declared MakerNote, plus unknown or unsupported vendor layouts,
through nested_payloads_skipped and complete() rather than decoding
against an incorrect subspan. Contiguous sources retain the complete existing
nested-decoder behavior.
scan_jpeg_random_access(...) locates leading EXIF, XMP, ICC, MPF, vendor
APP, JUMBF, Photoshop IRB, FLIR, and comment segments through the same
positional source. It reads at most 512 bytes from a metadata segment for
classification, preserves multipart APP11 normalization, and stops at Start of
Scan without reading entropy-coded image data. Returned offsets are relative to
the supplied source range.
scan_png_random_access(...), scan_webp_random_access(...),
scan_gif_random_access(...),
scan_jp2_random_access(...), scan_jxl_random_access(...), and
scan_bmff_random_access(...) provide the same descriptor contract. PNG text
prefixes are scanned incrementally. GIF traversal reads extension framing and
sub-block lengths while skipping raster sub-block contents. JP2/JXL/BMFF
traversal reads structural boxes and metadata tables while skipping image
codestream, mdat, and unrelated payload bytes. These six scanners require a
32-byte minimum window; larger windows reduce callback traffic.
decode_exr_header_random_access(...) traverses EXR attributes and stops at
the header terminator without reading chunk tables or pixel data. Structural
reads use caller-owned read-window storage. The largest selected attribute value
must fit caller value scratch; value_scratch_needed reports its exact size.
measure_exr_header_random_access(...) validates the structure without
fetching attribute bodies.
extract_payload_random_access(...) fetches one discovered logical metadata
stream. It supports direct ranges, GIF sub-blocks, multipart JPEG ICC and
extended XMP, general multipart blocks, and bounded Deflate/Brotli
decompression through caller-owned compressed and output storage.
Explicitly typed RAF, X3F, and CRW sources use native positional readers. RAF
reads its fixed header and declared native directories, X3F reads its header,
section directory, and PROP sections, and CRW follows CIFF directory
offsets and individual values. These paths do not read intervening image
payload ranges. When embedded-container decoding is requested,
scan_raf_random_access(...) follows the header-declared preview JPEG and
FujiIFD/TIFF range, while scan_x3f_random_access(...) follows declared
IMA2/IMAG SECi JPEG sections. Both stop at Start of Scan without
fetching entropy-coded image data.
Callback example¶
#include "openmeta/random_access_source.h"
struct HostReader {
// Host-owned file, range, asset, or proxy state.
};
openmeta::RandomAccessIoResult
host_read_at(void* context, uint64_t offset,
std::span<std::byte> destination) noexcept
{
HostReader* reader = static_cast<HostReader*>(context);
return {/* code */, /* bytes_read */};
}
HostReader reader;
openmeta::RandomAccessSource source =
openmeta::make_callback_random_access_source(
source_size, &reader, host_read_at, true);
openmeta::RandomAccessReadLimits limits;
limits.max_requests = 4096;
limits.max_total_bytes = 32ULL * 1024ULL * 1024ULL;
limits.max_single_read_bytes = 4ULL * 1024ULL * 1024ULL;
openmeta::RandomAccessReadState state;
std::array<std::byte, 16> header;
openmeta::RandomAccessReadCode code =
openmeta::random_access_read_exact(source, 0, header, &state, limits);
random_access_read_exact(...) does not retry a short read. A host using a
partial-read transport must complete retries inside the callback or return the
actual shorter count. OpenMeta performs range and budget checks before invoking
the callback.
TIFF/DNG decode¶
std::array<std::byte, 16 * 1024> structural_window;
std::array<std::byte, 1 * 1024 * 1024> value_scratch;
openmeta::ExifRandomAccessScratch scratch;
scratch.read_window = structural_window;
scratch.value = value_scratch;
scratch.window_options.minimum_read_bytes = structural_window.size();
openmeta::RandomAccessSourceRange range =
openmeta::make_random_access_source_range(
source, tiff_offset, tiff_size);
openmeta::MetaStore store;
std::array<openmeta::ExifIfdRef, 128> ifds;
openmeta::ExifRandomAccessDecodeResult result =
openmeta::decode_exif_tiff_random_access(
range, store, ifds, scratch,
openmeta::ExifDecodeOptions{}, limits);
The structural window must hold at least 20 bytes for BigTIFF. Larger windows
batch adjacent reads. The value scratch holds one out-of-line metadata value or
the combined GeoTIFF parameter payloads. value_scratch_needed reports an
insufficient buffer without hidden allocation.
Container scan¶
#include "openmeta/container_scan.h"
std::array<std::byte, 512> scan_window;
openmeta::ContainerRandomAccessScratch scan_scratch;
scan_scratch.read_window = scan_window;
scan_scratch.window_options.minimum_read_bytes = scan_window.size();
openmeta::RandomAccessSourceRange jpeg =
openmeta::make_random_access_source_range(
source, jpeg_offset, jpeg_size);
std::array<openmeta::ContainerBlockRef, 32> blocks;
openmeta::ContainerRandomAccessScanResult scan =
openmeta::scan_jpeg_random_access(
jpeg, blocks, scan_scratch, limits);
if (!scan.complete()) {
// Inspect scan.input for source, scratch, or resource-limit failure.
}
if (scan.scan.status != openmeta::ScanStatus::Ok) {
// Source I/O completed, but the JPEG is unsupported or malformed.
}
The 512-byte window preserves bare APP1 XMP detection parity. Smaller windows
work when every required probe fits and otherwise return ScratchTooSmall.
measure_scan_jpeg_random_access(...) reports scan.needed without block
output storage.
PNG, WebP, GIF, JP2, JXL, and ISO-BMFF use the corresponding
scan_*_random_access(...) and measure_scan_*_random_access(...) pairs.
They require at least 32 bytes of read-window storage. A 4 KiB or larger window
is generally a better host default for table-heavy BMFF files.
Bounded payload and snapshot assembly¶
#include "openmeta/metadata_transfer.h"
std::array<openmeta::ContainerBlockRef, 64> blocks;
std::array<openmeta::ExifIfdRef, 128> ifds;
std::array<uint32_t, 64> payload_indices;
std::array<std::byte, 4 * 1024> read_window;
std::array<std::byte, 2 * 1024 * 1024> payload;
std::array<std::byte, 2 * 1024 * 1024> compressed_payload;
std::array<std::byte, 1 * 1024 * 1024> value;
openmeta::ReadTransferSourceSnapshotRandomAccessScratch scratch;
scratch.blocks = blocks;
scratch.ifds = ifds;
scratch.payload_indices = payload_indices;
scratch.read_window = read_window;
scratch.payload = payload;
scratch.compressed_payload = compressed_payload;
scratch.value = value;
openmeta::ReadTransferSourceSnapshotRandomAccessResult result =
openmeta::read_transfer_source_snapshot_random_access(
source_range, openmeta::ContainerFormat::Jpeg,
scratch, {}, limits);
The scanner, payload, decompression, and value workspaces are caller-owned and
valid only for the call. The returned TransferSourceSnapshot owns its
finalized MetaStore, so those workspaces may be released or reused afterward.
Request-count and byte ceilings are cumulative across scan, payload, decode,
PNG text-prefix, and optional raw-carrier reads.
The high-level positional path supports JPEG, PNG, WebP, GIF, JP2, JXL,
HEIF/AVIF/CR3 BMFF containers, native TIFF/DNG-family input, EXR headers, and
native RAF, X3F, and CRW metadata. RAF/X3F embedded decoding follows declared
preview, FujiIFD, and section-JPEG ranges without searching arbitrary image
bytes. A missing declared lane that would require fallback signature hunting,
selected source-wide BMFF enrichment, unsupported MakerNote subpaths, and
whole-file raw-carrier preservation increment
residual_metadata_paths. The decoded snapshot remains usable, but
complete() returns false.
Project machine-readable diagnostics without allocation:
std::array<openmeta::ReadTransferSourceDiagnostic, 8> diagnostics;
openmeta::ReadTransferSourceDiagnosticOptions diagnostic_options;
diagnostic_options.decode_makernote_requested = options.decode_makernote;
diagnostic_options.decode_embedded_containers_requested =
options.decode_embedded_containers;
openmeta::ReadTransferSourceDiagnosticsResult diagnostic_result =
openmeta::collect_read_transfer_source_diagnostics(
result, diagnostics, diagnostic_options);
Each record has severity, stable code, domain, format, source offset, required
byte count, item count, EXIF/native tag, and original input failure code where
available. Name and short-message helpers return static strings. If written
differs from needed, resize the caller buffer and project again; the
snapshot read itself is not repeated.
Raw-carrier preservation is opt-in and copies only discovered carrier bytes
within max_raw_carrier_bytes. Ordinary decoded snapshot assembly does not
retain the whole source, and transfer still uses the normal safety policy and
decoded re-emission rules.
Real-time and concurrent use¶
The positional source, scanner, decoder, and payload layers add no global state,
virtual dispatch, std::function, hidden allocation, file-position mutation,
locking, or background work. The high-level snapshot result intentionally owns
its MetaStore and optional raw-carrier bytes. The callback is a plain
function pointer, and all counters belong to operation-local state.
OpenMeta does not issue concurrent callbacks within one synchronous scan or
decode.
Separate operations may share an immutable source when concurrent_reads is
true and the host context supports concurrent positional reads. Each operation
must use separate decoder scratch and accounting state.
The TIFF/DNG decoder batches adjacent structural reads through caller-owned scratch and keeps out-of-line values in separate caller storage so they do not evict the structural cache. Pixel and media payloads are outside this metadata contract and are not read by positional container scanning.
Source lifetime¶
RandomAccessSource is borrowed. Backing memory, callback, and context must
remain valid and immutable for the complete operation. If a callback detects
replacement, truncation, or another size-changing mutation, it must return
RandomAccessIoCode::SourceChanged.
Use the memory descriptor when the host already owns the complete encoded asset. Use the callback descriptor for file handles, range-backed storage, host asset APIs, and I/O proxy objects.