Skip to content

Commit 5bee276

Browse files
authored
Merge pull request #274 from ToolboxAid/PR_26179_CHARLIE_037-sprites-animation-export
PR_26179_CHARLIE_037-sprites-animation-export
2 parents 6a4f0a4 + a20ed73 commit 5bee276

6 files changed

Lines changed: 240 additions & 191 deletions

File tree

assets/toolbox/sprites/js/index.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,12 @@ function renderPixelsToCanvas(canvas, paintedPixels, gridSize) {
457457
return;
458458
}
459459
const size = gridSize || DEFAULT_GRID_SIZE;
460-
const cellSize = canvas.width / size;
461460
context.clearRect(0, 0, canvas.width, canvas.height);
461+
drawPixelsToContext(context, paintedPixels, size, 0, 0, canvas.width);
462+
}
463+
464+
function drawPixelsToContext(context, paintedPixels, gridSize, offsetX, offsetY, outputSize) {
465+
const cellSize = outputSize / gridSize;
462466
for (const [key, colorKey] of paintedPixels.entries()) {
463467
const [rowText, columnText] = key.split(":");
464468
const row = Number(rowText);
@@ -467,7 +471,7 @@ function renderPixelsToCanvas(canvas, paintedPixels, gridSize) {
467471
continue;
468472
}
469473
context.fillStyle = editorColorValue(colorKey);
470-
context.fillRect((column - 1) * cellSize, (row - 1) * cellSize, cellSize, cellSize);
474+
context.fillRect(offsetX + (column - 1) * cellSize, offsetY + (row - 1) * cellSize, cellSize, cellSize);
471475
}
472476
}
473477

@@ -647,6 +651,52 @@ function exportPreviewPng() {
647651
}, "image/png");
648652
}
649653

654+
function exportAnimationStripPng() {
655+
const status = document.querySelector("[data-sprites-export-status]");
656+
if (editorState.frames.length < 2) {
657+
if (status) {
658+
status.textContent = "Animation strip export needs at least two unsaved frames.";
659+
}
660+
return;
661+
}
662+
stopAnimationPreview("Animation preview stopped before export.");
663+
const frameSize = 160;
664+
const canvas = document.createElement("canvas");
665+
canvas.width = frameSize * editorState.frames.length;
666+
canvas.height = frameSize;
667+
const context = canvas.getContext("2d");
668+
if (!context) {
669+
if (status) {
670+
status.textContent = "Animation strip export is unavailable in this browser session.";
671+
}
672+
return;
673+
}
674+
context.clearRect(0, 0, canvas.width, canvas.height);
675+
editorState.frames.forEach((frame, index) => {
676+
drawPixelsToContext(context, frame.paintedPixels, editorState.gridSize, index * frameSize, 0, frameSize);
677+
});
678+
canvas.toBlob((blob) => {
679+
if (!blob) {
680+
if (status) {
681+
status.textContent = "Animation strip export is unavailable in this browser session.";
682+
}
683+
return;
684+
}
685+
const objectUrl = URL.createObjectURL(blob);
686+
const link = document.createElement("a");
687+
link.href = objectUrl;
688+
link.download = "sprite-creator-animation-strip.png";
689+
link.rel = "noopener";
690+
document.body.append(link);
691+
link.click();
692+
link.remove();
693+
URL.revokeObjectURL(objectUrl);
694+
if (status) {
695+
status.textContent = `Animation strip PNG downloaded from ${editorState.frames.length} unsaved frames.`;
696+
}
697+
}, "image/png");
698+
}
699+
650700
function setGridSize(size, options = {}) {
651701
const grid = document.querySelector("[data-sprites-pixel-grid]");
652702
const status = document.querySelector("[data-sprites-grid-status]");
@@ -807,6 +857,10 @@ function wireExportButton() {
807857
if (button) {
808858
button.addEventListener("click", exportPreviewPng);
809859
}
860+
const animationButton = document.querySelector("[data-sprites-export-animation]");
861+
if (animationButton) {
862+
animationButton.addEventListener("click", exportAnimationStripPng);
863+
}
810864
}
811865

812866
wireGridControls();

dev/tests/playwright/tools/SpritesToolShell.spec.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,11 @@ test("Sprite Creator previews unsaved animation frames", async ({ page }) => {
558558
await page.getByRole("button", { name: "Stop Preview" }).click();
559559
await expect(page.locator("[data-sprites-animation-status]")).toContainText("Selected Frame 2 is shown");
560560
await expectCenterAndPreviewPainted(page, 1, 1, "sprite-canvas-cell--blue");
561+
const stripDownloadPromise = page.waitForEvent("download");
562+
await page.getByRole("button", { name: "Download Animation Strip" }).click();
563+
const stripDownload = await stripDownloadPromise;
564+
expect(stripDownload.suggestedFilename()).toBe("sprite-creator-animation-strip.png");
565+
await expect(page.locator("[data-sprites-export-status]")).toContainText("Animation strip PNG downloaded from 2 unsaved frames");
561566

562567
expect(failures.failedRequests).toEqual([]);
563568
expect(failures.pageErrors).toEqual([]);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# PR_26179_CHARLIE_037-sprites-animation-export
2+
3+
Team: CHARLIE
4+
5+
Mode: Batch governance stacked feature workflow
6+
7+
Base branch: PR_26179_CHARLIE_036-sprites-animation-preview
8+
9+
Branch: PR_26179_CHARLIE_037-sprites-animation-export
10+
11+
## Summary
12+
13+
Added local animation strip export for Sprite Creator. Download Animation Strip generates a PNG strip from unsaved page-session frames only. The export does not save to the sprite library, publish an asset, call an API, create database records, or introduce authoritative browser-owned product data.
14+
15+
## Branch Validation
16+
17+
PASS
18+
19+
- Built from `PR_26179_CHARLIE_036-sprites-animation-preview`.
20+
- Scope stayed limited to unsaved animation export.
21+
- No DB/API/schema changes.
22+
- No saved product data or browser-owned authoritative product data added.
23+
- No start_of_day files changed.
24+
- ZIP package created at the requested path.
25+
26+
## Requirement Checklist
27+
28+
PASS - Animation strip export button added.
29+
30+
PASS - Export requires at least two unsaved frames.
31+
32+
PASS - Export creates a local PNG strip download.
33+
34+
PASS - Export stops animation preview before generating the strip.
35+
36+
PASS - No persistence, publishing, API, database, or storage behavior added.
37+
38+
PASS - Targeted Playwright coverage updated.
39+
40+
## Validation Lane
41+
42+
PASS - `node --check assets/toolbox/sprites/js/index.js`
43+
44+
PASS - `node --check dev/tests/playwright/tools/SpritesToolShell.spec.mjs`
45+
46+
PASS - `git diff --check -- toolbox/sprites/index.html assets/toolbox/sprites/js/index.js dev/tests/playwright/tools/SpritesToolShell.spec.mjs`
47+
48+
PASS - Runtime guard scan found no prohibited inline style/script/event-handler or stale placeholder text in touched runtime files.
49+
50+
PASS - `npx playwright test dev/tests/playwright/tools/SpritesToolShell.spec.mjs --workers=1 --reporter=list`
51+
52+
## Manual Validation Notes
53+
54+
1. Open `toolbox/sprites/index.html`.
55+
2. Draw Frame 1.
56+
3. Add Frame 2 and draw a different draft.
57+
4. Click Download Animation Strip.
58+
5. Confirm `sprite-creator-animation-strip.png` downloads.
59+
6. Confirm the export status says the PNG came from unsaved frames.
60+
7. Confirm no save, publish, API, database, or library workflow is introduced.
61+
62+
## ZIP
63+
64+
`dev/workspace/zip/PR_26179_CHARLIE_037-sprites-animation-export_delta.zip`
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
assets/toolbox/sprites/js/index.js
22
dev/tests/playwright/tools/SpritesToolShell.spec.mjs
3-
docs_build/dev/reports/PR_26179_CHARLIE_036-sprites-animation-preview.md
3+
docs_build/dev/reports/PR_26179_CHARLIE_037-sprites-animation-export.md
44
docs_build/dev/reports/codex_changed_files.txt
55
docs_build/dev/reports/codex_review.diff
66
toolbox/sprites/index.html

0 commit comments

Comments
 (0)