@@ -10,6 +10,108 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
1010
1111.. towncrier release notes start
1212
13+ 0.2.0 (2026-07-10)
14+ ==================
15+
16+ New Features
17+ ------------
18+
19+ - 2-D scalar images can now render on the **GPU via WebGPU ** (``imshow(...,
20+ gpu="auto"|True|False) ``): the image uploads as an R8 texture and a WGSL fragment
21+ shader applies the colormap LUT + contrast (clim) in one draw, replacing the
22+ per-pixel JavaScript colormap loop. Large images (≳1 megapixel) take the GPU path
23+ automatically; everything below the threshold, RGB images, ``gpu=False ``, and any
24+ device without WebGPU keep the identical Canvas2D path. ``plot.gpu_active `` reports
25+ which path ran. Verified on an NVIDIA Pascal GPU.
26+ - Arbitrarily large images can now display through **tile mode **
27+ (``imshow(..., tile="auto") ``): the figure shows a downsampled overview as its
28+ base and, after each zoom/pan settles, samples a high-resolution detail tile of
29+ just the visible region at panel resolution — deep zooms stay crisp without
30+ ever shipping the full-resolution frame. ``Plot2D.enable_tile `` /
31+ ``update_tile_source `` swap the underlying frame while the zoom and
32+ subselection persist (live-data contract), and a pluggable ``TileBackend ``
33+ (default: a fast vectorised numpy box-mean) lets out-of-core or GPU sources
34+ own the sampling.
35+ - Markers gained a ``clip_display `` option controlling whether they draw
36+ outside the current axes view or are clipped to it.
37+ - ``set_extent `` now updates the axes state (calibrated units / scale bar), so
38+ applying a calibrated extent after figure creation renders labelled axes
39+ instead of bare pixels.
40+ - Regular ``pcolormesh `` meshes are detected and rasterized to an image for
41+ display (fast IPF-style heatmaps) instead of drawing per-quad.
42+
43+
44+ Bug Fixes
45+ ---------
46+
47+ - Fixed a display freeze under the Electron binary pixel transport: the routing
48+ layer stripped the pixel key out of the slimmed geom JSON, so the renderer's
49+ "unchanged → skip re-upload" caches (Canvas2D blit cache, WebGPU texture,
50+ overlay-mask cache) fell back to a 4-sampled-byte fingerprint of the buffer —
51+ two frames differing anywhere else collided and the display stayed frozen on
52+ the old frame (seen as a stale overview after a movie scrub). The slimmed geom
53+ now carries a small ``\x00bin:<checksum> `` content token under the pixel key,
54+ binary buffers are additionally stamped with an arrival sequence as a fallback
55+ key, and the overlay-mask draw path now reads the binary byte side-channel
56+ (it previously only decoded base64, so masks never displayed over the binary
57+ transport).
58+ - Fixed the WebGPU 2-D image path sampling a vertically MIRRORED window when the
59+ view was panned off-centre: the shader applied a global ``1 - v `` flip after
60+ interpolating the ``[v0, v1] `` uv window, which sampled ``[1-v1, 1-v0] ``
61+ instead — correct only for a full or vertically-centred view. Symptoms: pan-y
62+ moved the image the wrong way on GPU-rendered panels, and markers/widgets
63+ (drawn by the shared Canvas2D overlay transform, which was always correct)
64+ appeared detached from the image features they marked. The base and
65+ detail-tile passes share the shader, so both are fixed. GPU-vs-CPU screenshot
66+ parity tests (zoom, pan, markers, widgets, detail tile) now run on real WebGPU
67+ in headless Chromium (``channel="chromium" `` + ``--enable-unsafe-webgpu ``) and
68+ skip on machines with no adapter.
69+ - ``Plot2D.set_data `` no longer makes a float64 copy of every incoming frame.
70+ The float64 cast now happens lazily in the ``.data `` property (the only reader),
71+ so a frame stream — e.g. scrubbing an in-situ movie — keeps the source dtype and
72+ skips a ~12 ms float64 copy of a 4k frame per tick. ``.data `` still returns a
73+ read-only float64 copy, unchanged for callers.
74+ - The Electron binary pixel transport now ships the RAW uint8 image bytes end to
75+ end, instead of base64-encoding them in ``set_data `` only to base64-decode them
76+ straight back in the routing layer. ``Plot2D.set_data `` stashes the raw bytes on
77+ the Figure's ``_raw_pixels `` side-table and leaves a tiny content-checksum
78+ change-token in ``image_b64 ``; ``_electron._route_change `` ships those bytes to a
79+ PLOTBIN frame directly. This removes the ~20 ms base64 encode, the ~17 ms decode,
80+ and the megabyte ``json.dumps `` of the pixel string from every scrub frame — a
81+ 2.2x faster ``set_data `` (≈98 ms → ≈44 ms on a 2048² frame) and ~25% less
82+ bytes-on-wire. Non-Electron hosts (Jupyter / Pyodide / standalone / ``save_html ``)
83+ are unchanged: they have no PLOTBIN channel, so the token is resolved back to
84+ inline base64 via ``Plot2D.resolve_pixel_tokens `` when the figure state is
85+ serialised for them.
86+ - Tile mode: a data update while zoomed in (``update_tile_source `` with a detail
87+ tile shown) refreshes only the detail tile, leaving the overview base on the
88+ old frame — zooming out then flashed the pre-update frame. The skipped
89+ overview is now marked stale and re-sampled once on the next view settle
90+ (riding the same push as the detail/clear), preserving the per-frame skip
91+ optimisation while never exposing stale base pixels.
92+ - Interactive zoom/pan on a 2-D image no longer re-serialises (and re-transmits)
93+ the full image on every mouse tick. The wheel/pan/orbit handlers write only the
94+ light *view * state back to the ``panel_<id>_json `` trait now, excluding the
95+ cached geometry (pixels, colormap LUT) that ``_applyGeom `` splices into the panel
96+ state for drawing. Previously the whole frame was ``JSON.stringify ``-d per tick —
97+ catastrophically so on the binary transport, where the pixel buffer is a
98+ ``Uint8Array `` that stringifies to a ``{"0":..,"1":..} `` object with one key per
99+ byte — which stalled zoom on large images.
100+ - Fixed a first-paint race under the Electron binary transport: binary
101+ side-table bytes that arrived before ``render() `` attached its listeners were
102+ stranded, leaving the first frame blank until the next update — they are now
103+ spliced into the initial paint.
104+
105+
106+ Maintenance
107+ -----------
108+
109+ - ``anyplotlib.__version__ `` is now exposed from the package metadata.
110+ - Per-frame hot-path costs trimmed: the colormap LUT is cached instead of being
111+ rebuilt every frame (~100 ms), and small-range data rescales in float32
112+ (~60 ms → ~27 ms per 2048² frame).
113+
114+
131150.1.0 (2026-06-24)
14116==================
15117
0 commit comments