From fcfd21428dd41010ddbd9a0122ac2098fc10c16a Mon Sep 17 00:00:00 2001
From: th
Date: Fri, 24 Apr 2026 10:21:30 +0200
Subject: [PATCH] 0.16.0: animated GIF / APNG export + transparent canvas
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- New frame-by-frame export pipeline in src/lib/frameExport.ts: loads
scene images, walks rangeIn..rangeOut at scene.fps, drawSceneFrame
to an offscreen canvas, awaits a per-frame callback, yields every
4 frames to keep progress responsive, honours AbortSignal.
- Animated GIF via gifenc (per-frame quantize/palette, ~5 KB lib).
- Animated PNG via UPNG.js + pako (lossless RGBA, alpha-aware, ~30 KB).
- drawSceneFrame now clearRects first and skips the background fill
when scene.canvas.background === 'transparent' — prerequisite for
the alpha to reach the APNG output.
- DocumentDrawer: new checkerboard toggle next to the colour picker
flips the stage between its last-picked colour and transparent;
disabled swatch + 'Transparent' hex label make the state obvious.
- ExportDialog: three new option rows — Animated GIF, Animated PNG,
and an APNG preview page that bundles the rendered APNG into a
standalone HTML with a checkerboard backdrop + dark/light toggles
for verifying transparency at a glance.
- Tests: renderFrames frame count + abort, GIF header magic, APNG
acTL presence, transparent round-trip (decoded alpha == 0 on
un-painted pixels), preview HTML structure, and the two UI tests
covering ExportDialog's new rows and the progress-view transition.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
package-lock.json | 31 +++-
package.json | 4 +-
src/components/DocumentDrawer.vue | 75 +++++++++-
src/components/ExportDialog.vue | 124 ++++++++++++++++
src/devtools/tests.ts | 229 +++++++++++++++++++++++++++++
src/i18n/messages.ts | 24 +++
src/lib/apngExport.ts | 236 ++++++++++++++++++++++++++++++
src/lib/frameExport.ts | 100 +++++++++++++
src/lib/gifExport.ts | 58 ++++++++
src/lib/render.ts | 16 +-
src/types/encoders.d.ts | 71 +++++++++
src/version.ts | 2 +-
12 files changed, 956 insertions(+), 14 deletions(-)
create mode 100644 src/lib/apngExport.ts
create mode 100644 src/lib/frameExport.ts
create mode 100644 src/lib/gifExport.ts
create mode 100644 src/types/encoders.d.ts
diff --git a/package-lock.json b/package-lock.json
index 408a060..a40bdc3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,18 @@
{
- "name": "animatr",
- "version": "0.0.0",
+ "name": "ruwio",
+ "version": "0.15.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "animatr",
- "version": "0.0.0",
+ "name": "ruwio",
+ "version": "0.15.0",
"dependencies": {
"@phosphor-icons/vue": "^2.2.1",
+ "gifenc": "^1.0.3",
"konva": "^9.3.16",
"pinia": "^2.2.6",
+ "upng-js": "^2.1.0",
"vue": "^3.5.13",
"vue-konva": "^3.1.2"
},
@@ -1210,6 +1212,12 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/gifenc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz",
+ "integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw==",
+ "license": "MIT"
+ },
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -1290,6 +1298,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -1458,6 +1472,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/upng-js": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/upng-js/-/upng-js-2.1.0.tgz",
+ "integrity": "sha512-d3xzZzpMP64YkjP5pr8gNyvBt7dLk/uGI67EctzDuVp4lCZyVMo0aJO6l/VDlgbInJYDY6cnClLoBp29eKWI6g==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^1.0.5"
+ }
+ },
"node_modules/vite": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
diff --git a/package.json b/package.json
index 47cfa39..1a56bc3 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "ruwio",
"private": true,
- "version": "0.15.0",
+ "version": "0.16.0",
"type": "module",
"scripts": {
"dev": "vite",
@@ -12,8 +12,10 @@
},
"dependencies": {
"@phosphor-icons/vue": "^2.2.1",
+ "gifenc": "^1.0.3",
"konva": "^9.3.16",
"pinia": "^2.2.6",
+ "upng-js": "^2.1.0",
"vue": "^3.5.13",
"vue-konva": "^3.1.2"
},
diff --git a/src/components/DocumentDrawer.vue b/src/components/DocumentDrawer.vue
index 52f2553..67b0f4b 100644
--- a/src/components/DocumentDrawer.vue
+++ b/src/components/DocumentDrawer.vue
@@ -1,6 +1,6 @@
+ |