imazen / imazen/zenresize

Streaming mask support: avoid f32 path and full materialization for pixel-wise masks

Open
#3 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
2
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Summary

Masks in zenresize (via with_mask()) currently force the f32 pipeline path. Since all mask types (RoundedRect, LinearGradient, RadialGradient) compute values per-pixel per-row, there's no fundamental reason they can't work in the i16 paths too.

Additionally, zenfilters' Masked filter wrapper operates on full OklabPlanes buffers, but for row-local inner filters (exposure, contrast, saturation, tone curves — anything that doesn't need spatial neighborhood), the save→apply→mask→blend cycle could stream row-by-row:

  1. Save original row
  2. Apply filter to row (row-local filters only)
  3. Compute mask values for that row (all mask types are y-addressable)
  4. Blend: result = original * (1 - mask) + filtered * mask
  5. Emit row

What this enables

  • i16 mask path in zenresize: per-pixel lerp in i16 space ((a * (256 - m) + b * m + 128) >> 8 for u8 masks). Avoids f32 conversion overhead for the common case of rounded corners + solid background → JPEG.
  • Streaming masked filters in zenfilters/zenpipe: color grading + gradient mask could stream without materializing the full image. Only non-row-local inner filters (blur, warp, sharpen with large radius) would still need full materialization.
  • Composable with zenresize's existing streaming pad/crop/orient: mask application slots in after resize, before orient — same position it's in now, just without forcing f32.

Non-goals

  • Masks that depend on image content (future content-aware masks) are inherently non-streaming.
  • Non-row-local inner filters (blur, warp) still need full materialization regardless of mask streaming.

Relevant code

  • zenresize/src/streaming.rswith_mask() forces f32 path
  • zenblend/src/mask.rsMaskSource trait, MaskSpans::for_row(y, width)
  • zenfilters/src/masked.rsMasked wrapper, full-image save→apply→blend
  • zenfilters/src/filter.rsFilter::is_neighborhood() / neighborhood_radius() — already distinguishes row-local from spatial filters

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with zenresize/src/streaming.rs to trace how with_mask() selects the f32 path, then read zenblend/src/mask.rs and MaskSpans::for_row(y, width). Review zenfilters/src/masked.rs and zenfilters/src/filter.rs to distinguish row-local from neighborhood filters. Done means i16 mask processing and row-by-row masked filtering work without full materialization where the stated constraints allow it.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
computer-graphics, performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.