@pixerate/editor 0.246.0 → 0.249.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 +1 -0
- package/dist/animation-editor/index.cjs +64 -61
- package/dist/animation-editor/index.js +8 -5
- package/dist/{brushstrokeRender-ZDHMXJAE.cjs → brushstrokeRender-GCKK7V2I.cjs} +14 -11
- package/dist/{brushstrokeRender-AKUI3SEJ.js → brushstrokeRender-T3JZST44.js} +7 -4
- package/dist/canvas/index.cjs +8 -8
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-UVPD55UZ.cjs → chunk-2SMMZDMG.cjs} +35 -35
- package/dist/chunk-2VJAVXLI.js +192 -0
- package/dist/{chunk-OLNIPTB3.cjs → chunk-2VX55NFM.cjs} +1 -320
- package/dist/chunk-3K6HWEXZ.cjs +92 -0
- package/dist/{chunk-UOOK7HTX.js → chunk-3PPJRMIG.js} +1 -1
- package/dist/chunk-4D6KP5BV.js +92 -0
- package/dist/chunk-5RH3VXAK.js +269 -0
- package/dist/{chunk-LDPLEZ5T.js → chunk-6BIDVPED.js} +1 -1
- package/dist/{chunk-2ONK3ERD.cjs → chunk-75K3W4D6.cjs} +20 -20
- package/dist/chunk-BOHDJJ2O.cjs +192 -0
- package/dist/{chunk-2PBZBRP5.js → chunk-D5KGAO4Z.js} +10 -92
- package/dist/{chunk-DQWPMGH2.js → chunk-DOIU7VJG.js} +1 -1
- package/dist/{chunk-HB4ICNNA.cjs → chunk-DPYAO5U6.cjs} +42 -42
- package/dist/chunk-EJLRBS5Q.js +301 -0
- package/dist/{chunk-Q2UYUWAZ.cjs → chunk-H4AOUUBE.cjs} +679 -164
- package/dist/{chunk-PQ3H2AAN.cjs → chunk-I7NJPHWV.cjs} +122 -204
- package/dist/{chunk-FK5LTDOO.cjs → chunk-IOFVM3L3.cjs} +7 -7
- package/dist/{chunk-42TWUPNJ.js → chunk-IRSTHLQQ.js} +1 -320
- package/dist/{chunk-VDAWA546.js → chunk-J7T5QZ45.js} +614 -99
- package/dist/{chunk-VMROIWLL.cjs → chunk-KBYB7OWB.cjs} +3 -3
- package/dist/{chunk-5MFDD7SP.cjs → chunk-KYX7NZ5V.cjs} +1 -1
- package/dist/chunk-LJRCU3BJ.cjs +301 -0
- package/dist/{chunk-XTWXBQD4.cjs → chunk-LP2MCSBE.cjs} +593 -236
- package/dist/{chunk-A23PSOBB.js → chunk-O75V4ZS3.js} +1 -1
- package/dist/chunk-OTTT5N3K.js +322 -0
- package/dist/{chunk-N6WUB3Y5.js → chunk-PZP2SZIZ.js} +2 -2
- package/dist/{chunk-4PMCMWQS.js → chunk-UBRJOIR5.js} +367 -10
- package/dist/chunk-W5M3J7PV.cjs +322 -0
- package/dist/{chunk-4P6H35HU.js → chunk-WA44NKPF.js} +2 -2
- package/dist/{chunk-CEPKXLBS.cjs → chunk-WLJZZHUZ.cjs} +23 -23
- package/dist/{chunk-P3NIPIKS.js → chunk-WQCNC4DK.js} +1 -1
- package/dist/chunk-ZEYMKDYS.cjs +269 -0
- package/dist/image-editor/effect-tools.d.cts +37 -4
- package/dist/image-editor/effect-tools.d.ts +37 -4
- package/dist/image-editor/index.cjs +1340 -1828
- package/dist/image-editor/index.d.cts +1 -1
- package/dist/image-editor/index.d.ts +1 -1
- package/dist/image-editor/index.js +298 -786
- package/dist/paint/index.cjs +8 -5
- package/dist/paint/index.js +13 -10
- package/dist/paint-editor/controller.d.cts +71 -2
- package/dist/paint-editor/controller.d.ts +71 -2
- package/dist/paint-editor/index.cjs +901 -589
- package/dist/paint-editor/index.d.cts +3 -0
- package/dist/paint-editor/index.d.ts +3 -0
- package/dist/paint-editor/index.js +365 -53
- package/dist/paint-editor/procedural.d.cts +82 -5
- package/dist/paint-editor/procedural.d.ts +82 -5
- package/dist/paint-editor/sticker-art.d.cts +55 -0
- package/dist/paint-editor/sticker-art.d.ts +55 -0
- package/dist/paint-editor/stickers.d.cts +30 -17
- package/dist/paint-editor/stickers.d.ts +30 -17
- package/dist/paint-editor/stroke.d.cts +4 -0
- package/dist/paint-editor/stroke.d.ts +4 -0
- package/dist/paint-editor/tools.d.cts +1 -1
- package/dist/paint-editor/tools.d.ts +1 -1
- package/dist/paint-editor/ui.d.cts +25 -4
- package/dist/paint-editor/ui.d.ts +25 -4
- package/dist/{image-editor/layers/effects-ui.d.cts → raster/effects/ui.d.cts} +32 -5
- package/dist/{image-editor/layers/effects-ui.d.ts → raster/effects/ui.d.ts} +32 -5
- package/dist/raster/index.cjs +17 -3
- package/dist/raster/index.d.cts +1 -0
- package/dist/raster/index.d.ts +1 -0
- package/dist/raster/index.js +23 -9
- package/dist/stickers/browser.d.cts +48 -0
- package/dist/stickers/browser.d.ts +48 -0
- package/dist/stickers/combine.d.cts +10 -0
- package/dist/stickers/combine.d.ts +10 -0
- package/dist/stickers/conformance.d.cts +18 -0
- package/dist/stickers/conformance.d.ts +18 -0
- package/dist/stickers/editor.d.cts +14 -0
- package/dist/stickers/editor.d.ts +14 -0
- package/dist/stickers/errors.d.cts +6 -0
- package/dist/stickers/errors.d.ts +6 -0
- package/dist/stickers/fake.d.cts +16 -0
- package/dist/stickers/fake.d.ts +16 -0
- package/dist/stickers/helpers.d.cts +51 -0
- package/dist/stickers/helpers.d.ts +51 -0
- package/dist/stickers/index.cjs +331 -0
- package/dist/stickers/index.d.cts +16 -0
- package/dist/stickers/index.d.ts +16 -0
- package/dist/stickers/index.js +331 -0
- package/dist/stickers/stamps.d.cts +13 -0
- package/dist/stickers/stamps.d.ts +13 -0
- package/dist/stickers/tools.d.cts +25 -0
- package/dist/stickers/tools.d.ts +25 -0
- package/dist/stickers/types.d.cts +123 -0
- package/dist/stickers/types.d.ts +123 -0
- package/dist/vector/index.cjs +978 -975
- package/dist/vector/index.js +9 -6
- package/package.json +11 -1
- package/skills/README.md +1 -0
- package/skills/pixerate-paint-editor/SKILL.md +26 -32
- package/skills/pixerate-paint-editor/references/gradient-paint.md +23 -0
- package/skills/pixerate-paint-editor/references/layer-effects.md +28 -0
- package/skills/pixerate-paint-editor/references/light-paint.md +10 -2
- package/skills/pixerate-paint-editor/references/particle-brushes.md +66 -0
- package/skills/pixerate-stickers/SKILL.md +45 -0
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
STAMP_KINDS,
|
|
3
|
+
StickerError,
|
|
4
|
+
describeStickerRef,
|
|
5
|
+
editorStampOf,
|
|
6
|
+
normalizeStickerRef,
|
|
7
|
+
stampCoverage,
|
|
8
|
+
stickerIdFor
|
|
9
|
+
} from "./chunk-5RH3VXAK.js";
|
|
1
10
|
import {
|
|
2
11
|
CoverageMap,
|
|
3
12
|
DEFAULT_BLENDER,
|
|
@@ -11,16 +20,22 @@ import {
|
|
|
11
20
|
TileMedium,
|
|
12
21
|
dabRect,
|
|
13
22
|
grainCatch,
|
|
14
|
-
hash2,
|
|
15
23
|
normalizeBlender,
|
|
16
24
|
normalizeBrush,
|
|
17
25
|
normalizeMixer,
|
|
18
26
|
normalizeSmudge,
|
|
19
|
-
rand01,
|
|
20
|
-
randSigned,
|
|
21
27
|
rasterizeDab,
|
|
22
28
|
readPaintSource
|
|
23
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-D5KGAO4Z.js";
|
|
30
|
+
import {
|
|
31
|
+
hash2,
|
|
32
|
+
rand01,
|
|
33
|
+
randSigned
|
|
34
|
+
} from "./chunk-4D6KP5BV.js";
|
|
35
|
+
import {
|
|
36
|
+
decodePng,
|
|
37
|
+
parseDataUrl
|
|
38
|
+
} from "./chunk-OTTT5N3K.js";
|
|
24
39
|
import {
|
|
25
40
|
TILE,
|
|
26
41
|
TiledSurface,
|
|
@@ -586,14 +601,129 @@ function toStrokePoint(p) {
|
|
|
586
601
|
return { x: p.x, y: p.y, pressure: pressureOf(p), ...typeof p.t === "number" ? { t: p.t } : {} };
|
|
587
602
|
}
|
|
588
603
|
|
|
604
|
+
// src/paint-editor/sticker-art.ts
|
|
605
|
+
var STICKER_ART_SIZE = 256;
|
|
606
|
+
var defaultStickerRasterizer = async (asset, size) => {
|
|
607
|
+
if (asset.kind === "image") {
|
|
608
|
+
const parsed = parseDataUrl(asset.url);
|
|
609
|
+
if (parsed && parsed.mime === "image/png") {
|
|
610
|
+
const png = decodePng(parsed.bytes);
|
|
611
|
+
return { width: png.width, height: png.height, data: new Uint8ClampedArray(png.data) };
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const g = globalThis;
|
|
615
|
+
if (!g.createImageBitmap || !g.OffscreenCanvas) {
|
|
616
|
+
throw new StickerError("unsupported_format", `This environment can't draw ${asset.kind === "svg" ? "SVG" : "non-PNG"} sticker art; pass rasterizeSticker in the paint editor's options (or use a provider that offers png).`);
|
|
617
|
+
}
|
|
618
|
+
const blob = asset.kind === "svg" ? new Blob([asset.svg], { type: "image/svg+xml" }) : await (await g.fetch(asset.url)).blob();
|
|
619
|
+
const bitmap = await g.createImageBitmap(blob);
|
|
620
|
+
const scale = size / Math.max(bitmap.width, bitmap.height, 1);
|
|
621
|
+
const w = Math.max(1, Math.round(bitmap.width * scale));
|
|
622
|
+
const h = Math.max(1, Math.round(bitmap.height * scale));
|
|
623
|
+
const canvas = new g.OffscreenCanvas(w, h);
|
|
624
|
+
const ctx = canvas.getContext("2d");
|
|
625
|
+
ctx.drawImage(bitmap, 0, 0, w, h);
|
|
626
|
+
const img = ctx.getImageData(0, 0, w, h);
|
|
627
|
+
return { width: w, height: h, data: img.data };
|
|
628
|
+
};
|
|
629
|
+
var StickerArtCache = class {
|
|
630
|
+
entries = /* @__PURE__ */ new Map();
|
|
631
|
+
/** The art for `ref` when it has loaded (editor stamps never need any). */
|
|
632
|
+
get = (ref) => {
|
|
633
|
+
const e = this.entries.get(ref);
|
|
634
|
+
return e?.status === "ready" ? e.art : void 0;
|
|
635
|
+
};
|
|
636
|
+
/** `ready`, `loading`, `failed` (with the reason) or `none` (never asked for). Editor stamps are always `ready`. */
|
|
637
|
+
status(ref) {
|
|
638
|
+
if (editorStampOf(ref)) return { status: "ready" };
|
|
639
|
+
const e = this.entries.get(ref);
|
|
640
|
+
if (!e) return { status: "none" };
|
|
641
|
+
return e.status === "failed" ? { status: "failed", error: e.error } : { status: e.status };
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Loads and rasterises `ref` from `provider` once (later calls share the load). Unknown refs
|
|
645
|
+
* reject with the closest known ones; failures are kept so they are reported, and retried on
|
|
646
|
+
* the next call.
|
|
647
|
+
*/
|
|
648
|
+
ensure(provider, ref, rasterize) {
|
|
649
|
+
if (editorStampOf(ref)) return Promise.resolve(null);
|
|
650
|
+
const e = this.entries.get(ref);
|
|
651
|
+
if (e?.status === "ready") return Promise.resolve(e.art);
|
|
652
|
+
if (e?.status === "loading") return e.promise;
|
|
653
|
+
const promise = (async () => {
|
|
654
|
+
const d = await describeStickerRef(provider, ref);
|
|
655
|
+
const id = stickerIdFor(provider, ref);
|
|
656
|
+
const max = provider.capabilities.maxSize ?? STICKER_ART_SIZE;
|
|
657
|
+
const size = Math.min(STICKER_ART_SIZE, max);
|
|
658
|
+
const asset = await provider.load(id, d.formats.includes("png") ? { format: "png", size } : { format: d.formats[0] ?? "svg", size });
|
|
659
|
+
const px = await rasterize(asset, size);
|
|
660
|
+
if (!(px.width > 0 && px.height > 0) || px.data.length !== px.width * px.height * 4) throw new StickerError("load_failed", `The art for "${ref}" came back empty or the wrong size.`);
|
|
661
|
+
return { ...px, tintable: d.tintable };
|
|
662
|
+
})();
|
|
663
|
+
this.entries.set(ref, { status: "loading", promise });
|
|
664
|
+
return promise.then(
|
|
665
|
+
(art) => {
|
|
666
|
+
this.entries.set(ref, { status: "ready", art });
|
|
667
|
+
return art;
|
|
668
|
+
},
|
|
669
|
+
(err) => {
|
|
670
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
671
|
+
this.entries.set(ref, { status: "failed", error: message });
|
|
672
|
+
throw err;
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
/** Forgets every load (a new provider). */
|
|
677
|
+
clear() {
|
|
678
|
+
this.entries.clear();
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
function sampleArt(art, x, y, out) {
|
|
682
|
+
const { width: w, height: h, data } = art;
|
|
683
|
+
const fx = x - 0.5;
|
|
684
|
+
const fy = y - 0.5;
|
|
685
|
+
const x0 = Math.floor(fx);
|
|
686
|
+
const y0 = Math.floor(fy);
|
|
687
|
+
const tx = fx - x0;
|
|
688
|
+
const ty = fy - y0;
|
|
689
|
+
let r = 0;
|
|
690
|
+
let g = 0;
|
|
691
|
+
let b = 0;
|
|
692
|
+
let a = 0;
|
|
693
|
+
for (let k = 0; k < 4; k++) {
|
|
694
|
+
const xx = x0 + (k & 1);
|
|
695
|
+
const yy = y0 + (k >> 1);
|
|
696
|
+
if (xx < 0 || yy < 0 || xx >= w || yy >= h) continue;
|
|
697
|
+
const wgt = (k & 1 ? tx : 1 - tx) * (k >> 1 ? ty : 1 - ty);
|
|
698
|
+
const i = (yy * w + xx) * 4;
|
|
699
|
+
const al = data[i + 3] * wgt;
|
|
700
|
+
r += data[i] * al;
|
|
701
|
+
g += data[i + 1] * al;
|
|
702
|
+
b += data[i + 2] * al;
|
|
703
|
+
a += al;
|
|
704
|
+
}
|
|
705
|
+
out[0] = a > 0 ? r / a / 255 : 0;
|
|
706
|
+
out[1] = a > 0 ? g / a / 255 : 0;
|
|
707
|
+
out[2] = a > 0 ? b / a / 255 : 0;
|
|
708
|
+
out[3] = a / 255;
|
|
709
|
+
return out;
|
|
710
|
+
}
|
|
711
|
+
|
|
589
712
|
// src/paint-editor/stickers.ts
|
|
590
|
-
var
|
|
591
|
-
var DEFAULT_STICKER = { stamps: ["star"], density: 0.4, sizeJitter: 0.4, rotationJitter: 1, scatter: 0.5, colorJitter: 0.2 };
|
|
713
|
+
var MAX_STICKER_STAMPS = 16;
|
|
714
|
+
var DEFAULT_STICKER = { stamps: ["editor:star"], density: 0.4, sizeJitter: 0.4, rotationJitter: 1, scatter: 0.5, colorJitter: 0.2 };
|
|
592
715
|
var unit2 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
|
|
593
716
|
function normalizeStickerSettings(raw = {}, base = DEFAULT_STICKER) {
|
|
594
|
-
const
|
|
717
|
+
const refs = (list) => list.flatMap((s) => {
|
|
718
|
+
try {
|
|
719
|
+
return [normalizeStickerRef(s)];
|
|
720
|
+
} catch {
|
|
721
|
+
return [];
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
const stamps = Array.isArray(raw.stamps) ? refs(raw.stamps) : refs(base.stamps);
|
|
595
725
|
return {
|
|
596
|
-
stamps: stamps.length ? [...new Set(stamps)] :
|
|
726
|
+
stamps: stamps.length ? [...new Set(stamps)].slice(0, MAX_STICKER_STAMPS) : refs(base.stamps.length ? base.stamps : DEFAULT_STICKER.stamps),
|
|
597
727
|
density: unit2(raw.density, base.density),
|
|
598
728
|
sizeJitter: unit2(raw.sizeJitter, base.sizeJitter),
|
|
599
729
|
rotationJitter: unit2(raw.rotationJitter, base.rotationJitter),
|
|
@@ -601,59 +731,6 @@ function normalizeStickerSettings(raw = {}, base = DEFAULT_STICKER) {
|
|
|
601
731
|
colorJitter: unit2(raw.colorJitter, base.colorJitter)
|
|
602
732
|
};
|
|
603
733
|
}
|
|
604
|
-
function stampCoverage(kind, u, v, t) {
|
|
605
|
-
const r = Math.hypot(u, v);
|
|
606
|
-
const a = Math.atan2(v, u);
|
|
607
|
-
const edge = (d) => Math.min(1, Math.max(0, d * 24 + 0.5));
|
|
608
|
-
switch (kind) {
|
|
609
|
-
case "dot":
|
|
610
|
-
return edge(0.95 - r);
|
|
611
|
-
case "ring":
|
|
612
|
-
return edge(Math.min(0.95 - r, r - 0.6));
|
|
613
|
-
case "star": {
|
|
614
|
-
const f = (a + Math.PI / 2) * 5 / (2 * Math.PI) % 1;
|
|
615
|
-
const notch = Math.min(f < 0 ? f + 1 : f, 1 - (f < 0 ? f + 1 : f)) * 2;
|
|
616
|
-
return edge(0.95 - 0.55 * notch - r);
|
|
617
|
-
}
|
|
618
|
-
case "sparkle": {
|
|
619
|
-
const q = Math.pow(Math.abs(u) / 0.95, 0.6) + Math.pow(Math.abs(v) / 0.95, 0.6);
|
|
620
|
-
return edge((1 - q) * 0.4);
|
|
621
|
-
}
|
|
622
|
-
case "heart": {
|
|
623
|
-
const x = u * 1.1;
|
|
624
|
-
const y = -v * 1.1 + 0.15;
|
|
625
|
-
const q = x * x + y * y - 0.55;
|
|
626
|
-
return edge(-(q * q * q - x * x * y * y * y) * 3);
|
|
627
|
-
}
|
|
628
|
-
case "leaf": {
|
|
629
|
-
const w = 0.42 * Math.sqrt(Math.max(0, 1 - u * u));
|
|
630
|
-
const inside = edge(w - Math.abs(v + 0.15 * u * u));
|
|
631
|
-
const rib = Math.abs(v + 0.15 * u * u) < 0.03 && Math.abs(u) < 0.85 ? 0.65 : 1;
|
|
632
|
-
return inside * rib;
|
|
633
|
-
}
|
|
634
|
-
case "flower": {
|
|
635
|
-
const petals = 0.6 + 0.35 * Math.abs(Math.cos(2.5 * a));
|
|
636
|
-
const body = edge(petals - r);
|
|
637
|
-
return r < 0.22 ? Math.max(body, 1) * 0.85 : body;
|
|
638
|
-
}
|
|
639
|
-
case "confetti": {
|
|
640
|
-
return edge(Math.min(0.9 - Math.abs(u), 0.35 - Math.abs(v)));
|
|
641
|
-
}
|
|
642
|
-
case "splat": {
|
|
643
|
-
let rr = 0.55;
|
|
644
|
-
for (let k = 1; k <= 3; k++) rr += 0.08 * Math.sin(a * (k + 2) + hash2(k, 1, t) * 6.28) / k;
|
|
645
|
-
let c = edge(rr - r);
|
|
646
|
-
for (let k = 0; k < 4; k++) {
|
|
647
|
-
const ang = hash2(k, 7, t) * Math.PI * 2;
|
|
648
|
-
const dist = 0.72 + 0.2 * hash2(k, 9, t);
|
|
649
|
-
const dx = u - Math.cos(ang) * dist;
|
|
650
|
-
const dy = v - Math.sin(ang) * dist;
|
|
651
|
-
c = Math.max(c, edge(0.08 + 0.06 * hash2(k, 11, t) - Math.hypot(dx, dy)));
|
|
652
|
-
}
|
|
653
|
-
return c;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
734
|
function hexRgb(hex) {
|
|
658
735
|
const n = parseInt(hex.slice(1, 7), 16);
|
|
659
736
|
return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
|
|
@@ -664,7 +741,22 @@ function stickerRect(s) {
|
|
|
664
741
|
const y0 = Math.floor(s.y - r);
|
|
665
742
|
return { x: x0, y: y0, width: Math.ceil(s.x + r) - x0, height: Math.ceil(s.y + r) - y0 };
|
|
666
743
|
}
|
|
667
|
-
function
|
|
744
|
+
function stickerStamp(s) {
|
|
745
|
+
if (s.ref) {
|
|
746
|
+
try {
|
|
747
|
+
return editorStampOf(s.ref);
|
|
748
|
+
} catch {
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return s.stamp && STAMP_KINDS.includes(s.stamp) ? s.stamp : null;
|
|
753
|
+
}
|
|
754
|
+
function drawSticker(medium, s, art) {
|
|
755
|
+
const kind = stickerStamp(s);
|
|
756
|
+
if (!kind) {
|
|
757
|
+
const a2 = art?.(s.ref);
|
|
758
|
+
return a2 ? drawArtSticker(medium, s, a2) : stickerRect(s);
|
|
759
|
+
}
|
|
668
760
|
const rect = stickerRect(s);
|
|
669
761
|
const n = rect.width * rect.height;
|
|
670
762
|
const cov = new Float32Array(n);
|
|
@@ -682,7 +774,7 @@ function drawSticker(medium, s) {
|
|
|
682
774
|
const v = (-dx * sa + dy * ca) / rad;
|
|
683
775
|
if (u < -1.05 || u > 1.05 || v < -1.05 || v > 1.05) continue;
|
|
684
776
|
let c = 0;
|
|
685
|
-
for (let k = 0; k < 4; k++) c += stampCoverage(
|
|
777
|
+
for (let k = 0; k < 4; k++) c += stampCoverage(kind, u + ((k & 1) - 0.5) / rad / 2, v + ((k >> 1) - 0.5) / rad / 2, s.seed);
|
|
686
778
|
c /= 4;
|
|
687
779
|
if (c <= 0) continue;
|
|
688
780
|
cov[p] = c;
|
|
@@ -695,6 +787,35 @@ function drawSticker(medium, s) {
|
|
|
695
787
|
medium.deposit(rect, cov, s.opacity, [r, g, b], null, field);
|
|
696
788
|
return rect;
|
|
697
789
|
}
|
|
790
|
+
function drawArtSticker(medium, s, art) {
|
|
791
|
+
const rect = stickerRect(s);
|
|
792
|
+
const n = rect.width * rect.height;
|
|
793
|
+
const cov = new Float32Array(n);
|
|
794
|
+
const field = new Float32Array(n * 3);
|
|
795
|
+
const a = s.rotation * Math.PI / 180;
|
|
796
|
+
const ca = Math.cos(a);
|
|
797
|
+
const sa = Math.sin(a);
|
|
798
|
+
const scale = Math.max(art.width, art.height) / s.size;
|
|
799
|
+
const [r, g, b] = hexRgb(s.color);
|
|
800
|
+
const px = [0, 0, 0, 0];
|
|
801
|
+
for (let yy = 0, p = 0; yy < rect.height; yy++) {
|
|
802
|
+
for (let xx = 0; xx < rect.width; xx++, p++) {
|
|
803
|
+
const dx = rect.x + xx + 0.5 - s.x;
|
|
804
|
+
const dy = rect.y + yy + 0.5 - s.y;
|
|
805
|
+
const ax = (dx * ca + dy * sa) * scale + art.width / 2;
|
|
806
|
+
const ay = (-dx * sa + dy * ca) * scale + art.height / 2;
|
|
807
|
+
if (ax < -1 || ay < -1 || ax > art.width + 1 || ay > art.height + 1) continue;
|
|
808
|
+
sampleArt(art, ax, ay, px);
|
|
809
|
+
if (!(px[3] > 0)) continue;
|
|
810
|
+
cov[p] = px[3];
|
|
811
|
+
field[p * 3] = art.tintable ? r : px[0];
|
|
812
|
+
field[p * 3 + 1] = art.tintable ? g : px[1];
|
|
813
|
+
field[p * 3 + 2] = art.tintable ? b : px[2];
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
medium.deposit(rect, cov, s.opacity, [r, g, b], null, field);
|
|
817
|
+
return rect;
|
|
818
|
+
}
|
|
698
819
|
function jitterColor(hex, amount, seed, i) {
|
|
699
820
|
const [r, g, b] = hexRgb(hex);
|
|
700
821
|
const lch = oklabToOklch(rgbToOklab({ r, g, b, a: 1 }));
|
|
@@ -730,6 +851,8 @@ var StickerSpray = class {
|
|
|
730
851
|
init;
|
|
731
852
|
dabCount = 0;
|
|
732
853
|
stickers = [];
|
|
854
|
+
/** Refs the spray picked but could not draw because their art had not loaded. */
|
|
855
|
+
skipped = /* @__PURE__ */ new Set();
|
|
733
856
|
smoother;
|
|
734
857
|
walkers;
|
|
735
858
|
copies;
|
|
@@ -776,9 +899,17 @@ var StickerSpray = class {
|
|
|
776
899
|
this.next++;
|
|
777
900
|
return;
|
|
778
901
|
}
|
|
902
|
+
const ref = st.stamps[Math.floor(rand01(seed, i, 57) * st.stamps.length)] ?? "editor:dot";
|
|
903
|
+
const stamp = stickerStamp({ ref });
|
|
904
|
+
if (!stamp && !this.init.art?.(ref)) {
|
|
905
|
+
this.skipped.add(ref);
|
|
906
|
+
this.next++;
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
779
909
|
const sticker = {
|
|
780
910
|
id: `st${this.next++}`,
|
|
781
|
-
|
|
911
|
+
ref,
|
|
912
|
+
...stamp ? { stamp } : {},
|
|
782
913
|
x: Math.round((d.x + Math.cos(a) * dist) * 100) / 100,
|
|
783
914
|
y: Math.round((d.y + Math.sin(a) * dist) * 100) / 100,
|
|
784
915
|
size: Math.max(2, Math.round(size * 10) / 10),
|
|
@@ -788,7 +919,7 @@ var StickerSpray = class {
|
|
|
788
919
|
seed: Math.floor(rand01(seed, i, 65) * 1e9)
|
|
789
920
|
};
|
|
790
921
|
this.stickers.push(sticker);
|
|
791
|
-
rects.push(drawSticker(this.medium, sticker));
|
|
922
|
+
rects.push(drawSticker(this.medium, sticker, this.init.art));
|
|
792
923
|
this.dabCount++;
|
|
793
924
|
}
|
|
794
925
|
sampler;
|
|
@@ -807,16 +938,24 @@ var StickerSpray = class {
|
|
|
807
938
|
return { color: "#" + s.rgb.map((c) => Math.round(Math.min(1, Math.max(0, c)) * 255).toString(16).padStart(2, "0")).join(""), alpha: s.alpha };
|
|
808
939
|
}
|
|
809
940
|
};
|
|
810
|
-
function renderStickers(stickers, target) {
|
|
941
|
+
function renderStickers(stickers, target, art) {
|
|
811
942
|
const medium = new TileMedium(target, {});
|
|
812
|
-
for (const s of stickers) drawSticker(medium, s);
|
|
943
|
+
for (const s of stickers) drawSticker(medium, s, art);
|
|
813
944
|
}
|
|
814
945
|
function normalizeStickerUpdate(u) {
|
|
815
946
|
const out = {};
|
|
947
|
+
if (u.ref !== void 0 && u.stamp !== void 0) throw new Error("Give ref or stamp, not both (stamp is the older name for an editor stamp).");
|
|
816
948
|
if (u.stamp !== void 0) {
|
|
817
|
-
if (!STAMP_KINDS.includes(u.stamp)) throw new Error(`stamp ${JSON.stringify(u.stamp)} is not a stamp; use ${STAMP_KINDS.join(", ")}.`);
|
|
949
|
+
if (!STAMP_KINDS.includes(u.stamp)) throw new Error(`stamp ${JSON.stringify(u.stamp)} is not a stamp; use ${STAMP_KINDS.join(", ")}, or ref for any sticker (find_stickers).`);
|
|
950
|
+
out.ref = `editor:${u.stamp}`;
|
|
818
951
|
out.stamp = u.stamp;
|
|
819
952
|
}
|
|
953
|
+
if (u.ref !== void 0) {
|
|
954
|
+
out.ref = normalizeStickerRef(u.ref);
|
|
955
|
+
const stamp = stickerStamp({ ref: out.ref });
|
|
956
|
+
if (stamp) out.stamp = stamp;
|
|
957
|
+
else out.stamp = void 0;
|
|
958
|
+
}
|
|
820
959
|
for (const k of ["x", "y"]) if (u[k] !== void 0) {
|
|
821
960
|
if (!Number.isFinite(u[k])) throw new Error(`${k} must be a number of pixels.`);
|
|
822
961
|
out[k] = u[k];
|
|
@@ -839,7 +978,7 @@ function normalizeStickerUpdate(u) {
|
|
|
839
978
|
}
|
|
840
979
|
return out;
|
|
841
980
|
}
|
|
842
|
-
function stickerSurface(stickers, base, dirty) {
|
|
981
|
+
function stickerSurface(stickers, base, dirty, art) {
|
|
843
982
|
const only = base && dirty ? new Set(dirty.flatMap((r) => tilesInRect(r).map(({ tx, ty }) => tileKey(tx, ty)))) : null;
|
|
844
983
|
const live = /* @__PURE__ */ new Map();
|
|
845
984
|
let scratch = null;
|
|
@@ -857,7 +996,7 @@ function stickerSurface(stickers, base, dirty) {
|
|
|
857
996
|
}
|
|
858
997
|
};
|
|
859
998
|
const reaches = (s) => !only || tilesInRect(stickerRect(s)).some(({ tx, ty }) => only.has(tileKey(tx, ty)));
|
|
860
|
-
renderStickers(stickers.filter(reaches), target);
|
|
999
|
+
renderStickers(stickers.filter(reaches), target, art);
|
|
861
1000
|
const tiles = /* @__PURE__ */ new Map();
|
|
862
1001
|
if (only && base) {
|
|
863
1002
|
for (const [k, t] of base.tiles) if (!only.has(k)) tiles.set(k, t);
|
|
@@ -868,7 +1007,7 @@ function stickerSurface(stickers, base, dirty) {
|
|
|
868
1007
|
}
|
|
869
1008
|
return new TiledSurface(4, tiles, 0);
|
|
870
1009
|
}
|
|
871
|
-
function stickerAt(stickers, x, y) {
|
|
1010
|
+
function stickerAt(stickers, x, y, art) {
|
|
872
1011
|
for (let i = stickers.length - 1; i >= 0; i--) {
|
|
873
1012
|
const s = stickers[i];
|
|
874
1013
|
const rad = s.size / 2;
|
|
@@ -878,7 +1017,16 @@ function stickerAt(stickers, x, y) {
|
|
|
878
1017
|
const u = (dx * Math.cos(a) + dy * Math.sin(a)) / rad;
|
|
879
1018
|
const v = (-dx * Math.sin(a) + dy * Math.cos(a)) / rad;
|
|
880
1019
|
if (Math.abs(u) > 1.05 || Math.abs(v) > 1.05) continue;
|
|
881
|
-
if (s.size < 16
|
|
1020
|
+
if (s.size < 16) return s;
|
|
1021
|
+
const kind = stickerStamp(s);
|
|
1022
|
+
if (kind) {
|
|
1023
|
+
if (stampCoverage(kind, u, v, s.seed) > 0.25) return s;
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
const pic = art?.(s.ref);
|
|
1027
|
+
if (!pic) return s;
|
|
1028
|
+
const scale = Math.max(pic.width, pic.height) / 2;
|
|
1029
|
+
if (sampleArt(pic, u * scale + pic.width / 2, v * scale + pic.height / 2, [0, 0, 0, 0])[3] > 0.25) return s;
|
|
882
1030
|
}
|
|
883
1031
|
return null;
|
|
884
1032
|
}
|
|
@@ -964,9 +1112,56 @@ var MarkStroke = class {
|
|
|
964
1112
|
const rect = dabRect(dab, hardness);
|
|
965
1113
|
const n = rect.width * rect.height;
|
|
966
1114
|
if (n <= 0) return;
|
|
1115
|
+
const vals = this.scratch(n);
|
|
1116
|
+
rasterizeDab(dab, hardness, rect, vals);
|
|
1117
|
+
this.lay(rect, vals, dab.opacity, pressure, x, y, rects);
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Stamps a textured mark: `kind` from the sticker stamps over a square of half-size `r`, turned
|
|
1121
|
+
* `rotation` radians clockwise, with flow `alpha` towards `opacity`. Small marks are supersampled.
|
|
1122
|
+
*/
|
|
1123
|
+
stamp(x, y, r, kind, rotation, seed, alpha, opacity, pressure, rects) {
|
|
1124
|
+
if (!(r > 0.05) || !(alpha > 0)) return;
|
|
1125
|
+
const reach = Math.max(0.5, r) * Math.SQRT2 + 1;
|
|
1126
|
+
const rect = { x: Math.floor(x - reach), y: Math.floor(y - reach), width: 0, height: 0 };
|
|
1127
|
+
rect.width = Math.ceil(x + reach) - rect.x;
|
|
1128
|
+
rect.height = Math.ceil(y + reach) - rect.y;
|
|
1129
|
+
const n = rect.width * rect.height;
|
|
1130
|
+
const vals = this.scratch(n);
|
|
1131
|
+
const ss = r < 10 ? 3 : 1;
|
|
1132
|
+
const c = Math.cos(rotation);
|
|
1133
|
+
const s = Math.sin(rotation);
|
|
1134
|
+
const inv = 1 / Math.max(0.5, r);
|
|
1135
|
+
const a = Math.min(1, alpha);
|
|
1136
|
+
for (let yy = 0, k = 0; yy < rect.height; yy++) {
|
|
1137
|
+
for (let xx = 0; xx < rect.width; xx++, k++) {
|
|
1138
|
+
let sum = 0;
|
|
1139
|
+
for (let sy = 0; sy < ss; sy++) {
|
|
1140
|
+
for (let sx = 0; sx < ss; sx++) {
|
|
1141
|
+
const dx = rect.x + xx + (sx + 0.5) / ss - x;
|
|
1142
|
+
const dy = rect.y + yy + (sy + 0.5) / ss - y;
|
|
1143
|
+
sum += stampCoverage(kind, (dx * c + dy * s) * inv, (dy * c - dx * s) * inv, seed);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
vals[k] = sum / (ss * ss) * a;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
this.lay(rect, vals, Math.min(1, opacity), pressure, x, y, rects);
|
|
1150
|
+
}
|
|
1151
|
+
/** A scratch buffer of `n` values (reused; valid until the next mark). */
|
|
1152
|
+
scratch(n) {
|
|
967
1153
|
if (this.buf.length < n) this.buf = new Float32Array(n);
|
|
968
1154
|
const vals = this.buf.subarray(0, n);
|
|
969
|
-
|
|
1155
|
+
vals.fill(0);
|
|
1156
|
+
return vals;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Lays coverage `vals` over `rect` towards `opacity`: paper grain, then effect paint or the
|
|
1160
|
+
* colour of a mark at (x, y) (gradient paint), into the stroke's coverage.
|
|
1161
|
+
*/
|
|
1162
|
+
lay(rect, vals, opacity, pressure, x, y, rects) {
|
|
1163
|
+
const n = rect.width * rect.height;
|
|
1164
|
+
if (n <= 0) return;
|
|
970
1165
|
const grain = this.options.grain;
|
|
971
1166
|
if (this.depth > 0 && grain) {
|
|
972
1167
|
for (let yy = 0, k = 0; yy < rect.height; yy++)
|
|
@@ -976,11 +1171,11 @@ var MarkStroke = class {
|
|
|
976
1171
|
if (this.field.length < n * 3) this.field = new Float32Array(n * 3);
|
|
977
1172
|
const field = this.field.subarray(0, n * 3);
|
|
978
1173
|
sampleEffectField(this.options.effect, rect, vals, field, this.pen);
|
|
979
|
-
this.coverage.accumulate(rect, vals,
|
|
1174
|
+
this.coverage.accumulate(rect, vals, opacity, null, field);
|
|
980
1175
|
} else {
|
|
981
1176
|
const color = this.markColor(x, y);
|
|
982
1177
|
if (color && color.alpha < 1) for (let k = 0; k < n; k++) vals[k] *= color.alpha;
|
|
983
|
-
this.coverage.accumulate(rect, vals,
|
|
1178
|
+
this.coverage.accumulate(rect, vals, opacity, color ? color.rgb : null);
|
|
984
1179
|
}
|
|
985
1180
|
rects.push(rect);
|
|
986
1181
|
this.dabCount++;
|
|
@@ -993,14 +1188,41 @@ var MarkStroke = class {
|
|
|
993
1188
|
for (let i = 1; i <= n; i++) this.mark(x0 + (x1 - x0) * i / n, y0 + (y1 - y0) * i / n, r, alpha, opacity, pressure, rects);
|
|
994
1189
|
}
|
|
995
1190
|
};
|
|
996
|
-
var PARTICLE_KINDS = ["string", "spring", "orbit", "flow"];
|
|
997
|
-
var PARTICLE_RENDERS = ["dot", "line", "sequence"];
|
|
998
|
-
var
|
|
1191
|
+
var PARTICLE_KINDS = ["string", "spring", "orbit", "flow", "elastic", "fuzzy"];
|
|
1192
|
+
var PARTICLE_RENDERS = ["dot", "line", "sequence", "ribbon"];
|
|
1193
|
+
var RIBBON_PAIRS = ["adjacent", "first-last"];
|
|
1194
|
+
var DOT_TEXTURE_ORDERS = ["sequence", "random"];
|
|
1195
|
+
var DOT_ROTATIONS = ["follow", "random", "fixed"];
|
|
1196
|
+
var MAX_DOT_TEXTURES = 4;
|
|
1197
|
+
var DEFAULT_PARTICLE = {
|
|
1198
|
+
kind: "spring",
|
|
1199
|
+
render: "line",
|
|
1200
|
+
count: 12,
|
|
1201
|
+
size: 0.08,
|
|
1202
|
+
spread: 0.5,
|
|
1203
|
+
stiffness: 0.4,
|
|
1204
|
+
damping: 0.25,
|
|
1205
|
+
speed: 0.5,
|
|
1206
|
+
gravity: 0,
|
|
1207
|
+
life: 0.5,
|
|
1208
|
+
interval: 0.3,
|
|
1209
|
+
ribbonFill: 0.5,
|
|
1210
|
+
ribbonPairs: "adjacent",
|
|
1211
|
+
waveAmplitude: 0,
|
|
1212
|
+
waveLength: 60,
|
|
1213
|
+
waveSpeed: 0,
|
|
1214
|
+
wavePhaseJitter: 0,
|
|
1215
|
+
dotTextures: [],
|
|
1216
|
+
dotTextureOrder: "sequence",
|
|
1217
|
+
dotRotation: "random"
|
|
1218
|
+
};
|
|
999
1219
|
var unit3 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
|
|
1220
|
+
var oneOf = (v, all, d) => all.includes(v) ? v : d;
|
|
1000
1221
|
function normalizeParticle(raw = {}, base = DEFAULT_PARTICLE) {
|
|
1222
|
+
const textures = Array.isArray(raw.dotTextures) ? raw.dotTextures.filter((t) => STAMP_KINDS.includes(t)).slice(0, MAX_DOT_TEXTURES) : [...base.dotTextures ?? []];
|
|
1001
1223
|
return {
|
|
1002
|
-
kind:
|
|
1003
|
-
render:
|
|
1224
|
+
kind: oneOf(raw.kind, PARTICLE_KINDS, base.kind),
|
|
1225
|
+
render: oneOf(raw.render, PARTICLE_RENDERS, base.render),
|
|
1004
1226
|
count: typeof raw.count === "number" && Number.isFinite(raw.count) ? Math.min(64, Math.max(1, Math.round(raw.count))) : base.count,
|
|
1005
1227
|
size: unit3(raw.size, base.size),
|
|
1006
1228
|
spread: unit3(raw.spread, base.spread),
|
|
@@ -1009,9 +1231,61 @@ function normalizeParticle(raw = {}, base = DEFAULT_PARTICLE) {
|
|
|
1009
1231
|
speed: unit3(raw.speed, base.speed),
|
|
1010
1232
|
gravity: typeof raw.gravity === "number" && Number.isFinite(raw.gravity) ? Math.min(1, Math.max(-1, raw.gravity)) : base.gravity,
|
|
1011
1233
|
life: unit3(raw.life, base.life),
|
|
1012
|
-
interval: unit3(raw.interval, base.interval)
|
|
1234
|
+
interval: unit3(raw.interval, base.interval),
|
|
1235
|
+
ribbonFill: unit3(raw.ribbonFill, base.ribbonFill ?? DEFAULT_PARTICLE.ribbonFill),
|
|
1236
|
+
ribbonPairs: oneOf(raw.ribbonPairs, RIBBON_PAIRS, base.ribbonPairs ?? DEFAULT_PARTICLE.ribbonPairs),
|
|
1237
|
+
waveAmplitude: unit3(raw.waveAmplitude, base.waveAmplitude ?? 0),
|
|
1238
|
+
waveLength: typeof raw.waveLength === "number" && Number.isFinite(raw.waveLength) ? Math.min(2e3, Math.max(4, raw.waveLength)) : base.waveLength ?? DEFAULT_PARTICLE.waveLength,
|
|
1239
|
+
waveSpeed: unit3(raw.waveSpeed, base.waveSpeed ?? 0),
|
|
1240
|
+
wavePhaseJitter: unit3(raw.wavePhaseJitter, base.wavePhaseJitter ?? 0),
|
|
1241
|
+
dotTextures: textures,
|
|
1242
|
+
dotTextureOrder: oneOf(raw.dotTextureOrder, DOT_TEXTURE_ORDERS, base.dotTextureOrder ?? DEFAULT_PARTICLE.dotTextureOrder),
|
|
1243
|
+
dotRotation: oneOf(raw.dotRotation, DOT_ROTATIONS, base.dotRotation ?? DEFAULT_PARTICLE.dotRotation)
|
|
1013
1244
|
};
|
|
1014
1245
|
}
|
|
1246
|
+
var FUZZY_AMPLITUDE = 0.12;
|
|
1247
|
+
var NOMINAL_MS_PER_PX = 2;
|
|
1248
|
+
var RIBBON_SUB = 4;
|
|
1249
|
+
function chainDamp(damp, k) {
|
|
1250
|
+
return Math.min(damp, 1 / (1 + 0.8 * Math.sqrt(Math.max(0, k))) ** 2);
|
|
1251
|
+
}
|
|
1252
|
+
function addPolygonArea(pts, rect, out, weight = 1) {
|
|
1253
|
+
const n = pts.length >> 1;
|
|
1254
|
+
if (n < 3) return;
|
|
1255
|
+
let y0 = Infinity;
|
|
1256
|
+
let y1 = -Infinity;
|
|
1257
|
+
for (let i = 1; i < pts.length; i += 2) {
|
|
1258
|
+
if (pts[i] < y0) y0 = pts[i];
|
|
1259
|
+
if (pts[i] > y1) y1 = pts[i];
|
|
1260
|
+
}
|
|
1261
|
+
const r0 = Math.max(0, Math.floor(y0) - rect.y);
|
|
1262
|
+
const r1 = Math.min(rect.height - 1, Math.floor(y1) - rect.y);
|
|
1263
|
+
const xs = [];
|
|
1264
|
+
const w = weight / RIBBON_SUB;
|
|
1265
|
+
for (let row = r0; row <= r1; row++) {
|
|
1266
|
+
const base = row * rect.width;
|
|
1267
|
+
for (let sub = 0; sub < RIBBON_SUB; sub++) {
|
|
1268
|
+
const y = rect.y + row + (sub + 0.5) / RIBBON_SUB;
|
|
1269
|
+
xs.length = 0;
|
|
1270
|
+
for (let i = 0, j = n - 1; i < n; j = i++) {
|
|
1271
|
+
const yi = pts[i * 2 + 1];
|
|
1272
|
+
const yj = pts[j * 2 + 1];
|
|
1273
|
+
if (yi <= y !== yj <= y) xs.push(pts[i * 2] + (y - yi) / (yj - yi) * (pts[j * 2] - pts[i * 2]));
|
|
1274
|
+
}
|
|
1275
|
+
if (xs.length < 2) continue;
|
|
1276
|
+
xs.sort((a, b) => a - b);
|
|
1277
|
+
for (let k = 0; k + 1 < xs.length; k += 2) {
|
|
1278
|
+
const a = Math.max(rect.x, xs[k]);
|
|
1279
|
+
const b = Math.min(rect.x + rect.width, xs[k + 1]);
|
|
1280
|
+
if (!(b > a)) continue;
|
|
1281
|
+
for (let px = Math.floor(a); px < b; px++) {
|
|
1282
|
+
const cover = Math.min(b, px + 1) - Math.max(a, px);
|
|
1283
|
+
if (cover > 0) out[base + px - rect.x] += cover * w;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1015
1289
|
function curl(x, y, seed) {
|
|
1016
1290
|
const n = (u, v) => {
|
|
1017
1291
|
const x0 = Math.floor(u);
|
|
@@ -1032,12 +1306,51 @@ function curl(x, y, seed) {
|
|
|
1032
1306
|
}
|
|
1033
1307
|
var ParticleStroke = class extends MarkStroke {
|
|
1034
1308
|
sets;
|
|
1309
|
+
/** Ribbon pairs per copy: particles in their order around the pen (non-flow kinds). */
|
|
1310
|
+
orders;
|
|
1035
1311
|
s;
|
|
1036
1312
|
spawn = 0;
|
|
1313
|
+
/** Textured dots drawn so far, per copy (sequence order, random picks). */
|
|
1314
|
+
dots;
|
|
1315
|
+
/** Ribbon fill: the area each pixel has had so far this stroke (telescoped into the coverage). */
|
|
1316
|
+
filled = /* @__PURE__ */ new Map();
|
|
1317
|
+
pts = [];
|
|
1037
1318
|
constructor(brush, options) {
|
|
1038
1319
|
super(brush, options);
|
|
1039
1320
|
this.s = brush.particle;
|
|
1040
1321
|
this.sets = this.copies.map(() => []);
|
|
1322
|
+
this.orders = this.copies.map(() => []);
|
|
1323
|
+
this.dots = this.copies.map(() => 0);
|
|
1324
|
+
}
|
|
1325
|
+
/** Strand kinds: a chain of masses behind each particle. */
|
|
1326
|
+
get strands() {
|
|
1327
|
+
return this.s.kind === "string" || this.s.kind === "elastic" || this.s.kind === "fuzzy";
|
|
1328
|
+
}
|
|
1329
|
+
newParticle(x, y, ox, oy, k, phase, i, copy) {
|
|
1330
|
+
const s = this.s;
|
|
1331
|
+
const fuzzy = s.kind === "fuzzy";
|
|
1332
|
+
const jitter = fuzzy ? 1 : s.wavePhaseJitter;
|
|
1333
|
+
return {
|
|
1334
|
+
x,
|
|
1335
|
+
y,
|
|
1336
|
+
vx: 0,
|
|
1337
|
+
vy: 0,
|
|
1338
|
+
ox,
|
|
1339
|
+
oy,
|
|
1340
|
+
k,
|
|
1341
|
+
phase,
|
|
1342
|
+
age: 0,
|
|
1343
|
+
run: 0,
|
|
1344
|
+
bead: 0,
|
|
1345
|
+
alive: true,
|
|
1346
|
+
wavePhase: jitter > 0 ? jitter * Math.PI * 2 * rand01(this.seed, i, 43 + copy) : 0,
|
|
1347
|
+
waveScale: fuzzy ? 0.6 + 0.8 * rand01(this.seed, i, 47 + copy) : 1,
|
|
1348
|
+
dx: x,
|
|
1349
|
+
dy: y,
|
|
1350
|
+
px: NaN,
|
|
1351
|
+
py: NaN,
|
|
1352
|
+
fade: 1
|
|
1353
|
+
};
|
|
1041
1354
|
}
|
|
1042
1355
|
init(copy, d) {
|
|
1043
1356
|
const s = this.s;
|
|
@@ -1048,18 +1361,28 @@ var ParticleStroke = class extends MarkStroke {
|
|
|
1048
1361
|
const a = rand01(this.seed, i, 5 + copy) * Math.PI * 2;
|
|
1049
1362
|
const ox = Math.cos(a) * r;
|
|
1050
1363
|
const oy = Math.sin(a) * r;
|
|
1051
|
-
const p =
|
|
1052
|
-
if (
|
|
1364
|
+
const p = this.newParticle(d.x + ox, d.y + oy, ox, oy, 0.6 + 0.8 * rand01(this.seed, i, 7), a, i, copy);
|
|
1365
|
+
if (this.strands) p.strand = Array.from({ length: 6 }, () => ({ x: d.x + ox, y: d.y + oy, vx: 0, vy: 0 }));
|
|
1053
1366
|
if (s.kind === "orbit") {
|
|
1054
1367
|
p.ox = Math.max(1, r + size * 0.15);
|
|
1055
1368
|
p.oy = 0;
|
|
1056
1369
|
}
|
|
1057
1370
|
list.push(p);
|
|
1058
1371
|
}
|
|
1372
|
+
this.orders[copy] = list.map((_, i) => i).sort((i, j) => list[i].phase - list[j].phase || i - j);
|
|
1373
|
+
}
|
|
1374
|
+
/** The sideways wave of particle `p` at pen dab `d` (px along the normal to the pen's direction). */
|
|
1375
|
+
wave(p, d, size) {
|
|
1376
|
+
const s = this.s;
|
|
1377
|
+
const amp = s.kind === "fuzzy" ? Math.max(FUZZY_AMPLITUDE, s.waveAmplitude) : s.waveAmplitude;
|
|
1378
|
+
if (!(amp > 0)) return 0;
|
|
1379
|
+
const ms = d.time ?? d.travel * NOMINAL_MS_PER_PX;
|
|
1380
|
+
const phase = 2 * Math.PI * d.travel / (s.waveLength * p.waveScale) - s.waveSpeed * 0.025 * ms + p.wavePhase;
|
|
1381
|
+
return amp * size * Math.sin(phase);
|
|
1059
1382
|
}
|
|
1060
1383
|
advance(copy, d, rects) {
|
|
1061
1384
|
const s = this.s;
|
|
1062
|
-
|
|
1385
|
+
let list = this.sets[copy];
|
|
1063
1386
|
if (d.index === 0 && !list.length) this.init(copy, d);
|
|
1064
1387
|
const size = Math.max(1, d.radius * 2);
|
|
1065
1388
|
const markR = Math.max(0.5, size * s.size / 2);
|
|
@@ -1068,6 +1391,8 @@ var ParticleStroke = class extends MarkStroke {
|
|
|
1068
1391
|
const g = s.gravity * 0.15;
|
|
1069
1392
|
const op = d.opacity;
|
|
1070
1393
|
const al = Math.max(0.05, d.alpha);
|
|
1394
|
+
const nx = Math.sin(d.direction);
|
|
1395
|
+
const ny = Math.cos(d.direction);
|
|
1071
1396
|
if (s.kind === "flow") {
|
|
1072
1397
|
this.spawn += s.count / 8;
|
|
1073
1398
|
while (this.spawn >= 1) {
|
|
@@ -1075,15 +1400,13 @@ var ParticleStroke = class extends MarkStroke {
|
|
|
1075
1400
|
const i = this.dabCount + list.length * 31 + Math.floor(d.index);
|
|
1076
1401
|
const a = rand01(this.seed, i, 11 + copy) * Math.PI * 2;
|
|
1077
1402
|
const r = rand01(this.seed, i, 13 + copy) * s.spread * size * 0.5;
|
|
1078
|
-
list.push(
|
|
1403
|
+
list.push(this.newParticle(d.x + Math.cos(a) * r, d.y + Math.sin(a) * r, 0, 0, 1, a, i, copy));
|
|
1079
1404
|
}
|
|
1080
1405
|
}
|
|
1081
1406
|
const lifeSteps = 8 + 120 * s.life;
|
|
1082
1407
|
for (let i = 0; i < list.length; i++) {
|
|
1083
1408
|
const p = list[i];
|
|
1084
1409
|
if (!p.alive) continue;
|
|
1085
|
-
const px = p.x;
|
|
1086
|
-
const py = p.y;
|
|
1087
1410
|
switch (s.kind) {
|
|
1088
1411
|
case "spring": {
|
|
1089
1412
|
p.vx = (p.vx + (d.x + p.ox - p.x) * k * p.k) * damp;
|
|
@@ -1092,13 +1415,38 @@ var ParticleStroke = class extends MarkStroke {
|
|
|
1092
1415
|
p.y += p.vy;
|
|
1093
1416
|
break;
|
|
1094
1417
|
}
|
|
1095
|
-
case "string":
|
|
1418
|
+
case "string":
|
|
1419
|
+
case "fuzzy": {
|
|
1096
1420
|
const strand = p.strand;
|
|
1421
|
+
const dm = s.kind === "fuzzy" ? chainDamp(damp, k * p.k) : damp;
|
|
1097
1422
|
let ax = d.x + p.ox;
|
|
1098
1423
|
let ay = d.y + p.oy;
|
|
1099
1424
|
for (const m of strand) {
|
|
1100
|
-
m.vx = (m.vx + (ax - m.x) * k * p.k + 0) *
|
|
1101
|
-
m.vy = (m.vy + (ay - m.y) * k * p.k + g) *
|
|
1425
|
+
m.vx = (m.vx + (ax - m.x) * k * p.k + 0) * dm;
|
|
1426
|
+
m.vy = (m.vy + (ay - m.y) * k * p.k + g) * dm;
|
|
1427
|
+
m.x += m.vx;
|
|
1428
|
+
m.y += m.vy;
|
|
1429
|
+
ax = m.x;
|
|
1430
|
+
ay = m.y;
|
|
1431
|
+
}
|
|
1432
|
+
p.x = strand[strand.length - 1].x;
|
|
1433
|
+
p.y = strand[strand.length - 1].y;
|
|
1434
|
+
break;
|
|
1435
|
+
}
|
|
1436
|
+
case "elastic": {
|
|
1437
|
+
const strand = p.strand;
|
|
1438
|
+
const rest = size * 0.12;
|
|
1439
|
+
const ke = Math.min(0.9, (0.1 + 0.6 * s.stiffness) * p.k);
|
|
1440
|
+
const dm = chainDamp(damp, ke);
|
|
1441
|
+
let ax = d.x + p.ox;
|
|
1442
|
+
let ay = d.y + p.oy;
|
|
1443
|
+
for (const m of strand) {
|
|
1444
|
+
const ex = ax - m.x;
|
|
1445
|
+
const ey = ay - m.y;
|
|
1446
|
+
const len = Math.hypot(ex, ey);
|
|
1447
|
+
const pull = len > rest ? (len - rest) / len : 0;
|
|
1448
|
+
m.vx = (m.vx + ex * pull * ke) * dm;
|
|
1449
|
+
m.vy = (m.vy + ey * pull * ke + g) * dm;
|
|
1102
1450
|
m.x += m.vx;
|
|
1103
1451
|
m.y += m.vy;
|
|
1104
1452
|
ax = m.x;
|
|
@@ -1127,11 +1475,21 @@ var ParticleStroke = class extends MarkStroke {
|
|
|
1127
1475
|
break;
|
|
1128
1476
|
}
|
|
1129
1477
|
}
|
|
1478
|
+
const w = this.wave(p, d, size);
|
|
1479
|
+
const qx = w ? p.x + nx * w : p.x;
|
|
1480
|
+
const qy = w ? p.y + ny * w : p.y;
|
|
1481
|
+
const px = p.dx;
|
|
1482
|
+
const py = p.dy;
|
|
1483
|
+
p.px = p.dx;
|
|
1484
|
+
p.py = p.dy;
|
|
1485
|
+
p.dx = qx;
|
|
1486
|
+
p.dy = qy;
|
|
1130
1487
|
const fade = s.kind === "flow" ? 1 - p.age / lifeSteps : 1;
|
|
1131
|
-
|
|
1488
|
+
p.fade = fade;
|
|
1489
|
+
const moved = Math.hypot(qx - px, qy - py);
|
|
1132
1490
|
if (d.index === 0 && s.kind !== "flow") continue;
|
|
1133
|
-
if (s.render === "dot") this.
|
|
1134
|
-
else if (s.render === "line") this.segment(px, py,
|
|
1491
|
+
if (s.render === "dot") this.dot(copy, qx, qy, qx - px, qy - py, markR * fade, al * fade, op, d, rects);
|
|
1492
|
+
else if (s.render === "line" || s.render === "ribbon") this.segment(px, py, qx, qy, markR * fade, al * fade, op, d.pressure, rects);
|
|
1135
1493
|
else {
|
|
1136
1494
|
p.run += moved;
|
|
1137
1495
|
const gap = Math.max(markR * 2, size * (0.05 + s.interval));
|
|
@@ -1139,13 +1497,101 @@ var ParticleStroke = class extends MarkStroke {
|
|
|
1139
1497
|
p.run -= gap;
|
|
1140
1498
|
p.bead++;
|
|
1141
1499
|
const t = moved > 0 ? 1 - p.run / Math.max(moved, 1e-6) : 1;
|
|
1142
|
-
const bx = px + (
|
|
1143
|
-
const by = py + (
|
|
1144
|
-
this.
|
|
1500
|
+
const bx = px + (qx - px) * Math.min(1, Math.max(0, t));
|
|
1501
|
+
const by = py + (qy - py) * Math.min(1, Math.max(0, t));
|
|
1502
|
+
this.dot(copy, bx, by, qx - px, qy - py, markR * (0.7 + 0.6 * (p.bead % 3 / 2)) * fade, al * fade, op, d, rects);
|
|
1145
1503
|
}
|
|
1146
1504
|
}
|
|
1147
1505
|
}
|
|
1148
|
-
if (s.
|
|
1506
|
+
if (s.render === "ribbon" && !(d.index === 0 && s.kind !== "flow")) this.ribbon(copy, list, op, al, d, rects);
|
|
1507
|
+
if (s.kind === "flow" && list.length > 512) {
|
|
1508
|
+
list = list.filter((p) => p.alive);
|
|
1509
|
+
this.sets[copy] = list;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
/** A dot (or bead): round, or a textured stamp turned by the particle's motion (mx, my), at random or to the tip angle. */
|
|
1513
|
+
dot(copy, x, y, mx, my, r, alpha, opacity, d, rects) {
|
|
1514
|
+
const s = this.s;
|
|
1515
|
+
const textures = s.dotTextures;
|
|
1516
|
+
if (!textures.length) return this.mark(x, y, r, alpha, opacity, d.pressure, rects);
|
|
1517
|
+
const n = this.dots[copy]++;
|
|
1518
|
+
const pick = s.dotTextureOrder === "random" ? Math.min(textures.length - 1, Math.floor(rand01(this.seed, n, 31 + copy) * textures.length)) : n % textures.length;
|
|
1519
|
+
const rotation = s.dotRotation === "random" ? rand01(this.seed, n, 37 + copy) * Math.PI * 2 : s.dotRotation === "follow" ? mx || my ? Math.atan2(my, mx) : -d.direction : -this.brush.tip.angle * Math.PI / 180;
|
|
1520
|
+
this.stamp(x, y, r, textures[pick], rotation, this.seed + n * 2654435761 >>> 0, alpha, opacity, d.pressure, rects);
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Ribbon fill for one step: the band between each pair of joined particles, from where they
|
|
1524
|
+
* were drawn last step to where they are drawn now. Bands of one step, and of consecutive steps,
|
|
1525
|
+
* share edges exactly, so the fill has no seams; it is telescoped into the coverage
|
|
1526
|
+
* (`telescope`), so it does not build up at the joins.
|
|
1527
|
+
*/
|
|
1528
|
+
ribbon(copy, list, opacity, alpha, d, rects) {
|
|
1529
|
+
const s = this.s;
|
|
1530
|
+
if (!(s.ribbonFill > 0)) return;
|
|
1531
|
+
const live = (p) => !!p && p.alive && !Number.isNaN(p.px);
|
|
1532
|
+
let order;
|
|
1533
|
+
if (s.kind === "flow") order = list.filter(live);
|
|
1534
|
+
else order = this.orders[copy].map((i) => list[i]).filter(live);
|
|
1535
|
+
if (order.length < 2) return;
|
|
1536
|
+
const pairs = [];
|
|
1537
|
+
if (s.ribbonPairs === "first-last") pairs.push([order[0], order[order.length - 1]]);
|
|
1538
|
+
else for (let i = 0; i + 1 < order.length; i++) pairs.push([order[i], order[i + 1]]);
|
|
1539
|
+
const pts = this.pts;
|
|
1540
|
+
const ceil = Math.min(1, opacity * s.ribbonFill);
|
|
1541
|
+
for (const [a, b] of pairs) {
|
|
1542
|
+
pts.length = 0;
|
|
1543
|
+
pts.push(a.px, a.py, a.dx, a.dy, b.dx, b.dy, b.px, b.py);
|
|
1544
|
+
let x0 = Infinity;
|
|
1545
|
+
let y0 = Infinity;
|
|
1546
|
+
let x1 = -Infinity;
|
|
1547
|
+
let y1 = -Infinity;
|
|
1548
|
+
for (let i = 0; i < 8; i += 2) {
|
|
1549
|
+
x0 = Math.min(x0, pts[i]);
|
|
1550
|
+
x1 = Math.max(x1, pts[i]);
|
|
1551
|
+
y0 = Math.min(y0, pts[i + 1]);
|
|
1552
|
+
y1 = Math.max(y1, pts[i + 1]);
|
|
1553
|
+
}
|
|
1554
|
+
const rect = { x: Math.floor(x0), y: Math.floor(y0), width: Math.floor(x1) - Math.floor(x0) + 1, height: Math.floor(y1) - Math.floor(y0) + 1 };
|
|
1555
|
+
const area = this.scratch(rect.width * rect.height);
|
|
1556
|
+
addPolygonArea(pts, rect, area, Math.min(a.fade, b.fade) * alpha);
|
|
1557
|
+
if (this.telescope(rect, area)) this.lay(rect, area, ceil, d.pressure, (a.dx + b.dx) / 2, (a.dy + b.dy) / 2, rects);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Turns the area newly filled over `rect` into coverage to lay towards the fill's ceiling: the
|
|
1562
|
+
* share of what was left unfilled, so after any number of bands (in any grouping) a pixel's
|
|
1563
|
+
* coverage is the ceiling times the area filled there. False when nothing was added.
|
|
1564
|
+
*/
|
|
1565
|
+
telescope(rect, area) {
|
|
1566
|
+
let any = false;
|
|
1567
|
+
for (let yy = 0, k = 0; yy < rect.height; yy++) {
|
|
1568
|
+
const gy = rect.y + yy;
|
|
1569
|
+
const ty = Math.floor(gy / TILE);
|
|
1570
|
+
let t;
|
|
1571
|
+
let tx = NaN;
|
|
1572
|
+
for (let xx = 0; xx < rect.width; xx++, k++) {
|
|
1573
|
+
const add = area[k];
|
|
1574
|
+
if (!(add > 0)) continue;
|
|
1575
|
+
const gx = rect.x + xx;
|
|
1576
|
+
const ntx = Math.floor(gx / TILE);
|
|
1577
|
+
if (ntx !== tx || !t) {
|
|
1578
|
+
tx = ntx;
|
|
1579
|
+
const key = tileKey(tx, ty);
|
|
1580
|
+
t = this.filled.get(key);
|
|
1581
|
+
if (!t) {
|
|
1582
|
+
t = new Float32Array(TILE * TILE);
|
|
1583
|
+
this.filled.set(key, t);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
const ti = (gy - ty * TILE) * TILE + (gx - tx * TILE);
|
|
1587
|
+
const before = t[ti];
|
|
1588
|
+
const after = Math.min(1, before + add);
|
|
1589
|
+
t[ti] = after;
|
|
1590
|
+
area[k] = before < 1 ? (after - before) / (1 - before) : 0;
|
|
1591
|
+
any = true;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
return any;
|
|
1149
1595
|
}
|
|
1150
1596
|
};
|
|
1151
1597
|
var DEFAULT_BRISTLE = { count: 32, stiffness: 0.5, splay: 0.5, thickness: 0.06, load: 0.6, jitter: 0.4 };
|
|
@@ -1897,6 +2343,66 @@ var BUILTIN_BRUSHES = [
|
|
|
1897
2343
|
particle: { kind: "flow", render: "dot", count: 8, size: 0.06, spread: 0.7, speed: 0.9, life: 0.3 },
|
|
1898
2344
|
light: { amount: 1 }
|
|
1899
2345
|
}),
|
|
2346
|
+
builtin({
|
|
2347
|
+
// Silk of light: strands dragged behind the pen on slow waves, the bands between them filled.
|
|
2348
|
+
id: "light-ribbon",
|
|
2349
|
+
name: "Light ribbon",
|
|
2350
|
+
category: "light",
|
|
2351
|
+
engine: "particle",
|
|
2352
|
+
tip: { size: 80, hardness: 0.8, opacity: 0.9, flow: 1, smoothing: 0.35, pressureSize: false, pressureOpacity: false },
|
|
2353
|
+
particle: { kind: "string", render: "ribbon", count: 5, size: 0.015, spread: 1, stiffness: 0.05, damping: 0.3, ribbonFill: 0.45, waveAmplitude: 0.25, waveLength: 220, wavePhaseJitter: 0.25 },
|
|
2354
|
+
light: { amount: 0.8 }
|
|
2355
|
+
}),
|
|
2356
|
+
builtin({
|
|
2357
|
+
// Flames: short-lived bands thrown off the pen, rising and curling in the flow.
|
|
2358
|
+
id: "flame-ribbon",
|
|
2359
|
+
name: "Flame ribbon",
|
|
2360
|
+
category: "light",
|
|
2361
|
+
engine: "particle",
|
|
2362
|
+
tip: { size: 50, hardness: 0.6, opacity: 0.85, flow: 1, smoothing: 0.25, pressureSize: true, pressureOpacity: false },
|
|
2363
|
+
particle: { kind: "flow", render: "ribbon", count: 4, size: 0.012, spread: 0.5, speed: 0.6, gravity: -0.6, life: 0.2, ribbonFill: 0.5 },
|
|
2364
|
+
light: { amount: 1 }
|
|
2365
|
+
}),
|
|
2366
|
+
builtin({
|
|
2367
|
+
// A neon tube that lags and snaps back, with a slow shimmer.
|
|
2368
|
+
id: "neon-strand",
|
|
2369
|
+
name: "Neon strand",
|
|
2370
|
+
category: "light",
|
|
2371
|
+
engine: "particle",
|
|
2372
|
+
tip: { size: 40, hardness: 0.9, opacity: 1, flow: 1, smoothing: 0.4, pressureSize: false, pressureOpacity: false },
|
|
2373
|
+
particle: { kind: "elastic", render: "line", count: 3, size: 0.05, spread: 0.25, stiffness: 0.6, damping: 0.3, waveAmplitude: 0.06, waveLength: 120 },
|
|
2374
|
+
light: { amount: 1 }
|
|
2375
|
+
}),
|
|
2376
|
+
builtin({
|
|
2377
|
+
// Smoke lit from within: thin bands thrown into a slow, rising flow.
|
|
2378
|
+
id: "smoke-ribbon",
|
|
2379
|
+
name: "Smoke ribbon",
|
|
2380
|
+
category: "light",
|
|
2381
|
+
engine: "particle",
|
|
2382
|
+
tip: { size: 70, hardness: 0.3, opacity: 0.45, flow: 1, smoothing: 0.3, pressureSize: true, pressureOpacity: false },
|
|
2383
|
+
particle: { kind: "flow", render: "ribbon", count: 3, size: 5e-3, spread: 0.5, speed: 0.15, gravity: -0.15, life: 0.35, ribbonFill: 0.25, waveAmplitude: 0.1, waveLength: 140, wavePhaseJitter: 1 },
|
|
2384
|
+
light: { amount: 0.3 }
|
|
2385
|
+
}),
|
|
2386
|
+
builtin({
|
|
2387
|
+
// Out-of-focus lights: soft discs and rings spaced along springy paths.
|
|
2388
|
+
id: "bokeh-dots",
|
|
2389
|
+
name: "Bokeh dots",
|
|
2390
|
+
category: "light",
|
|
2391
|
+
engine: "particle",
|
|
2392
|
+
tip: { size: 80, hardness: 0.7, opacity: 0.6, flow: 1, smoothing: 0.3, pressureSize: true, pressureOpacity: false },
|
|
2393
|
+
particle: { kind: "spring", render: "sequence", count: 3, size: 0.3, spread: 1, stiffness: 0.15, damping: 0.4, interval: 1, dotTextures: ["dot", "dot", "ring"], dotTextureOrder: "random" },
|
|
2394
|
+
light: { amount: 0.9 }
|
|
2395
|
+
}),
|
|
2396
|
+
builtin({
|
|
2397
|
+
// Sparks: twinkles and stars thrown into the flow, spaced along it.
|
|
2398
|
+
id: "twinkle-sparks",
|
|
2399
|
+
name: "Twinkle sparks",
|
|
2400
|
+
category: "light",
|
|
2401
|
+
engine: "particle",
|
|
2402
|
+
tip: { size: 50, hardness: 0.8, opacity: 1, flow: 1, smoothing: 0.1, pressureSize: true, pressureOpacity: false },
|
|
2403
|
+
particle: { kind: "flow", render: "sequence", count: 3, size: 0.25, spread: 0.8, speed: 0.9, life: 0.35, interval: 0.6, dotTextures: ["sparkle", "sparkle", "star"], dotTextureOrder: "random", dotRotation: "random" },
|
|
2404
|
+
light: { amount: 1 }
|
|
2405
|
+
}),
|
|
1900
2406
|
builtin({
|
|
1901
2407
|
id: "glow-wash",
|
|
1902
2408
|
name: "Glow wash",
|
|
@@ -2067,11 +2573,15 @@ export {
|
|
|
2067
2573
|
DryStroke,
|
|
2068
2574
|
mixerTip,
|
|
2069
2575
|
toStrokePoint,
|
|
2070
|
-
|
|
2576
|
+
STICKER_ART_SIZE,
|
|
2577
|
+
defaultStickerRasterizer,
|
|
2578
|
+
StickerArtCache,
|
|
2579
|
+
sampleArt,
|
|
2580
|
+
MAX_STICKER_STAMPS,
|
|
2071
2581
|
DEFAULT_STICKER,
|
|
2072
2582
|
normalizeStickerSettings,
|
|
2073
|
-
stampCoverage,
|
|
2074
2583
|
stickerRect,
|
|
2584
|
+
stickerStamp,
|
|
2075
2585
|
drawSticker,
|
|
2076
2586
|
StickerSpray,
|
|
2077
2587
|
renderStickers,
|
|
@@ -2080,8 +2590,13 @@ export {
|
|
|
2080
2590
|
stickerAt,
|
|
2081
2591
|
PARTICLE_KINDS,
|
|
2082
2592
|
PARTICLE_RENDERS,
|
|
2593
|
+
RIBBON_PAIRS,
|
|
2594
|
+
DOT_TEXTURE_ORDERS,
|
|
2595
|
+
DOT_ROTATIONS,
|
|
2596
|
+
MAX_DOT_TEXTURES,
|
|
2083
2597
|
DEFAULT_PARTICLE,
|
|
2084
2598
|
normalizeParticle,
|
|
2599
|
+
addPolygonArea,
|
|
2085
2600
|
ParticleStroke,
|
|
2086
2601
|
DEFAULT_BRISTLE,
|
|
2087
2602
|
normalizeBristle,
|