6 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Opus 5 f8869368c9 Keep the background when importing delta-encoded GIFs
Frames after the first came in with the background punched out. The
cause is a common exporter pattern: mark the unchanged background as
the frame's transparent index so each frame compresses down to just
what moved, then tag every frame disposal 2. Those two are
contradictory - disposal 2 ("restore to background") is implemented as
a clear to transparent by every browser, which wipes the very pixels
the delta relies on showing through. The reported file also declares
its background colour as an unrelated cyan palette slot, so honouring
the spec literally is no better.

Suppress the disposal-2 clear in exactly one case: a full-canvas frame
in an animation whose first frame was fully opaque. Such a file
demonstrably has a baked-in background, so "restore to background"
cannot coherently mean "erase to nothing".

Both conditions are load-bearing, and each has a regression test:

- full-canvas only, so sprite erasure - disposal 2 on a small frame
  rect, the case the method genuinely exists for - still clears and
  leaves no trail over an opaque background;
- opaque-first-frame only, so animations on real transparency keep it.
  That includes our own exporter's output, which also writes
  full-canvas disposal-2 frames: without this condition a sprite
  re-imported from an Artstr GIF trailed its previous position.

Verified on the reported 640x640 40-frame GIF (every frame now solid,
100% opaque, black background intact) and on the transparent-background
Artstr icon animation (corners still transparent on every frame).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-02 11:14:36 -04:00
thePR0M3TH3ANandClaude Opus 5 8702043abf Import animated GIFs into the pixel editor
Add an inline GIF89a decoder - the mirror of the existing encoder, so
still no vendored dependency - and wire it into "From image / GIF...".
Every frame of an animated GIF becomes a frame in the pixel editor. A
browser <img> only ever exposes frame 0 to canvas, so decoding is the
only route to the rest; the decoder covers LZW, interlacing, local
colour tables, transparency and all three disposal methods.

The whole animation is quantized against one shared palette (per-frame
palettes made colours shimmer and burned through the 256-entry cap),
playback FPS is derived from the median GIF frame delay, and a Frames
control samples long animations down evenly. Frame count and working
resolution are bounded so a large GIF cannot exhaust memory, and
whatever gets dropped is reported rather than silently truncated.

Fix the reported pixel-editor import bugs:

- Dead canvas. applyLayout() tore the pixel editor down before its
  "layout unchanged" early return, and only the render() at the end
  rebuilds a standalone one - so clicking Pixel art while already in
  Pixel art left the grid on screen with nothing behind it and
  painting, the palette and import all silently did nothing.
- Locked W/H with no explanation, and a multi-image batch forced to the
  New frame target could never set dimensions at all. Batches can now
  target Whole sprite, and the lock states why and how to change it.
- Cover/Fit/Stretch previews were indistinguishable: the pixelized
  canvas rendered at its natural size (32 CSS px in a 250px box). Both
  panes now fill the box, and the source pane is framed exactly as the
  conversion frames it, so the two read as before/after.
- Photos were point-sampled on the way down, which threw away almost
  the whole image and read as noise. Area-average via progressive
  halving, switching to nearest at 1:1 and integer ratios so pixel-art
  sources stay crisp and import exactly.

Also fixed while verifying:

- Standalone pixel work was lost on refresh - pixelCommitIfStandalone()
  mutates state without calling render(), which is what writes the
  autosave. persistAutosave() existed for this case but was never
  called here.
- The quantizer always split to the requested colour count, padding
  small palettes with duplicate dead entries that cost event-size
  budget. It now keeps the source's exact colours when there are fewer
  than requested.
- sanitizePixelArtBlob truncated imported palettes at 64 though the
  model allows 256 (cells past the cut rendered transparent) and
  dropped per-frame durations.
- Stale grid-size pill after a resize, zoom not refitting when the grid
  grows, and a dead pixelImportFromImage() pointing at a nonexistent
  element.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-02 11:02:40 -04:00
thePR0M3TH3ANandClaude Opus 4.7 6a1352904e Docs: ship pixel-art Phases A–D, prune stale TODOs, update README
PIXEL_ART_FEATURE.md: rewrite Implementation status to reflect that
Phases A–D all shipped (embedded layer, standalone design, raster
export + editor extras, animation, image → pixelize), with the
backlog (WebP/APNG, Blossom offload, animated pixel layers driving
Stack actions) called out as deferred.

TODO.md: drop stale items now that they're resolved —

- Pixel Art (moved to a "Shipped (recent)" section pointing at the
  spec doc).
- Pixel editor layout expansion (subsumed by the collapsible
  sidebar — both the pixel editor and vector canvas can now use
  the full viewport width).
- Print / Save event ID wrapping (the `<dd>` already uses
  `overflow-wrap: anywhere; word-break: break-word`).
- TMDB auto-fill cleanup (the doc was already deleted).

The Color picker fixes item is kept but narrowed to the vector
tools — the pixel editor's in-app HSV picker shipped in Phase A.

README.md: add Pixel Art everywhere it's relevant —

- One-liner mention in the intro paragraph.
- New "Pixel art" Designer-tab layout entry alongside Slide / Disc
  design.
- `pixelart` layer type added to the Layers list.
- New dedicated "Pixel art" section before Editor canvas, covering
  tools / transforms / animation / export / import / undo /
  animated previews.
- Editor canvas section mentions the new sidebar collapse toggle.
- Schema section lists `pixelart` template mode and layer type,
  documents the `pixelArt` data-model shape, and notes that the
  inline GIF89a encoder lives in `index.html` (not a vendored dep).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-26 10:54:10 -04:00
thePR0M3TH3AN 8a2c146408 added pixel art editor 2026-05-22 16:06:36 -04:00
thePR0M3TH3ANandClaude Opus 4.7 b24511ad80 Add Pixel Art Phase A1 — editable pixel layers
A new `pixelart` layer type: a palette-indexed, RLE-encoded grid stored
in the project JSON, rendered crisp (image-rendering: pixelated) across
the editor, preview-DOM renderers, and the export canvas.

A dedicated #pixelEditorView with pencil / eraser / fill / eyedropper
(drag-paint with line interpolation), a palette panel (pick / add
colours), grid toggle, mirror X/Y, zoom, and per-stroke in-editor undo.
Reachable via a tool-palette button (adds a 32x32 layer + opens the
editor) and an "Edit pixels" button / double-click on a pixel layer.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-22 15:30:43 -04:00
thePR0M3TH3ANandClaude Opus 4.7 5e44c9e2e4 Draft Pixel Art feature plan with event-size budgeting
Spec for a Nostr-native pixel-art editor: an editable `pixelart` layer
type (palette-indexed + RLE grid stored in JSON, raster as export-only),
a dedicated Pixel Editor canvas-area view, a standalone casewrap-pixelart
Designer-tab design importable into any design as a layer, PNG export,
and a phased path to animation frames + GIF.

Includes a Size limits section: relays cap event size (commonly 64 KB),
so publishing is gated on a measured event-size budget derived from the
relays' NIP-11 max_event_size (min across the user's set, 64 KB
fallback) — soft warning at ~50%, hard block at ~90%, with a live size
readout in the editor. Encoder stores the smaller of RLE / base64 to
bound the worst case; rough size table per grid dimension included.

Also index the spec in TODO.md under drafted-but-unbuilt features.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-22 07:29:14 -04:00