vercel-labs / vercel-labs/native

canvas: `image_src` is not on `ui.ElementOptions`, so apps cannot atlas and the 16-slot registry becomes the ceiling

Open Beginner friendly
#387 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Zig
Stars
7.7k
Forks
314
Avg merge
5h
Merged PRs (30d)
13

Description

The problem

ui.ElementOptions cannot set image_src, so an app cannot draw a sub-rectangle of a registered image. That makes a texture atlas impossible from app code, and the 16-slot registry becomes a hard ceiling on how many distinct images an app can show.

The renderer already supports it. Widget carries the field and the draw path consumes it:

  • src/primitives/canvas/widgets.zig:1206image_src: ?geometry.RectF = null on the widget options
  • src/primitives/canvas/widgets.zig:1289.image_src = options.image_src, passed straight through
  • src/primitives/canvas/widget_render.zig:2696.src = widget.image_src on the emitted DrawImage
  • src/primitives/canvas/widget_invalidation.zig:315 — already compared for damage

The app-facing options struct exposes only the id:

  • src/primitives/canvas/ui.zig:581image: canvas.ImageId = 0, and no image_src anywhere in ElementOptions
  • src/primitives/canvas/ui.zig:2103pub fn image(self: *Self, options: ElementOptions) Node

So the capability exists end to end except for one field on the struct the app fills in.

The fix

Add image_src: ?geometry.RectF = null to ui.ElementOptions and forward it where the other image fields are already forwarded, alongside image_fit and image_sampling. As far as I can tell nothing else has to change: the widget field, the render path and the invalidation comparison are all in place.

Markup would want an attribute too, though the Zig-view path alone is enough to unblock this, and a rect in markup may not be worth the grammar.

Why it is worth doing

It lifts the practical image ceiling without touching the fixed-capacity design. canvas_limits.zig:107 sets max_registered_canvas_images = 16, sized in its own comment for "avatar and cover-art scale". With a source crop, one 512x512 slot holds 64 avatars at 64x64, so the same 16 slots cover roughly a thousand small images. The budget stays frozen, the preallocation stays predictable, and the loud-overflow behaviour is unchanged. Nothing about the philosophy moves; the same memory just goes further.

It is the difference between viable and not for one class of app. I am building a Nostr client. A social feed shows dozens of small images at once: a face on every row, a gallery in a note, an icon per community in a sidebar. Today those compete for 16 slots app-wide, so I run an LRU over them and faces still fall back to initials on a dense screen. The workarounds all cost something real. Splitting the budget by kind strands capacity, evicting aggressively makes avatars flicker as you scroll, and drawing letter tiles instead of pictures is just not showing the image. An atlas removes the problem rather than rationing it.

It is cheap for you and it composes with what already shipped. 0.9.2 made the per-image budget app-settable through app.zon (max_image_pixel_bytes, types.zig:666), which fixed the size half of this. This is the count half, and it needs no new budget, no new manifest key and no new platform code.

It also helps the cases you already target. Sprite sheets for a toolbar, a spritemap of state icons, frames of a small animation, nine-slice art: all of them are one registration and many draws rather than one registration each.

Not asking for

A bigger registry, a dynamic one, or any change to how overflow behaves. Just the field.

Contributor guide

Open the contributing guide

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 in src/primitives/canvas/ui.zig at ElementOptions and the image function around line 2103. Compare how image_fit and image_sampling are forwarded, then trace the existing widget and render references in widgets.zig and widget_render.zig. Done means app-facing options can provide the source rectangle and the existing canvas behavior handles it without changes to registry limits.

Written by the indexing model from the issue text.

Assessment

Tech stack
zig
Domain
frontend
Issue type
Feature
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
85/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.