@amigo-labs/pincel-wasm 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,7 +24,7 @@ await init();
24
24
  const doc = new Document(64, 64); // or Document.openAseprite(bytes)
25
25
  doc.applyTool('pencil', x, y, 0xff0000ff); // tool id, position, RGBA
26
26
  const frame = doc.compose(0, 1); // frame index, zoom
27
- // frame.pixels is a Uint8ClampedArray view into wasm memory (zero-copy)
27
+ // frame.pixels is a fresh Uint8Array copy (RGBA8, row-major) on each read
28
28
 
29
29
  const bytes = doc.saveAseprite();
30
30
  ```
@@ -36,8 +36,8 @@ changes with `doc.drainEvents()`, and release the handle with
36
36
  ## Status
37
37
 
38
38
  Phase 1. The API surface is documented in `docs/specs/pincel.md` §17.5 of
39
- the [repository][Pincel] and kept small and stable; `STATUS.md` there
40
- tracks the current milestone.
39
+ the [repository][Pincel] and kept small and stable; open work there is
40
+ tracked as feature specs indexed in `docs/specs/README.md`.
41
41
 
42
42
  ## License
43
43
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@amigo-labs/pincel-wasm",
3
3
  "type": "module",
4
4
  "description": "WebAssembly bindings for the Pincel core (wasm-bindgen / wasm-pack target)",
5
- "version": "1.4.0",
5
+ "version": "1.5.0",
6
6
  "license": "MIT OR Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
package/pincel_wasm.d.ts CHANGED
@@ -1,6 +1,34 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
 
4
+ /**
5
+ * What the animation export dialog shows before exporting.
6
+ */
7
+ export class AnimationInfo {
8
+ private constructor();
9
+ free(): void;
10
+ [Symbol.dispose](): void;
11
+ /**
12
+ * Default play count: the tag's repeat count when non-zero, else `0`
13
+ * (forever).
14
+ */
15
+ readonly defaultLoops: number;
16
+ /**
17
+ * Number of frames the export contains (pingpong counts the way back).
18
+ */
19
+ readonly frameCount: number;
20
+ /**
21
+ * A frame does not fit a 256-entry GIF colour table, so a GIF export
22
+ * approximates its colours.
23
+ */
24
+ readonly gifApproximated: boolean;
25
+ /**
26
+ * A frame has partially transparent pixels, which GIF reduces to
27
+ * on / off.
28
+ */
29
+ readonly semiTransparent: boolean;
30
+ }
31
+
4
32
  /**
5
33
  * An exported texture atlas: the PNG bytes plus the manifest that says
6
34
  * where each frame landed.
@@ -62,6 +90,35 @@ export class ComposeFrame {
62
90
  readonly width: number;
63
91
  }
64
92
 
93
+ /**
94
+ * What Copy / Cut produced: the PNG and the canvas position it came from.
95
+ */
96
+ export class CopiedImage {
97
+ private constructor();
98
+ free(): void;
99
+ [Symbol.dispose](): void;
100
+ /**
101
+ * The copied pixels as RGBA8 PNG bytes.
102
+ */
103
+ png(): Uint8Array;
104
+ /**
105
+ * Height of the copied region in pixels.
106
+ */
107
+ readonly height: number;
108
+ /**
109
+ * Width of the copied region in pixels.
110
+ */
111
+ readonly width: number;
112
+ /**
113
+ * Sprite-space `x` of the copied region (its paste-in-place origin).
114
+ */
115
+ readonly x: number;
116
+ /**
117
+ * Sprite-space `y` of the copied region.
118
+ */
119
+ readonly y: number;
120
+ }
121
+
65
122
  /**
66
123
  * Owned Pincel document — the [`Sprite`] plus its detached cel store —
67
124
  * exposed as a JS class.
@@ -174,6 +231,12 @@ export class Document {
174
231
  * or when the command bus rejects the insert.
175
232
  */
176
233
  addTileset(name: string, tile_w: number, tile_h: number): number;
234
+ /**
235
+ * Describe the animation `tag` selects (`undefined` / `null` = every
236
+ * frame) for the export dialog. Errors as
237
+ * [`Document::export_animation`] for the tag and the renderer.
238
+ */
239
+ animationInfo(tag?: string | null): AnimationInfo;
177
240
  /**
178
241
  * Flood-fill the contiguous region of pixels matching the seed pixel
179
242
  * at sprite-space `(x, y)` with the given non-premultiplied RGBA
@@ -332,6 +395,20 @@ export class Document {
332
395
  * lands when more tools ship.
333
396
  */
334
397
  applyTool(tool_id: string, x: number, y: number, color: number): void;
398
+ /**
399
+ * Start a Pencil / Eraser gesture on the active layer / current frame.
400
+ * Nothing is painted until the first [`Self::stroke_to`].
401
+ *
402
+ * - `tool`: `"pencil"` paints `color` (`0xRRGGBBAA`); `"eraser"`
403
+ * clears to transparent and ignores `color`.
404
+ * - `size`: brush size, clamped to 1–8.
405
+ * - `shape`: `"square"` or `"round"`.
406
+ * - `pixel_perfect`: drop L-corner pixels; only at size 1.
407
+ *
408
+ * Errors (and starts nothing) for an unknown tool or shape, a locked
409
+ * target layer, or a document without a paintable image layer.
410
+ */
411
+ beginStroke(tool: string, color: number, size: number, shape: string, pixel_perfect: boolean): void;
335
412
  /**
336
413
  * Drop the active marquee selection. No-op when no selection is
337
414
  * active; always enqueues a `selection-changed` event with zeroed
@@ -373,11 +450,27 @@ export class Document {
373
450
  * Shrink the selection by `radius` pixels (may clear it).
374
451
  */
375
452
  contractSelection(radius: number): void;
453
+ /**
454
+ * Copy (spec §5.4): the active layer at the current frame — with
455
+ * `merged`, the composite of all visible layers — cropped to the
456
+ * selection's bounding box (clipped to the canvas; the whole canvas
457
+ * without a selection), pixels outside a shaped selection
458
+ * transparent. A group or tilemap layer copies its rendered pixels.
459
+ * The document is unchanged.
460
+ */
461
+ copySelection(merged: boolean): CopiedImage;
376
462
  /**
377
463
  * Crop the canvas to the marquee selection (intersected with the
378
464
  * canvas). Returns `Ok(false)` when there is no usable selection.
379
465
  */
380
466
  cropToSelection(): boolean;
467
+ /**
468
+ * Cut (spec §5.4): what [`Self::copy_selection`] returns for the
469
+ * active layer, then clears those pixels (the whole layer without a
470
+ * selection) in one undo step. Refused — document unchanged — for a
471
+ * group or tilemap layer and for a locked layer.
472
+ */
473
+ cutSelection(): CopiedImage;
381
474
  /**
382
475
  * Clear the pixels inside the active marquee selection to transparent
383
476
  * on the active paint layer / current frame, routed through the undo
@@ -425,6 +518,20 @@ export class Document {
425
518
  * Grow the selection by `radius` pixels. No-op without a selection.
426
519
  */
427
520
  expandSelection(radius: number): void;
521
+ /**
522
+ * Encode the animation as APNG or GIF bytes.
523
+ *
524
+ * Mirrors `pincel_core::export_animation`. `format` is `"apng"` or
525
+ * `"gif"`; `tag`, when given, exports that tag in its direction —
526
+ * pass `undefined` / `null` for every frame forward. `scale` is the
527
+ * integer nearest-neighbour factor `1..=10`; `loops` the total play
528
+ * count, `0` for forever (at most 65535). The returned `Box<[u8]>` is
529
+ * a fresh `Uint8Array` copy. Errors surface the `pincel-core`
530
+ * message: an unknown format or tag, a scale outside 1–10, an output
531
+ * edge above 16384 px, a frameless sequence, or a sprite the renderer
532
+ * refuses (an indexed sprite).
533
+ */
534
+ exportAnimation(format: string, tag: string | null | undefined, scale: number, loops: number): Uint8Array;
428
535
  /**
429
536
  * Pack frames into a grid atlas PNG and return it with its manifest.
430
537
  *
@@ -473,6 +580,12 @@ export class Document {
473
580
  * range.
474
581
  */
475
582
  frameDuration(frame: number): number;
583
+ /**
584
+ * Clipboard identity of encoded PNG `bytes`: size plus a hash of the
585
+ * decoded pixels, equal for two encodings of the same image. Tells
586
+ * Pincel's own copy apart on the system clipboard.
587
+ */
588
+ static imageKey(bytes: Uint8Array): string;
476
589
  /**
477
590
  * Replace the palette with a GIMP `.gpl` file's colours and return
478
591
  * how many it holds. One undo entry. Refused for indexed documents.
@@ -482,8 +595,8 @@ export class Document {
482
595
  * Decode PNG bytes and add them as a new image layer named `name` on
483
596
  * top of the stack, at the current frame, anchored at the canvas
484
597
  * origin and clipped to the canvas. Returns the new layer id (which
485
- * also becomes the active paint target). One undo step restores the
486
- * layer's blank cel, a second removes the layer.
598
+ * also becomes the active paint target). One undo step removes the
599
+ * layer and its pixels (the Paste command, spec §5.4).
487
600
  */
488
601
  importPngAsLayer(bytes: Uint8Array, name: string): number;
489
602
  /**
@@ -639,6 +752,15 @@ export class Document {
639
752
  * callers can distinguish "unnamed" from "no such entry".
640
753
  */
641
754
  paletteName(index: number): string;
755
+ /**
756
+ * Paste (spec §5.4): decode PNG `bytes` onto a new image layer above
757
+ * the active one, on the current frame, top-left at sprite `(x, y)`.
758
+ * With `expand` the canvas first grows (anchored top-left) to cover
759
+ * the image there; otherwise the image is cropped to the canvas. One
760
+ * undo step. Returns the new layer's id, which becomes active.
761
+ * Refused for an indexed sprite (writing indexed cels is Phase 2).
762
+ */
763
+ pasteLayer(bytes: Uint8Array, x: number, y: number, expand: boolean): number;
642
764
  /**
643
765
  * Sample the composited color at sprite coordinates `(x, y)` on the
644
766
  * given frame. Returns the packed non-premultiplied RGBA8 value as
@@ -871,7 +993,7 @@ export class Document {
871
993
  * rect matches the previous one — the UI's RAF coalescer collapses
872
994
  * the duplicate, and the symmetric "every write emits" contract
873
995
  * matches the other paint methods. Selection state is not part of
874
- * the undo stack in the M7.8 slice (see spec §5.2 / STATUS.md).
996
+ * the undo stack in the M7.8 slice (see spec §5.2).
875
997
  */
876
998
  setSelection(x: number, y: number, width: number, height: number): void;
877
999
  /**
@@ -1012,6 +1134,19 @@ export class Document {
1012
1134
  * key — the codec rejects empty key vectors).
1013
1135
  */
1014
1136
  sliceName(slice_id: number): string;
1137
+ /**
1138
+ * Extend the gesture to sprite-space `(x, y)`: the first call stamps
1139
+ * the brush there, every later call stamps it at each pixel of the
1140
+ * straight line from the previous point. Pixels outside the canvas
1141
+ * are skipped silently. The whole gesture is one undo entry; emits a
1142
+ * `dirty-rect` for the painted segment.
1143
+ *
1144
+ * `pressure` is the optional pen pressure at `(x, y)`: absent means
1145
+ * full pressure (mouse, touch, or the Size option off). It is clamped
1146
+ * to 0–1, and each stamp has size `max(1, round(size × p))` with `p`
1147
+ * interpolated linearly between consecutive points.
1148
+ */
1149
+ strokeTo(x: number, y: number, pressure?: number | null): void;
1015
1150
  /**
1016
1151
  * Display color of the tag at `index`, packed `0xRRGGBBAA`.
1017
1152
  */
@@ -1384,11 +1519,57 @@ export interface InitOutput {
1384
1519
  readonly document_undoDepth: (a: number) => number;
1385
1520
  readonly document_width: (a: number) => number;
1386
1521
  readonly palettePresetNames: () => [number, number];
1522
+ readonly __wbg_animationinfo_free: (a: number, b: number) => void;
1523
+ readonly __wbg_event_free: (a: number, b: number) => void;
1524
+ readonly animationinfo_defaultLoops: (a: number) => number;
1525
+ readonly animationinfo_frameCount: (a: number) => number;
1526
+ readonly animationinfo_gifApproximated: (a: number) => number;
1527
+ readonly animationinfo_semiTransparent: (a: number) => number;
1528
+ readonly document_animationInfo: (a: number, b: number, c: number) => [number, number, number];
1529
+ readonly document_exportAnimation: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
1530
+ readonly event_frame: (a: number) => number;
1531
+ readonly event_height: (a: number) => number;
1532
+ readonly event_kind: (a: number) => [number, number];
1533
+ readonly event_width: (a: number) => number;
1534
+ readonly event_x: (a: number) => number;
1535
+ readonly event_y: (a: number) => number;
1536
+ readonly event_layer: (a: number) => number;
1537
+ readonly __wbg_copiedimage_free: (a: number, b: number) => void;
1538
+ readonly copiedimage_height: (a: number) => number;
1539
+ readonly copiedimage_png: (a: number) => [number, number];
1540
+ readonly copiedimage_width: (a: number) => number;
1541
+ readonly copiedimage_x: (a: number) => number;
1542
+ readonly copiedimage_y: (a: number) => number;
1543
+ readonly document_copySelection: (a: number, b: number) => [number, number, number];
1544
+ readonly document_cutSelection: (a: number) => [number, number, number];
1545
+ readonly document_imageKey: (a: number, b: number) => [number, number, number, number];
1546
+ readonly document_pasteLayer: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
1547
+ readonly blendModeNames: () => [number, number];
1548
+ readonly document_addGroup: (a: number, b: number, c: number) => [number, number, number];
1549
+ readonly document_beginStroke: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => [number, number];
1550
+ readonly document_duplicateLayer: (a: number, b: number) => [number, number, number];
1551
+ readonly document_flattenImage: (a: number) => [number, number, number];
1552
+ readonly document_layerBlendMode: (a: number, b: number) => [number, number];
1553
+ readonly document_layerDepth: (a: number, b: number) => number;
1554
+ readonly document_layerEditable: (a: number, b: number) => number;
1555
+ readonly document_layerLocked: (a: number, b: number) => number;
1556
+ readonly document_layerParent: (a: number, b: number) => number;
1557
+ readonly document_layerThumbnail: (a: number, b: number, c: number, d: number) => [number, number, number];
1558
+ readonly document_mergeDown: (a: number, b: number) => [number, number];
1559
+ readonly document_moveLayerTo: (a: number, b: number, c: number, d: number, e: number) => [number, number];
1560
+ readonly document_reorderLayer: (a: number, b: number, c: number) => [number, number];
1561
+ readonly document_setLayerBlendMode: (a: number, b: number, c: number, d: number) => [number, number];
1562
+ readonly document_setLayerEditable: (a: number, b: number, c: number) => [number, number];
1563
+ readonly document_setLayerOpacity: (a: number, b: number, c: number) => [number, number];
1564
+ readonly document_strokeTo: (a: number, b: number, c: number, d: number) => [number, number];
1387
1565
  readonly document_addTag: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
1566
+ readonly document_cropToSelection: (a: number) => [number, number, number];
1388
1567
  readonly document_frameDuration: (a: number, b: number) => [number, number, number];
1389
1568
  readonly document_onionSkinEnabled: (a: number) => number;
1390
1569
  readonly document_removeFrame: (a: number, b: number) => [number, number];
1391
1570
  readonly document_removeTag: (a: number, b: number) => [number, number];
1571
+ readonly document_resizeCanvas: (a: number, b: number, c: number, d: number, e: number) => [number, number];
1572
+ readonly document_scaleImage: (a: number, b: number, c: number, d: number, e: number) => [number, number];
1392
1573
  readonly document_setFrameDuration: (a: number, b: number, c: number) => [number, number];
1393
1574
  readonly document_setOnionSkin: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
1394
1575
  readonly document_setTagDirection: (a: number, b: number, c: number, d: number) => [number, number];
@@ -1400,26 +1581,12 @@ export interface InitOutput {
1400
1581
  readonly document_tagFrom: (a: number, b: number) => [number, number, number];
1401
1582
  readonly document_tagName: (a: number, b: number) => [number, number, number, number];
1402
1583
  readonly document_tagTo: (a: number, b: number) => [number, number, number];
1403
- readonly __wbg_event_free: (a: number, b: number) => void;
1404
- readonly document_cropToSelection: (a: number) => [number, number, number];
1405
- readonly document_resizeCanvas: (a: number, b: number, c: number, d: number, e: number) => [number, number];
1406
- readonly document_scaleImage: (a: number, b: number, c: number, d: number, e: number) => [number, number];
1407
1584
  readonly document_transformCanvas: (a: number, b: number, c: number) => [number, number];
1408
1585
  readonly document_transformLayer: (a: number, b: number, c: number) => [number, number];
1409
- readonly event_frame: (a: number) => number;
1410
- readonly event_height: (a: number) => number;
1411
- readonly event_kind: (a: number) => [number, number];
1412
- readonly event_layer: (a: number) => number;
1413
- readonly event_width: (a: number) => number;
1414
- readonly event_x: (a: number) => number;
1415
- readonly event_y: (a: number) => number;
1416
- readonly document_applyEffect: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
1417
- readonly document_applyShape: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number) => [number, number];
1418
1586
  readonly document_contractSelection: (a: number, b: number) => void;
1419
1587
  readonly document_drawText: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number) => [number, number, number];
1420
1588
  readonly document_expandSelection: (a: number, b: number) => void;
1421
1589
  readonly document_invertSelection: (a: number) => void;
1422
- readonly document_previewEffect: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
1423
1590
  readonly document_previewText: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number) => [number, number, number];
1424
1591
  readonly document_selectAll: (a: number) => void;
1425
1592
  readonly document_selectEllipse: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number];
@@ -1428,26 +1595,13 @@ export interface InitOutput {
1428
1595
  readonly document_selectWand: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => [number, number];
1429
1596
  readonly document_selectionIsRect: (a: number) => number;
1430
1597
  readonly document_selectionMask: (a: number) => [number, number];
1431
- readonly effectNames: () => [number, number];
1432
1598
  readonly registerFont: (a: number, b: number) => number;
1433
- readonly blendModeNames: () => [number, number];
1434
- readonly document_addGroup: (a: number, b: number, c: number) => [number, number, number];
1435
- readonly document_duplicateLayer: (a: number, b: number) => [number, number, number];
1436
- readonly document_flattenImage: (a: number) => [number, number, number];
1599
+ readonly document_applyEffect: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
1600
+ readonly document_applyShape: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number) => [number, number];
1601
+ readonly document_previewEffect: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
1602
+ readonly effectNames: () => [number, number];
1437
1603
  readonly document_importPngAsLayer: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
1438
- readonly document_layerBlendMode: (a: number, b: number) => [number, number];
1439
- readonly document_layerDepth: (a: number, b: number) => number;
1440
- readonly document_layerEditable: (a: number, b: number) => number;
1441
- readonly document_layerLocked: (a: number, b: number) => number;
1442
- readonly document_layerParent: (a: number, b: number) => number;
1443
- readonly document_layerThumbnail: (a: number, b: number, c: number, d: number) => [number, number, number];
1444
- readonly document_mergeDown: (a: number, b: number) => [number, number];
1445
- readonly document_moveLayerTo: (a: number, b: number, c: number, d: number, e: number) => [number, number];
1446
1604
  readonly document_openPng: (a: number, b: number) => [number, number, number];
1447
- readonly document_reorderLayer: (a: number, b: number, c: number) => [number, number];
1448
- readonly document_setLayerBlendMode: (a: number, b: number, c: number, d: number) => [number, number];
1449
- readonly document_setLayerEditable: (a: number, b: number, c: number) => [number, number];
1450
- readonly document_setLayerOpacity: (a: number, b: number, c: number) => [number, number];
1451
1605
  readonly __wbindgen_externrefs: WebAssembly.Table;
1452
1606
  readonly __wbindgen_free: (a: number, b: number, c: number) => void;
1453
1607
  readonly __externref_drop_slice: (a: number, b: number) => void;
package/pincel_wasm.js CHANGED
@@ -1,5 +1,63 @@
1
1
  /* @ts-self-types="./pincel_wasm.d.ts" */
2
2
 
3
+ /**
4
+ * What the animation export dialog shows before exporting.
5
+ */
6
+ export class AnimationInfo {
7
+ static __wrap(ptr) {
8
+ const obj = Object.create(AnimationInfo.prototype);
9
+ obj.__wbg_ptr = ptr;
10
+ AnimationInfoFinalization.register(obj, obj.__wbg_ptr, obj);
11
+ return obj;
12
+ }
13
+ __destroy_into_raw() {
14
+ const ptr = this.__wbg_ptr;
15
+ this.__wbg_ptr = 0;
16
+ AnimationInfoFinalization.unregister(this);
17
+ return ptr;
18
+ }
19
+ free() {
20
+ const ptr = this.__destroy_into_raw();
21
+ wasm.__wbg_animationinfo_free(ptr, 0);
22
+ }
23
+ /**
24
+ * Default play count: the tag's repeat count when non-zero, else `0`
25
+ * (forever).
26
+ * @returns {number}
27
+ */
28
+ get defaultLoops() {
29
+ const ret = wasm.animationinfo_defaultLoops(this.__wbg_ptr);
30
+ return ret;
31
+ }
32
+ /**
33
+ * Number of frames the export contains (pingpong counts the way back).
34
+ * @returns {number}
35
+ */
36
+ get frameCount() {
37
+ const ret = wasm.animationinfo_frameCount(this.__wbg_ptr);
38
+ return ret >>> 0;
39
+ }
40
+ /**
41
+ * A frame does not fit a 256-entry GIF colour table, so a GIF export
42
+ * approximates its colours.
43
+ * @returns {boolean}
44
+ */
45
+ get gifApproximated() {
46
+ const ret = wasm.animationinfo_gifApproximated(this.__wbg_ptr);
47
+ return ret !== 0;
48
+ }
49
+ /**
50
+ * A frame has partially transparent pixels, which GIF reduces to
51
+ * on / off.
52
+ * @returns {boolean}
53
+ */
54
+ get semiTransparent() {
55
+ const ret = wasm.animationinfo_semiTransparent(this.__wbg_ptr);
56
+ return ret !== 0;
57
+ }
58
+ }
59
+ if (Symbol.dispose) AnimationInfo.prototype[Symbol.dispose] = AnimationInfo.prototype.free;
60
+
3
61
  /**
4
62
  * An exported texture atlas: the PNG bytes plus the manifest that says
5
63
  * where each frame landed.
@@ -141,6 +199,71 @@ export class ComposeFrame {
141
199
  }
142
200
  if (Symbol.dispose) ComposeFrame.prototype[Symbol.dispose] = ComposeFrame.prototype.free;
143
201
 
202
+ /**
203
+ * What Copy / Cut produced: the PNG and the canvas position it came from.
204
+ */
205
+ export class CopiedImage {
206
+ static __wrap(ptr) {
207
+ const obj = Object.create(CopiedImage.prototype);
208
+ obj.__wbg_ptr = ptr;
209
+ CopiedImageFinalization.register(obj, obj.__wbg_ptr, obj);
210
+ return obj;
211
+ }
212
+ __destroy_into_raw() {
213
+ const ptr = this.__wbg_ptr;
214
+ this.__wbg_ptr = 0;
215
+ CopiedImageFinalization.unregister(this);
216
+ return ptr;
217
+ }
218
+ free() {
219
+ const ptr = this.__destroy_into_raw();
220
+ wasm.__wbg_copiedimage_free(ptr, 0);
221
+ }
222
+ /**
223
+ * Height of the copied region in pixels.
224
+ * @returns {number}
225
+ */
226
+ get height() {
227
+ const ret = wasm.copiedimage_height(this.__wbg_ptr);
228
+ return ret >>> 0;
229
+ }
230
+ /**
231
+ * The copied pixels as RGBA8 PNG bytes.
232
+ * @returns {Uint8Array}
233
+ */
234
+ png() {
235
+ const ret = wasm.copiedimage_png(this.__wbg_ptr);
236
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
237
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
238
+ return v1;
239
+ }
240
+ /**
241
+ * Width of the copied region in pixels.
242
+ * @returns {number}
243
+ */
244
+ get width() {
245
+ const ret = wasm.copiedimage_width(this.__wbg_ptr);
246
+ return ret >>> 0;
247
+ }
248
+ /**
249
+ * Sprite-space `x` of the copied region (its paste-in-place origin).
250
+ * @returns {number}
251
+ */
252
+ get x() {
253
+ const ret = wasm.copiedimage_x(this.__wbg_ptr);
254
+ return ret;
255
+ }
256
+ /**
257
+ * Sprite-space `y` of the copied region.
258
+ * @returns {number}
259
+ */
260
+ get y() {
261
+ const ret = wasm.copiedimage_y(this.__wbg_ptr);
262
+ return ret;
263
+ }
264
+ }
265
+ if (Symbol.dispose) CopiedImage.prototype[Symbol.dispose] = CopiedImage.prototype.free;
266
+
144
267
  /**
145
268
  * Owned Pincel document — the [`Sprite`] plus its detached cel store —
146
269
  * exposed as a JS class.
@@ -362,6 +485,22 @@ export class Document {
362
485
  }
363
486
  return ret[0] >>> 0;
364
487
  }
488
+ /**
489
+ * Describe the animation `tag` selects (`undefined` / `null` = every
490
+ * frame) for the export dialog. Errors as
491
+ * [`Document::export_animation`] for the tag and the renderer.
492
+ * @param {string | null} [tag]
493
+ * @returns {AnimationInfo}
494
+ */
495
+ animationInfo(tag) {
496
+ var ptr0 = isLikeNone(tag) ? 0 : passStringToWasm0(tag, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
497
+ var len0 = WASM_VECTOR_LEN;
498
+ const ret = wasm.document_animationInfo(this.__wbg_ptr, ptr0, len0);
499
+ if (ret[2]) {
500
+ throw takeFromExternrefTable0(ret[1]);
501
+ }
502
+ return AnimationInfo.__wrap(ret[0]);
503
+ }
365
504
  /**
366
505
  * Flood-fill the contiguous region of pixels matching the seed pixel
367
506
  * at sprite-space `(x, y)` with the given non-premultiplied RGBA
@@ -610,6 +749,34 @@ export class Document {
610
749
  throw takeFromExternrefTable0(ret[0]);
611
750
  }
612
751
  }
752
+ /**
753
+ * Start a Pencil / Eraser gesture on the active layer / current frame.
754
+ * Nothing is painted until the first [`Self::stroke_to`].
755
+ *
756
+ * - `tool`: `"pencil"` paints `color` (`0xRRGGBBAA`); `"eraser"`
757
+ * clears to transparent and ignores `color`.
758
+ * - `size`: brush size, clamped to 1–8.
759
+ * - `shape`: `"square"` or `"round"`.
760
+ * - `pixel_perfect`: drop L-corner pixels; only at size 1.
761
+ *
762
+ * Errors (and starts nothing) for an unknown tool or shape, a locked
763
+ * target layer, or a document without a paintable image layer.
764
+ * @param {string} tool
765
+ * @param {number} color
766
+ * @param {number} size
767
+ * @param {string} shape
768
+ * @param {boolean} pixel_perfect
769
+ */
770
+ beginStroke(tool, color, size, shape, pixel_perfect) {
771
+ const ptr0 = passStringToWasm0(tool, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
772
+ const len0 = WASM_VECTOR_LEN;
773
+ const ptr1 = passStringToWasm0(shape, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
774
+ const len1 = WASM_VECTOR_LEN;
775
+ const ret = wasm.document_beginStroke(this.__wbg_ptr, ptr0, len0, color, size, ptr1, len1, pixel_perfect);
776
+ if (ret[1]) {
777
+ throw takeFromExternrefTable0(ret[0]);
778
+ }
779
+ }
613
780
  /**
614
781
  * Drop the active marquee selection. No-op when no selection is
615
782
  * active; always enqueues a `selection-changed` event with zeroed
@@ -678,6 +845,23 @@ export class Document {
678
845
  contractSelection(radius) {
679
846
  wasm.document_contractSelection(this.__wbg_ptr, radius);
680
847
  }
848
+ /**
849
+ * Copy (spec §5.4): the active layer at the current frame — with
850
+ * `merged`, the composite of all visible layers — cropped to the
851
+ * selection's bounding box (clipped to the canvas; the whole canvas
852
+ * without a selection), pixels outside a shaped selection
853
+ * transparent. A group or tilemap layer copies its rendered pixels.
854
+ * The document is unchanged.
855
+ * @param {boolean} merged
856
+ * @returns {CopiedImage}
857
+ */
858
+ copySelection(merged) {
859
+ const ret = wasm.document_copySelection(this.__wbg_ptr, merged);
860
+ if (ret[2]) {
861
+ throw takeFromExternrefTable0(ret[1]);
862
+ }
863
+ return CopiedImage.__wrap(ret[0]);
864
+ }
681
865
  /**
682
866
  * Crop the canvas to the marquee selection (intersected with the
683
867
  * canvas). Returns `Ok(false)` when there is no usable selection.
@@ -699,6 +883,20 @@ export class Document {
699
883
  const ret = wasm.document_currentFrame(this.__wbg_ptr);
700
884
  return ret >>> 0;
701
885
  }
886
+ /**
887
+ * Cut (spec §5.4): what [`Self::copy_selection`] returns for the
888
+ * active layer, then clears those pixels (the whole layer without a
889
+ * selection) in one undo step. Refused — document unchanged — for a
890
+ * group or tilemap layer and for a locked layer.
891
+ * @returns {CopiedImage}
892
+ */
893
+ cutSelection() {
894
+ const ret = wasm.document_cutSelection(this.__wbg_ptr);
895
+ if (ret[2]) {
896
+ throw takeFromExternrefTable0(ret[1]);
897
+ }
898
+ return CopiedImage.__wrap(ret[0]);
899
+ }
702
900
  /**
703
901
  * Clear the pixels inside the active marquee selection to transparent
704
902
  * on the active paint layer / current frame, routed through the undo
@@ -793,6 +991,37 @@ export class Document {
793
991
  expandSelection(radius) {
794
992
  wasm.document_expandSelection(this.__wbg_ptr, radius);
795
993
  }
994
+ /**
995
+ * Encode the animation as APNG or GIF bytes.
996
+ *
997
+ * Mirrors `pincel_core::export_animation`. `format` is `"apng"` or
998
+ * `"gif"`; `tag`, when given, exports that tag in its direction —
999
+ * pass `undefined` / `null` for every frame forward. `scale` is the
1000
+ * integer nearest-neighbour factor `1..=10`; `loops` the total play
1001
+ * count, `0` for forever (at most 65535). The returned `Box<[u8]>` is
1002
+ * a fresh `Uint8Array` copy. Errors surface the `pincel-core`
1003
+ * message: an unknown format or tag, a scale outside 1–10, an output
1004
+ * edge above 16384 px, a frameless sequence, or a sprite the renderer
1005
+ * refuses (an indexed sprite).
1006
+ * @param {string} format
1007
+ * @param {string | null | undefined} tag
1008
+ * @param {number} scale
1009
+ * @param {number} loops
1010
+ * @returns {Uint8Array}
1011
+ */
1012
+ exportAnimation(format, tag, scale, loops) {
1013
+ const ptr0 = passStringToWasm0(format, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1014
+ const len0 = WASM_VECTOR_LEN;
1015
+ var ptr1 = isLikeNone(tag) ? 0 : passStringToWasm0(tag, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1016
+ var len1 = WASM_VECTOR_LEN;
1017
+ const ret = wasm.document_exportAnimation(this.__wbg_ptr, ptr0, len0, ptr1, len1, scale, loops);
1018
+ if (ret[3]) {
1019
+ throw takeFromExternrefTable0(ret[2]);
1020
+ }
1021
+ var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
1022
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
1023
+ return v3;
1024
+ }
796
1025
  /**
797
1026
  * Pack frames into a grid atlas PNG and return it with its manifest.
798
1027
  *
@@ -920,6 +1149,33 @@ export class Document {
920
1149
  const ret = wasm.document_height(this.__wbg_ptr);
921
1150
  return ret >>> 0;
922
1151
  }
1152
+ /**
1153
+ * Clipboard identity of encoded PNG `bytes`: size plus a hash of the
1154
+ * decoded pixels, equal for two encodings of the same image. Tells
1155
+ * Pincel's own copy apart on the system clipboard.
1156
+ * @param {Uint8Array} bytes
1157
+ * @returns {string}
1158
+ */
1159
+ static imageKey(bytes) {
1160
+ let deferred3_0;
1161
+ let deferred3_1;
1162
+ try {
1163
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
1164
+ const len0 = WASM_VECTOR_LEN;
1165
+ const ret = wasm.document_imageKey(ptr0, len0);
1166
+ var ptr2 = ret[0];
1167
+ var len2 = ret[1];
1168
+ if (ret[3]) {
1169
+ ptr2 = 0; len2 = 0;
1170
+ throw takeFromExternrefTable0(ret[2]);
1171
+ }
1172
+ deferred3_0 = ptr2;
1173
+ deferred3_1 = len2;
1174
+ return getStringFromWasm0(ptr2, len2);
1175
+ } finally {
1176
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
1177
+ }
1178
+ }
923
1179
  /**
924
1180
  * Replace the palette with a GIMP `.gpl` file's colours and return
925
1181
  * how many it holds. One undo entry. Refused for indexed documents.
@@ -939,8 +1195,8 @@ export class Document {
939
1195
  * Decode PNG bytes and add them as a new image layer named `name` on
940
1196
  * top of the stack, at the current frame, anchored at the canvas
941
1197
  * origin and clipped to the canvas. Returns the new layer id (which
942
- * also becomes the active paint target). One undo step restores the
943
- * layer's blank cel, a second removes the layer.
1198
+ * also becomes the active paint target). One undo step removes the
1199
+ * layer and its pixels (the Paste command, spec §5.4).
944
1200
  * @param {Uint8Array} bytes
945
1201
  * @param {string} name
946
1202
  * @returns {number}
@@ -1325,6 +1581,28 @@ export class Document {
1325
1581
  wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
1326
1582
  }
1327
1583
  }
1584
+ /**
1585
+ * Paste (spec §5.4): decode PNG `bytes` onto a new image layer above
1586
+ * the active one, on the current frame, top-left at sprite `(x, y)`.
1587
+ * With `expand` the canvas first grows (anchored top-left) to cover
1588
+ * the image there; otherwise the image is cropped to the canvas. One
1589
+ * undo step. Returns the new layer's id, which becomes active.
1590
+ * Refused for an indexed sprite (writing indexed cels is Phase 2).
1591
+ * @param {Uint8Array} bytes
1592
+ * @param {number} x
1593
+ * @param {number} y
1594
+ * @param {boolean} expand
1595
+ * @returns {number}
1596
+ */
1597
+ pasteLayer(bytes, x, y, expand) {
1598
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
1599
+ const len0 = WASM_VECTOR_LEN;
1600
+ const ret = wasm.document_pasteLayer(this.__wbg_ptr, ptr0, len0, x, y, expand);
1601
+ if (ret[2]) {
1602
+ throw takeFromExternrefTable0(ret[1]);
1603
+ }
1604
+ return ret[0] >>> 0;
1605
+ }
1328
1606
  /**
1329
1607
  * Sample the composited color at sprite coordinates `(x, y)` on the
1330
1608
  * given frame. Returns the packed non-premultiplied RGBA8 value as
@@ -1875,7 +2153,7 @@ export class Document {
1875
2153
  * rect matches the previous one — the UI's RAF coalescer collapses
1876
2154
  * the duplicate, and the symmetric "every write emits" contract
1877
2155
  * matches the other paint methods. Selection state is not part of
1878
- * the undo stack in the M7.8 slice (see spec §5.2 / STATUS.md).
2156
+ * the undo stack in the M7.8 slice (see spec §5.2).
1879
2157
  * @param {number} x
1880
2158
  * @param {number} y
1881
2159
  * @param {number} width
@@ -2231,6 +2509,27 @@ export class Document {
2231
2509
  wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
2232
2510
  }
2233
2511
  }
2512
+ /**
2513
+ * Extend the gesture to sprite-space `(x, y)`: the first call stamps
2514
+ * the brush there, every later call stamps it at each pixel of the
2515
+ * straight line from the previous point. Pixels outside the canvas
2516
+ * are skipped silently. The whole gesture is one undo entry; emits a
2517
+ * `dirty-rect` for the painted segment.
2518
+ *
2519
+ * `pressure` is the optional pen pressure at `(x, y)`: absent means
2520
+ * full pressure (mouse, touch, or the Size option off). It is clamped
2521
+ * to 0–1, and each stamp has size `max(1, round(size × p))` with `p`
2522
+ * interpolated linearly between consecutive points.
2523
+ * @param {number} x
2524
+ * @param {number} y
2525
+ * @param {number | null} [pressure]
2526
+ */
2527
+ strokeTo(x, y, pressure) {
2528
+ const ret = wasm.document_strokeTo(this.__wbg_ptr, x, y, isLikeNone(pressure) ? Number.MAX_SAFE_INTEGER : Math.fround(pressure));
2529
+ if (ret[1]) {
2530
+ throw takeFromExternrefTable0(ret[0]);
2531
+ }
2532
+ }
2234
2533
  /**
2235
2534
  * Display color of the tag at `index`, packed `0xRRGGBBAA`.
2236
2535
  * @param {number} index
@@ -2680,12 +2979,18 @@ function __wbg_get_imports() {
2680
2979
  };
2681
2980
  }
2682
2981
 
2982
+ const AnimationInfoFinalization = (typeof FinalizationRegistry === 'undefined')
2983
+ ? { register: () => {}, unregister: () => {} }
2984
+ : new FinalizationRegistry(ptr => wasm.__wbg_animationinfo_free(ptr, 1));
2683
2985
  const AtlasExportFinalization = (typeof FinalizationRegistry === 'undefined')
2684
2986
  ? { register: () => {}, unregister: () => {} }
2685
2987
  : new FinalizationRegistry(ptr => wasm.__wbg_atlasexport_free(ptr, 1));
2686
2988
  const ComposeFrameFinalization = (typeof FinalizationRegistry === 'undefined')
2687
2989
  ? { register: () => {}, unregister: () => {} }
2688
2990
  : new FinalizationRegistry(ptr => wasm.__wbg_composeframe_free(ptr, 1));
2991
+ const CopiedImageFinalization = (typeof FinalizationRegistry === 'undefined')
2992
+ ? { register: () => {}, unregister: () => {} }
2993
+ : new FinalizationRegistry(ptr => wasm.__wbg_copiedimage_free(ptr, 1));
2689
2994
  const DocumentFinalization = (typeof FinalizationRegistry === 'undefined')
2690
2995
  ? { register: () => {}, unregister: () => {} }
2691
2996
  : new FinalizationRegistry(ptr => wasm.__wbg_document_free(ptr, 1));
Binary file