# of::Image / of::Pixels Design Specification
# Version: 0.1.0-draft
# Status: Design crystallization for LLM context
metadata:
title: "Modern ofWorks Image Container"
namespace: "of"
related_types: ["Pixels<T>", "Image_<T>", "Image", "Image16", "ImageF"]
dependencies: ["ofTexture", "mango/image", "filesystem"]
design_principles:
- explicit_over_implicit: "Conversions must be explicit, no silent data loss"
- type_safety: "Bit depth encoded in type, compile-time checked"
- lazy_evaluation: "Texture created on-demand, not at load"
- modern_cpp: "Iterators, variants, templates, no raw pointers"
core_types:
Pixels<T>:
description: "Typed pixel buffer, the foundation"
template_param: "T = uint8_t | uint16_t | float"
static_members:
bits_per_channel: "sizeof(T) * 8"
constructors:
- default: "empty pixels"
- sized: "width, height, channels"
factories:
fromFile: "load with auto-detected bit depth, convert to T if needed"
fromFile8: "load as 8-bit"
fromFile16: "load as 16-bit"
fromFileF: "load as float"
accessors:
ptr: "raw data pointer"
row: "pointer to specific row (handles stride)"
width/height/numChannels: "dimensions"
bytesPerRow: "stride including padding"
methods:
to<Target>: "explicit type conversion"
asGrayscale: "channel view (no copy)"
asRGB: "channel view (no copy)"
iterators: "begin()/end() for range-for"
Image_<T>:
description: "Drawable image = Pixels<T> + lazy texture"
composition: ["Pixels<T> pixels", "mutable ofTexture texture", "bool textureDirty"]
constructors:
- default: "empty"
- from_pixels: "take ownership of Pixels<T>"
- from_file: "load and decode"
key_behavior: "texture created/updated only on first draw() if dirty"
methods:
load: "replace pixels, mark dirty"
save: "encode to file"
getPixels: "non-const marks dirty, const doesn't"
draw: "ensure texture, render"
to<Target>: "convert to different bit depth Image"
type_aliases:
Image: "Image_<> = Image_<uint8_t>"
Image16: "Image_<uint16_t>"
ImageF: "Image_<float>"
Pixels8: "Pixels<uint8_t>"
Pixels16: "Pixels<uint16_t>"
PixelsF: "Pixels<float>"
free_functions:
loadPixels:
overload_1: "path -> variant<Pixels8, Pixels16, PixelsF>"
overload_2: "template<T> path -> Pixels<T>"
loadImage:
"template<T=uint8_t> path -> Image_<T>"
conversion_behavior:
explicit_only: "template<T> to() const"
bit_depth_down: "e.g., 16->8: value >> 8 (divide by 256)"
bit_depth_up: "e.g., 8->16: value << 8 (multiply by 256)"
channel_mismatch: "error or configurable policy"
logging: "ofLogNotice on any conversion with file path"
texture_integration:
lazy_creation: "texture allocated on first draw()"
format_mapping:
uint8_t: "GL_RGBA8 or similar"
uint16_t: "GL_RGBA16 or GL_RGBA16F if supported"
float: "GL_RGBA32F"
fallback: "for unsupported 16-bit/float formats, convert to 8-bit with warning"
file_io:
backend: "mango/image"
preserve_depth: "loadPixels() returns variant with file's native depth"
auto_convert: "Image() constructor converts to 8-bit if needed (with notice)"
error_handling:
load_failure: "return empty Pixels/Image, ofLogError"
unsupported_conversion: "ofLogError, return empty or best effort"
texture_creation_failure: "ofLogError, subsequent draw() is no-op"
migration_from_legacy:
ofPixels: "Pixels8 (drop-in replacement)"
ofShortPixels: "Pixels16"
ofFloatPixels: "PixelsF"
ofImage: "Image"
ofShortImage: "Image16"
ofFloatImage: "ImageF"
ofLoadImage: "loadImage<T>() or loadPixels<T>()"
open_questions:
- "Should Image::load() throw on failure instead of returning bool?"
- "How to handle HDR display on non-HDR displays (tone mapping)?"
- "Should Pixels support memory-mapped files for huge images?"
- "GPU pixel buffer objects for faster upload?"
- "Integration with ofVideoPlayer frame extraction?"
example_usage:
simple_display: |
of::Image img("photo.png");
img.draw(0, 0);
preserve_hdr: |
of::Image16 hdr("raw_16bit.tif");
// process at full precision...
hdr.draw(0, 0); // auto-uploads 16-bit texture if supported
explicit_conversion: |
of::Image16 hdr("raw.tif");
of::Image ldr = hdr.to<uint8_t>(); // explicit downsample
flexible_loading: |
auto pix = of::loadPixels("mystery.tif");
std::visit([](auto&& p) {
ofLogNotice() << "Loaded " << sizeof(p.value_type)*8 << "bit image";
}, pix);
pixel_processing: |
auto pix = of::Pixels16::fromFile("raw.tif");
for (auto& p : pix) p = std::min(p * 2, 65535);
of::Image16 img(pix);
img.draw(0, 0);
implementation_notes:
mango_integration: "Use mango::image::Bitmap for loading, detect format.bits"
stride_handling: "Always use row-by-row copy for non-8-bit to handle alignment"
texture_cache: "mutable ofTexture, recreated only if pixels modified"
shared_ptr_storage: "Pixels data in shared_ptr for cheap copying"
related_discussions:
- "2026-02-24: 16-bit stride handling bug in mango port"
- "2026-02-24: Explicit conversion vs silent data loss debate"
- "Future: of::gl namespace for low-level texture access"