Skip to content

Plan tiled final rendering support in BrightRoomImageRenderer #284

Description

@muukii

Context

Brightroom's display path can usually survive large images because the UI works with scaled previews. The fragile part is final rendering/export, where the renderer currently materializes a full-size CGImage.

The current renderer contract returns Rendered(CGImage), so it cannot represent an output that is larger than available CPU/GPU memory. In the full-feature path, BrightRoomImageRenderer builds a lazy CIImage graph, but then creates one full CGImage for the whole effected_CIImage.extent before crop/drawing/resize. The crop-only path also loads and orients a full original CGImage.

Goal

Add a renderer-level tiled rendering contract for final output/export, without moving this responsibility into the display layer.

The key design point is: do not require the final output to exist as one in-memory image. Render and deliver bounded tiles instead.

Proposed Direction

Introduce a separate API alongside the existing render() -> Rendered API, for example:

renderer.renderTiles(
  options: options,
  tileSize: CGSize(width: 1024, height: 1024)
) { tile, tileRect in
  // write to a file, tile cache, or other consumer
}

Keep the existing render() behavior as the compatibility API. Internally, it can eventually share the same preparation pipeline and simply materialize the full extent for normal-sized images.

Implementation Plan

  1. Add a preparation layer that produces a lazy render graph instead of immediately creating a full CGImage.
struct PreparedRender {
  let image: CIImage
  let outputExtent: CGRect
  let workingFormat: CIFormat
  let colorSpace: CGColorSpace?
}
  1. Move filter application into this preparation layer while keeping the CIImage in full output/source coordinates.

  2. Apply crop/orientation/output transforms in the CI graph before materialization where possible.

  3. Add a tile iterator over outputExtent.

  4. For each tile, call CIContext.createCGImage(prepared.image, from: tileRect, ...) and pass the result to a consumer. The renderer should not accumulate all tiles in memory.

  5. Add an explicit error or unsupported path for edits that still require whole-image materialization.

  6. Later, make drawing/mask operations tile-aware, especially BlurredMask, which currently relies on context.makeImage() and full-context drawing.

Important Constraints

  • Do not apply filters to a pre-cropped tile image as the primary model. Some filters depend on the full image extent, such as blur radius and vignette radius. Build the full-coordinate CIImage graph first, then materialize only the requested tile rectangle.
  • render() -> Rendered(CGImage) cannot support truly huge outputs by itself. Tiled rendering needs a streaming/consumer contract.
  • For UIImage/CGImage backed sources, the source may already be fully resident in memory. True huge-image support also benefits from lazy sources such as CGImageSource and may eventually need a source contract that can avoid full decode.
  • Drawing and blurred masks need separate treatment. The first milestone can support filter/crop-only tiled rendering and fail explicitly for unsupported drawing edits.

Acceptance Criteria

  • Existing render() behavior remains unchanged for current callers.
  • A new tiled rendering API can cover the full output extent with fixed-size tiles.
  • Peak memory is bounded by tile size plus renderer overhead, not by full output dimensions, for supported edit paths.
  • Tiled output matches the existing full render for moderate-size images in tests.
  • Unsupported whole-image-only edit paths fail with a clear renderer error instead of silently falling back to full materialization.

Non-goals

  • Do not redesign the preview/display path as part of this work.
  • Do not require all filters/drawings to become tile-safe in the first milestone.
  • Do not replace the existing full-image Rendered API immediately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions