Skip to content

RFC - Alternative to ofPixels / ofImage #50

@dimitre

Description

@dimitre
 # 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"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions