Skip to content

New RawImage control: full-bandwidth pixel-frame streaming over DataChannels#6674

Merged
FeodorFitsner merged 7 commits into
flet-0.86from
raw-image
Jul 13, 2026
Merged

New RawImage control: full-bandwidth pixel-frame streaming over DataChannels#6674
FeodorFitsner merged 7 commits into
flet-0.86from
raw-image

Conversation

@FeodorFitsner

@FeodorFitsner FeodorFitsner commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Generalizes the matplotlib raw-frame pipeline (#6673) into a core ft.RawImage control — a persistent pixel surface the app pushes frames onto over a dedicated DataChannel, for Pillow output, numpy arrays, camera frames and procedural graphics at interactive frame rates.

Updating Image.src resends the whole blob through the msgpack control-patch protocol and forces a full client-side re-decode on every change. RawImage instead streams frames over a DataChannel with awaitable render() methods that resolve on the client's frame-applied ack, so a plain while True: await ri.render(...) loop self-paces to display speed.

Design

  • Wire format identical to MatplotlibChartCanvas: 0x04 raw premultiplied RGBA8888 [w u32 LE][h u32 LE][pixels], 0x01 encoded image bytes, 0x03 clear, 0xFF ack (Dart → Python).
  • Transport-aware: raw pixels on local_data_transport connections (desktop, flet run, Pyodide); automatic PNG fallback encoded off the event loop on remote flet-web. Opaque frames PNG-encode losslessly; non-opaque premultiplied raw input falls back to raw with a documented caveat.
  • API: render(pil_or_array, premultiplied=False) (PIL duck-typed, __array_interface__ for numpy), render_rgba(w, h, bytes), render_encoded(png_jpeg_webp_bytes), clear(); fit, filter_quality, scale, ready_timeout properties.
  • Premultiplied alpha (Flutter's rgba8888 upload expects it) handled in Pillow's C loops (ImageChops.multiply per band) with a getextrema() opaque fast-path; the numpy path reuses the Agg backend's uint16 math. Pillow and numpy remain optional via lazy imports.
  • Last frame retained and replayed on widget remount, so the image survives page rebuilds like Image.src.
  • Dart widget holds at most one live ui.Image (web-safe by construction) and renders through Flutter's built-in RawImage, giving Image-consistent fit/layout semantics.

Tests

  • 9 unit tests: packet layout, opaque detection, premultiply math incl. PIL↔numpy parity.
  • 9 integration tests with goldens against the real client, including a premultiply proof (semi-transparent disc over a colored background), real PNG/JPEG files from disk, numpy RGB padding, fit, clear, and an ack-backpressure burst.

Examples & docs

Five gallery examples (registered in gallery.yaml under Displays/RawImage): photo_viewer (encoded network JPEGs), plasma (FPS counter, detail slider), paint (supersampled anti-aliasing, dirty-flag render loop), mandelbrot (click-to-zoom, letterbox-aware tap mapping), game_of_life (pointer cell drawing). New rawimage.md docs page with "RawImage vs Image" guidance and a generated docs image.

Summary by Sourcery

Introduce a RawImage control that streams pixel frames over a dedicated data channel for high-frequency image updates, with full client/server plumbing, tests, examples, and docs.

New Features:

  • Add cross-platform RawImage control to stream Pillow, NumPy, raw RGBA, and encoded image frames at interactive rates via DataChannel.
  • Expose RawImage from the Python SDK and Flutter extension so it can be used like other Flet controls.
  • Provide multiple RawImage gallery examples including photo viewer, plasma animation, paint app, Mandelbrot explorer, and Game of Life.

Enhancements:

  • Implement transport-aware handling for RawImage to use raw RGBA pixels on local connections and PNG encoding on remote web clients.
  • Add backpressure and ack handling in RawImage so render loops self-pace to the client display speed.
  • Ensure RawImage preserves the last frame across widget remounts for Image-like persistence.

Documentation:

  • Add RawImage control documentation page with guidance on when to use RawImage vs Image and embedded example code.

Tests:

  • Add unit tests for RawImage packet layout, alpha premultiplication helpers, and NumPy parity.
  • Add integration tests exercising RawImage rendering paths (RGBA, Pillow, NumPy, encoded files), layout options, clear behavior, and sequential renders, plus an example screenshot for docs.

Chores:

  • Register RawImage in gallery.yaml, website sidebars, and per-example pyproject metadata for discovery in the examples gallery.

…hannels

Generalize the matplotlib raw-frame pipeline into a core control for
Pillow output, numpy arrays, camera frames and procedural graphics.
Updating Image.src resends the whole blob through the msgpack protocol
and re-decodes it on every change; RawImage streams frames over a
dedicated DataChannel with awaitable render() methods that resolve on
the client's frame-applied ack, so producer loops self-pace to display
speed.

- Same wire format as MatplotlibChartCanvas: 0x04 raw premultiplied
  RGBA8888 on local transports, 0x01 encoded (auto PNG fallback,
  encoded off-loop) on remote web, 0x03 clear, 0xFF ack
- render() accepts PIL images (duck-typed) and __array_interface__
  arrays; Pillow and numpy stay optional via lazy imports
- Premultiplication runs in Pillow's C loops (ImageChops.multiply per
  band) with a getextrema() opaque fast-path; numpy path reuses the
  Agg backend's uint16 math
- Last frame is retained and replayed on widget remount so the image
  survives page rebuilds like Image.src
- Dart side holds at most one live ui.Image (web-safe) and renders
  through Flutter's RawImage for Image-consistent fit/filter_quality/
  scale semantics
- Unit tests for packet layout and premultiply math (PIL vs numpy
  parity), 8 integration tests with goldens incl. a premultiply proof
- Examples: plasma (fps counter, detail slider), paint (supersampled
  anti-aliasing, dirty-flag render loop), mandelbrot (click-to-zoom,
  letterbox-aware tap mapping), game_of_life (pointer cell drawing);
  registered in the gallery catalog
- Docs page with RawImage vs Image guidance
Cover displaying regular encoded images (not just synthetic frames):

- photo_viewer example: downloads network JPEGs and displays the bytes
  with render_encoded, with prev/next navigation and a byte cache
- Integration tests rendering real PNG (with transparency) and JPEG
  files from the test assets straight from disk, with goldens
- Docs: photo viewer section on the RawImage page

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 13, 2026

Copy link
Copy Markdown

Deploying flet-website-v2 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3095a7c
Status: ✅  Deploy successful!
Preview URL: https://07106316.flet-website-v2.pages.dev
Branch Preview URL: https://raw-image.flet-website-v2.pages.dev

View logs

DataChannel has no API entry in the docs data, so the :class: role
can't resolve; use inline code instead.
Add types pages and sidebar entries so :class: cross-references to
flet.DataChannel resolve; restore the role in the RawImage docstring.
…let-web client

send_data_channel_frame in the flet-web FastAPI transport dereferenced
the send queue after the WebSocket send loop cleared it on disconnect;
drop the frame instead, mirroring FletSocketServer.

Dropped frames also mean the frame-applied ack never arrives, which
would leave awaited render() loops hanging forever (Session.close does
not cancel user tasks). Add RawImage.ack_timeout (default 10s): a
render call now raises TimeoutError when the ack does not arrive, the
same exception producer loops already catch to exit. The abandoned ack
future is withdrawn so a late ack cannot resolve the wrong frame's
wait. Also make the local-transport probe safe on detached controls.
@FeodorFitsner FeodorFitsner merged commit d6aad17 into flet-0.86 Jul 13, 2026
50 of 204 checks passed
@FeodorFitsner FeodorFitsner deleted the raw-image branch July 13, 2026 18:29
@FeodorFitsner FeodorFitsner restored the raw-image branch July 13, 2026 18:47
@FeodorFitsner FeodorFitsner deleted the raw-image branch July 13, 2026 18:47
@FeodorFitsner FeodorFitsner mentioned this pull request Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant