@pixerate/editor 0.276.0 → 0.277.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/dist/animation-editor/index.cjs +59 -59
- package/dist/animation-editor/index.js +3 -3
- package/dist/brushes/index.cjs +69 -67
- package/dist/brushes/index.js +6 -4
- package/dist/brushes/patch.d.cts +4 -4
- package/dist/brushes/patch.d.ts +4 -4
- package/dist/brushes/profiles.d.cts +2 -0
- package/dist/brushes/profiles.d.ts +2 -0
- package/dist/brushes/schema.d.cts +1 -0
- package/dist/brushes/schema.d.ts +1 -0
- package/dist/brushes/types.d.cts +10 -0
- package/dist/brushes/types.d.ts +10 -0
- package/dist/{brushstrokeRender-I6MYFCOU.cjs → brushstrokeRender-7HOMKZNO.cjs} +15 -15
- package/dist/{brushstrokeRender-FK3ASUUJ.js → brushstrokeRender-EIK3625T.js} +3 -3
- package/dist/canvas/index.cjs +13 -13
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-U26LUXCJ.cjs → chunk-4NZTK7LM.cjs} +35 -35
- package/dist/{chunk-CVBTJVRN.js → chunk-DWPFGWW5.js} +1 -1
- package/dist/{chunk-XMSH66VU.js → chunk-FNIWHSLI.js} +1 -1
- package/dist/{chunk-QNNZA3V5.cjs → chunk-H3EME3S5.cjs} +2 -2
- package/dist/{chunk-4EM5NGCL.js → chunk-HHX7ST6Z.js} +1 -1
- package/dist/{chunk-Q33V5PEF.cjs → chunk-HRUK5TFW.cjs} +73 -12
- package/dist/{chunk-5HR5CSLC.cjs → chunk-JVFGK3DK.cjs} +69 -58
- package/dist/{chunk-JNMNM3SL.js → chunk-LZE2QZCT.js} +1 -1
- package/dist/{chunk-OS6OG7DN.js → chunk-N4MCWQZ7.js} +1 -1
- package/dist/{chunk-IJ6NSWKM.js → chunk-NJLPW4MM.js} +17 -6
- package/dist/{chunk-OY24XRYF.cjs → chunk-PEQWARCP.cjs} +12 -12
- package/dist/{chunk-XVPQNX7O.js → chunk-PPDUCZZV.js} +66 -5
- package/dist/{chunk-X7XBEX3O.cjs → chunk-QBZEC2JJ.cjs} +86 -86
- package/dist/{chunk-GHYJAMMS.js → chunk-ROURHJYT.js} +1 -1
- package/dist/{chunk-ZIE6EJGR.cjs → chunk-VNF7A2JA.cjs} +23 -23
- package/dist/{chunk-ISCAJBPQ.cjs → chunk-WFSYZO5Z.cjs} +1 -1
- package/dist/image-editor/index.cjs +788 -788
- package/dist/image-editor/index.js +4 -4
- package/dist/paint-editor/brush-model.d.cts +15 -1
- package/dist/paint-editor/brush-model.d.ts +15 -1
- package/dist/paint-editor/controller.d.cts +50 -1
- package/dist/paint-editor/controller.d.ts +50 -1
- package/dist/paint-editor/document.d.cts +8 -1
- package/dist/paint-editor/document.d.ts +8 -1
- package/dist/paint-editor/ferro-tools.d.cts +10 -0
- package/dist/paint-editor/ferro-tools.d.ts +10 -0
- package/dist/paint-editor/ferro.d.cts +134 -0
- package/dist/paint-editor/ferro.d.ts +134 -0
- package/dist/paint-editor/index.cjs +1878 -831
- package/dist/paint-editor/index.d.cts +2 -0
- package/dist/paint-editor/index.d.ts +2 -0
- package/dist/paint-editor/index.js +1184 -137
- package/dist/paint-editor/stroke.d.cts +14 -0
- package/dist/paint-editor/stroke.d.ts +14 -0
- package/dist/paint-editor/thick.d.cts +15 -2
- package/dist/paint-editor/thick.d.ts +15 -2
- package/dist/paint-editor/ui.d.cts +2 -0
- package/dist/paint-editor/ui.d.ts +2 -0
- package/dist/vector/index.cjs +998 -998
- package/dist/vector/index.js +4 -4
- package/package.json +1 -1
- package/skills/pixerate-brushes/SKILL.md +1 -0
- package/skills/pixerate-paint-editor/SKILL.md +11 -29
- package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
- package/skills/pixerate-paint-editor/references/ferrofluid.md +36 -0
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
describeStickerRef
|
|
3
3
|
} from "./chunk-VE6UKYQ2.js";
|
|
4
4
|
import {
|
|
5
|
+
BRUSH_FACET_SCHEMAS,
|
|
5
6
|
BRUSH_HOST_PROFILES,
|
|
6
7
|
BRUSH_ID,
|
|
7
8
|
BRUSH_JSON_SCHEMA,
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
resolveEngine,
|
|
28
29
|
summarizeBrushSource,
|
|
29
30
|
withKeptIssues
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-PPDUCZZV.js";
|
|
31
32
|
import {
|
|
32
33
|
base64ToBytes
|
|
33
34
|
} from "./chunk-OTTT5N3K.js";
|
|
@@ -42,9 +43,16 @@ var FACETS = BRUSH_JSON_SCHEMA.properties;
|
|
|
42
43
|
var BRUSH_PATCH_PROPERTIES = (() => {
|
|
43
44
|
const { id: _id, name: _name, ...rest } = FACETS;
|
|
44
45
|
const { required: _r, ...tip } = rest.tip;
|
|
45
|
-
|
|
46
|
+
const ferro = {
|
|
47
|
+
description: "Liquid ferrofluid paint: 0..1 (amount), or { amount, wavelength, threshold }. Rises into glossy Rosensweig spikes near magnets until cured.",
|
|
48
|
+
anyOf: [
|
|
49
|
+
{ type: "number", minimum: 0, maximum: 1, description: "How much liquid ferrofluid the brush lays per dab (0..1)." },
|
|
50
|
+
BRUSH_FACET_SCHEMAS.ferro
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
return { ...rest, tip: { ...tip, description: `${tip.description} In a patch, shape may be left out to keep it.` }, ferro };
|
|
46
54
|
})();
|
|
47
|
-
var UNSETTABLE_BRUSH_FACETS = ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "livePatterns", "colour"];
|
|
55
|
+
var UNSETTABLE_BRUSH_FACETS = ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "ferro", "motion", "livePatterns", "colour"];
|
|
48
56
|
var BRUSH_UNSET_PROPERTY = {
|
|
49
57
|
type: "array",
|
|
50
58
|
minItems: 1,
|
|
@@ -54,9 +62,11 @@ var BRUSH_UNSET_PROPERTY = {
|
|
|
54
62
|
};
|
|
55
63
|
function mergeBrushPatch(base, patch, path = "") {
|
|
56
64
|
const out = { ...base };
|
|
57
|
-
for (const [k,
|
|
58
|
-
if (
|
|
59
|
-
const
|
|
65
|
+
for (const [k, rawV] of Object.entries(patch)) {
|
|
66
|
+
if (rawV === void 0) continue;
|
|
67
|
+
const v = path === "" && k === "ferro" && typeof rawV === "number" ? { amount: rawV } : rawV;
|
|
68
|
+
const rawOld = out[k];
|
|
69
|
+
const old = path === "" && k === "ferro" && typeof rawOld === "number" ? { amount: rawOld } : rawOld;
|
|
60
70
|
const replaceMarks = path === "" && k === "marks" && isObject(old) && isObject(v) && old.kind !== v.kind;
|
|
61
71
|
out[k] = isObject(v) && isObject(old) && !replaceMarks ? mergeBrushPatch(old, v, `${path}${k}.`) : copy(v);
|
|
62
72
|
}
|
|
@@ -728,6 +738,7 @@ function summary(entry, profile, withCheck) {
|
|
|
728
738
|
marks: b.marks?.kind ?? "continuous",
|
|
729
739
|
surface: b.behaviour?.surface ?? "dry",
|
|
730
740
|
...b.light?.amount ? { light: b.light.amount } : {},
|
|
741
|
+
...b.ferro?.amount ? { ferro: b.ferro.amount } : {},
|
|
731
742
|
...b.tags?.length ? { tags: b.tags } : {},
|
|
732
743
|
...check ? { renderable: check.ok } : {}
|
|
733
744
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
var
|
|
11
|
+
var _chunkHRUK5TFWcjs = require('./chunk-HRUK5TFW.cjs');
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
@@ -2796,7 +2796,7 @@ function effectDabField(img, rect, cov, centre, mixed, field, steps = 0, dab = n
|
|
|
2796
2796
|
var samplers = /* @__PURE__ */ new WeakMap();
|
|
2797
2797
|
function stampSampler(ref, art, hardness = 1) {
|
|
2798
2798
|
const sampler = baseSampler(ref, art);
|
|
2799
|
-
if (!sampler || hardness >= 1 || !
|
|
2799
|
+
if (!sampler || hardness >= 1 || !_chunkHRUK5TFWcjs.isBrushSourceRef.call(void 0, ref)) return sampler;
|
|
2800
2800
|
const h = Math.max(0, hardness);
|
|
2801
2801
|
return (u, v2, seed) => {
|
|
2802
2802
|
const r = Math.hypot(u, v2) / Math.SQRT2;
|
|
@@ -2871,7 +2871,7 @@ function rasterizeStampDab(d, sample, seed, rect, out) {
|
|
|
2871
2871
|
}
|
|
2872
2872
|
function brushStickerRefs(brush) {
|
|
2873
2873
|
const refs = brush.engine === "sticker" ? [...brush.sticker.stamps] : brush.engine === "particle" ? [...brush.particle.dotTextures] : brush.engine === "dab" && brush.stamp ? [brush.stamp] : [];
|
|
2874
|
-
return [...new Set(refs.filter((r) => r.includes(":") && !r.startsWith("editor:") && !
|
|
2874
|
+
return [...new Set(refs.filter((r) => r.includes(":") && !r.startsWith("editor:") && !_chunkHRUK5TFWcjs.isBrushSourceRef.call(void 0, r)))];
|
|
2875
2875
|
}
|
|
2876
2876
|
function sprayDroplets(d, spray, seed) {
|
|
2877
2877
|
const count = 1 + Math.round(spray.density * 31);
|
|
@@ -4798,11 +4798,11 @@ function renderBackdropBrushstroke(input, brush, options) {
|
|
|
4798
4798
|
|
|
4799
4799
|
// src/vector/brushstroke.ts
|
|
4800
4800
|
var VECTOR_BRUSHSTROKE_SCALE = 2;
|
|
4801
|
-
var PRESETS =
|
|
4801
|
+
var PRESETS = _chunkHRUK5TFWcjs.listPresets.call(void 0, _chunkHRUK5TFWcjs.VECTOR_PROFILE).map((spec) => _chunkHRUK5TFWcjs.BUILTIN_BRUSHES.find((b) => b.id === spec.id));
|
|
4802
4802
|
function admitted(spec) {
|
|
4803
4803
|
try {
|
|
4804
|
-
const brush =
|
|
4805
|
-
return
|
|
4804
|
+
const brush = _chunkHRUK5TFWcjs.normalizeBrushSpec.call(void 0, spec);
|
|
4805
|
+
return _chunkHRUK5TFWcjs.checkBrush.call(void 0, brush, _chunkHRUK5TFWcjs.VECTOR_PROFILE).ok && isVectorBrushstrokeEngine(_chunkHRUK5TFWcjs.resolveBrush.call(void 0, brush)) ? brush : null;
|
|
4806
4806
|
} catch (e6) {
|
|
4807
4807
|
return null;
|
|
4808
4808
|
}
|
|
@@ -4814,7 +4814,7 @@ function vectorBrushstrokeBrushes(user = []) {
|
|
|
4814
4814
|
const out = [...PRESETS];
|
|
4815
4815
|
for (const raw of user) {
|
|
4816
4816
|
const brush = admitted(raw);
|
|
4817
|
-
if (brush && !out.some((b) => b.id === brush.id)) out.push(
|
|
4817
|
+
if (brush && !out.some((b) => b.id === brush.id)) out.push(_chunkHRUK5TFWcjs.resolveBrush.call(void 0, brush));
|
|
4818
4818
|
}
|
|
4819
4819
|
return out;
|
|
4820
4820
|
}
|
|
@@ -4824,7 +4824,7 @@ function vectorBrushstrokeBrush(id) {
|
|
|
4824
4824
|
function vectorBrushstrokeBrushOf(brush) {
|
|
4825
4825
|
if (brush.spec) {
|
|
4826
4826
|
const saved = admitted(brush.spec);
|
|
4827
|
-
return saved && saved.id === brush.brushId ?
|
|
4827
|
+
return saved && saved.id === brush.brushId ? _chunkHRUK5TFWcjs.resolveBrush.call(void 0, saved) : null;
|
|
4828
4828
|
}
|
|
4829
4829
|
return vectorBrushstrokeBrush(brush.brushId);
|
|
4830
4830
|
}
|
|
@@ -4832,14 +4832,14 @@ function vectorBrushstrokeBrushError(id, user = []) {
|
|
|
4832
4832
|
if (vectorBrushstrokeBrush(id)) return null;
|
|
4833
4833
|
const saved = user.find((b) => b.id === id);
|
|
4834
4834
|
const ids = vectorBrushstrokeBrushes(user).map((b) => b.id);
|
|
4835
|
-
const reasons = (b) =>
|
|
4835
|
+
const reasons = (b) => _chunkHRUK5TFWcjs.checkBrush.call(void 0, b, _chunkHRUK5TFWcjs.VECTOR_PROFILE).issues.filter((i) => i.severity === "error").map((i) => `${i.reason} ${i.fix}`).join(" ") || "its engine does not paint brushstrokes.";
|
|
4836
4836
|
if (saved) return admitted(saved) ? null : `Brush "${id}" (${saved.name}) can't paint a brushstroke: ${reasons(saved)} Use one of: ${ids.join(", ")}.`;
|
|
4837
|
-
const preset2 =
|
|
4837
|
+
const preset2 = _chunkHRUK5TFWcjs.BRUSH_PRESETS.find((b) => b.id === id);
|
|
4838
4838
|
if (preset2) return `Brush "${id}" (${preset2.name}) can't paint a brushstroke: ${reasons(preset2)} Use one of: ${ids.join(", ")}.`;
|
|
4839
|
-
return `Unknown brush "${id}". ${
|
|
4839
|
+
return `Unknown brush "${id}". ${_chunkHRUK5TFWcjs.closest.call(void 0, id, ids)}Use one of: ${ids.join(", ")} (vector.listBrushes lists them).`;
|
|
4840
4840
|
}
|
|
4841
4841
|
function vectorBrushstrokeDefaultColour(id, user = [], scheme = "light") {
|
|
4842
|
-
const brush = _nullishCoalesce(
|
|
4842
|
+
const brush = _nullishCoalesce(_chunkHRUK5TFWcjs.BRUSH_PRESETS.find((b) => b.id === id), () => ( user.find((b) => b.id === id)));
|
|
4843
4843
|
return brush ? _chunkJBZRVLXVcjs.brushColourHex.call(void 0, _optionalChain([brush, 'access', _212 => _212.colour, 'optionalAccess', _213 => _213.default]), scheme) : null;
|
|
4844
4844
|
}
|
|
4845
4845
|
function sanitizeVectorBrushstroke(value, user = []) {
|
|
@@ -932,6 +932,29 @@ var BRUSH_PRESETS = [
|
|
|
932
932
|
tip: { shape: "round", size: 6, hardness: 0.9, spacing: 0.06 },
|
|
933
933
|
dynamics: { smoothing: 0.35, pressureSmoothing: 0.25, minSize: 0.25 },
|
|
934
934
|
mutation: {}
|
|
935
|
+
},
|
|
936
|
+
// --- Ferrofluid paint: liquid magnetic paint that rises into lit Rosensweig spikes near magnets ---
|
|
937
|
+
{
|
|
938
|
+
id: "ferrofluid",
|
|
939
|
+
name: "Ferrofluid",
|
|
940
|
+
kind: "mark",
|
|
941
|
+
medium: "paint",
|
|
942
|
+
variety: "ferrofluid",
|
|
943
|
+
tip: { shape: "round", size: 28, hardness: 0.75, flow: 0.9 },
|
|
944
|
+
dynamics: { smoothing: 0.35 },
|
|
945
|
+
ferro: { amount: 0.85 },
|
|
946
|
+
colour: { default: "#111827", palette: ["#111827", "#1e293b", "#312e81", "#0f3d3e", "#3b0764", "#451a03"] }
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
id: "ferro-ink",
|
|
950
|
+
name: "Ferro ink",
|
|
951
|
+
kind: "mark",
|
|
952
|
+
medium: "ink",
|
|
953
|
+
variety: "ferrofluid",
|
|
954
|
+
tip: { shape: "round", size: 12, spacing: 0.08 },
|
|
955
|
+
dynamics: { smoothing: 0.45, minSize: 0.25, velocitySize: -0.25 },
|
|
956
|
+
ferro: { amount: 0.7, wavelength: 12, threshold: 0.15 },
|
|
957
|
+
colour: { default: "#0f172a" }
|
|
935
958
|
}
|
|
936
959
|
];
|
|
937
960
|
|
|
@@ -1101,6 +1124,18 @@ function normalizeMutation(raw = {}, base = DEFAULT_MUTATION) {
|
|
|
1101
1124
|
fade: range(raw.fade, 0.5, 10, base.fade)
|
|
1102
1125
|
};
|
|
1103
1126
|
}
|
|
1127
|
+
var DEFAULT_FERRO = { amount: 0, wavelength: 16, threshold: 0.12 };
|
|
1128
|
+
function normalizeFerro(raw = {}, base = DEFAULT_FERRO) {
|
|
1129
|
+
if (typeof raw === "number") {
|
|
1130
|
+
return { amount: unit2(raw, base.amount), wavelength: base.wavelength, threshold: base.threshold };
|
|
1131
|
+
}
|
|
1132
|
+
const r = raw ?? {};
|
|
1133
|
+
return {
|
|
1134
|
+
amount: unit2(r.amount, base.amount),
|
|
1135
|
+
wavelength: range(r.wavelength, 4, 64, base.wavelength),
|
|
1136
|
+
threshold: range(r.threshold, 0.01, 0.95, base.threshold)
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1104
1139
|
function normalizeImpasto(raw = {}, base = DEFAULT_IMPASTO) {
|
|
1105
1140
|
return {
|
|
1106
1141
|
tool: IMPASTO_TOOLS.includes(raw.tool) ? raw.tool : base.tool,
|
|
@@ -1329,6 +1364,7 @@ function normalizePaintBrush(raw = {}, base) {
|
|
|
1329
1364
|
...withIridescence(readIridescence(raw.iridescence, b.iridescence ?? null)),
|
|
1330
1365
|
...ghost ? { ghost } : {},
|
|
1331
1366
|
...mutation ? { mutation } : {},
|
|
1367
|
+
ferro: normalizeFerro(raw.ferro, b.ferro ?? DEFAULT_FERRO),
|
|
1332
1368
|
stamp: readStamp(raw.stamp, b.stamp ?? null),
|
|
1333
1369
|
spray: readSpray(raw.spray, b.spray ?? null),
|
|
1334
1370
|
...withTipGrain(readTipGrain(raw.tipGrain, b.tipGrain ?? null)),
|
|
@@ -1360,6 +1396,7 @@ var BASE_BRUSH = {
|
|
|
1360
1396
|
light: { ...DEFAULT_LIGHT },
|
|
1361
1397
|
fluorescence: { ...DEFAULT_FLUORESCENCE },
|
|
1362
1398
|
thermochromic: { ...DEFAULT_THERMOCHROMIC },
|
|
1399
|
+
ferro: { ...DEFAULT_FERRO },
|
|
1363
1400
|
stamp: null,
|
|
1364
1401
|
spray: null,
|
|
1365
1402
|
paint: null
|
|
@@ -1419,6 +1456,7 @@ var BRUSH_FIELDS = [
|
|
|
1419
1456
|
...each("fluorescence", "fluorescence", ["amount", "invisible"]),
|
|
1420
1457
|
...each("thermochromic", "thermochromic", ["hotColor", "threshold"]),
|
|
1421
1458
|
...each("iridescence", "iridescence", ["thickness", "pressureToThickness", "ior"], FILM),
|
|
1459
|
+
...each("ferro", "ferro", ["amount", "wavelength", "threshold"]),
|
|
1422
1460
|
// Paint exchange: the dab, wet and impasto engines' behaviour, or the Mixer Brush's settings.
|
|
1423
1461
|
f("behaviour.exchange.pickup", "behavior.pickup", EXCHANGE),
|
|
1424
1462
|
f("behaviour.exchange.mix", "behavior.mix", EXCHANGE),
|
|
@@ -1627,7 +1665,7 @@ function skeleton(b) {
|
|
|
1627
1665
|
}
|
|
1628
1666
|
}
|
|
1629
1667
|
}
|
|
1630
|
-
var ORDER = ["id", "name", "description", "tags", "kind", "medium", "variety", "tool", "tip", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "livePatterns", "colour"];
|
|
1668
|
+
var ORDER = ["id", "name", "description", "tags", "kind", "medium", "variety", "tool", "tip", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "ferro", "motion", "livePatterns", "colour"];
|
|
1631
1669
|
function migrateBrushV1(raw) {
|
|
1632
1670
|
const b = normalizePaintBrush(raw);
|
|
1633
1671
|
const brush = skeleton(b);
|
|
@@ -1866,6 +1904,11 @@ var MUTATION = object("Mutation paint: you paint something and pause; once an ag
|
|
|
1866
1904
|
patience: num(5, 300, "Seconds to wait for an agent before giving up and keeping the paint as ordinary paint (default 45)."),
|
|
1867
1905
|
fade: num(0.5, 10, "Seconds the paint takes to sink away once claimed by read_mutation (default 2).")
|
|
1868
1906
|
});
|
|
1907
|
+
var FERRO = object("Liquid ferrofluid paint: rises into glossy Rosensweig spikes near magnets until cured.", {
|
|
1908
|
+
amount: unit3("How much liquid ferrofluid the brush lays per dab (default 0: none)."),
|
|
1909
|
+
wavelength: num(4, 64, "Capillary wavelength (spike-to-spike spacing) in pixels (default 16)."),
|
|
1910
|
+
threshold: num(0.01, 0.95, "Critical magnetic field |B| threshold where spikes begin to rise (default 0.12).")
|
|
1911
|
+
});
|
|
1869
1912
|
var MOTION = object("Animation after painting.", {
|
|
1870
1913
|
live: bool("Effect paint keeps playing on stroke layers (default false)."),
|
|
1871
1914
|
fps: int(1, 60, "Suggested playback rate in frames per second."),
|
|
@@ -1947,6 +1990,7 @@ var BRUSH_JSON_SCHEMA = object(
|
|
|
1947
1990
|
iridescence: IRIDESCENCE,
|
|
1948
1991
|
ghost: GHOST,
|
|
1949
1992
|
mutation: MUTATION,
|
|
1993
|
+
ferro: FERRO,
|
|
1950
1994
|
motion: MOTION,
|
|
1951
1995
|
livePatterns: LIVE_PATTERNS,
|
|
1952
1996
|
colour: COLOUR
|
|
@@ -1965,6 +2009,7 @@ var BRUSH_FACET_SCHEMAS = {
|
|
|
1965
2009
|
iridescence: IRIDESCENCE,
|
|
1966
2010
|
ghost: GHOST,
|
|
1967
2011
|
mutation: MUTATION,
|
|
2012
|
+
ferro: FERRO,
|
|
1968
2013
|
motion: MOTION,
|
|
1969
2014
|
livePatterns: LIVE_PATTERNS,
|
|
1970
2015
|
colour: COLOUR,
|
|
@@ -2210,14 +2255,14 @@ function normalizeBrushSpec(raw, problems = []) {
|
|
|
2210
2255
|
else if (raw.kind === void 0) kind = tool ? "tool" : "mark";
|
|
2211
2256
|
else throw new Error(`Brush "${raw.id}" kind ${JSON.stringify(raw.kind)} is not valid: use mark (lays a material) or tool (works on paint already down).`);
|
|
2212
2257
|
if (kind === "tool" && !tool) throw new Error(`Brush "${raw.id}" is a tool and needs tool: one of ${BRUSH_TOOLS.join(", ")}.`);
|
|
2213
|
-
const { colour: rawColour, marks: rawMarks, kind: _k, id: _i, name: _n, tool: _t, ...rest } = raw;
|
|
2258
|
+
const { colour: rawColour, marks: rawMarks, ferro: rawFerro, kind: _k, id: _i, name: _n, tool: _t, ...rest } = raw;
|
|
2214
2259
|
if (typeof rest.fluorescence === "number" && Number.isFinite(rest.fluorescence)) rest.fluorescence = { amount: rest.fluorescence };
|
|
2215
2260
|
const out = readObject(rest, BRUSH_JSON_SCHEMA, "brush", problems);
|
|
2216
2261
|
if (out.thermochromic?.hotColor) out.thermochromic.hotColor = out.thermochromic.hotColor.toLowerCase();
|
|
2217
2262
|
const brush = { id: raw.id, name, ...out, kind, ...tool ? { tool } : {}, tip: out.tip ?? { shape: "round" } };
|
|
2218
2263
|
const ordered = () => {
|
|
2219
|
-
const { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, motion, livePatterns, colour } = brush;
|
|
2220
|
-
const all = { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, motion, livePatterns, colour };
|
|
2264
|
+
const { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, ferro, motion, livePatterns, colour } = brush;
|
|
2265
|
+
const all = { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, ferro, motion, livePatterns, colour };
|
|
2221
2266
|
return Object.fromEntries(Object.entries(all).filter(([, v]) => v !== void 0));
|
|
2222
2267
|
};
|
|
2223
2268
|
if (!out.tip) problems.push("tip is missing; a round tip is used.");
|
|
@@ -2231,6 +2276,11 @@ function normalizeBrushSpec(raw, problems = []) {
|
|
|
2231
2276
|
if (!schema) problems.push(`marks.kind ${JSON.stringify(k)} is not valid; use continuous, scatter, particles or objects. marks dropped.`);
|
|
2232
2277
|
else brush.marks = readObject(rawMarks, schema, "brush.marks", problems);
|
|
2233
2278
|
}
|
|
2279
|
+
if (rawFerro !== void 0) {
|
|
2280
|
+
const ferroObj = typeof rawFerro === "number" ? { amount: rawFerro } : rawFerro;
|
|
2281
|
+
const f2 = readObject(ferroObj, BRUSH_FACET_SCHEMAS.ferro, "brush.ferro", problems);
|
|
2282
|
+
if (f2 && Object.keys(f2).length) brush.ferro = f2;
|
|
2283
|
+
}
|
|
2234
2284
|
if (rawColour !== void 0) {
|
|
2235
2285
|
const c = readObject(rawColour, { ...BRUSH_FACET_SCHEMAS.colour, properties: { own: BRUSH_FACET_SCHEMAS.colour.properties.own, default: {}, palette: {} } }, "brush.colour", problems);
|
|
2236
2286
|
if (c) {
|
|
@@ -2265,6 +2315,10 @@ function normalizeBrushSpec(raw, problems = []) {
|
|
|
2265
2315
|
drop("marks", "tools lay no marks");
|
|
2266
2316
|
delete brush.marks;
|
|
2267
2317
|
}
|
|
2318
|
+
if (brush.ferro) {
|
|
2319
|
+
drop("ferro", "tools lay no ferrofluid");
|
|
2320
|
+
delete brush.ferro;
|
|
2321
|
+
}
|
|
2268
2322
|
if (brush.iridescence) {
|
|
2269
2323
|
drop("iridescence", "tools lay no film");
|
|
2270
2324
|
delete brush.iridescence;
|
|
@@ -2394,7 +2448,8 @@ var PAINT_PROFILE = {
|
|
|
2394
2448
|
tipGrain: "full",
|
|
2395
2449
|
dabCount: "full",
|
|
2396
2450
|
livePatterns: "full",
|
|
2397
|
-
iridescence: "full"
|
|
2451
|
+
iridescence: "full",
|
|
2452
|
+
ferro: "full"
|
|
2398
2453
|
};
|
|
2399
2454
|
var IMAGE_PROFILE = {
|
|
2400
2455
|
id: "image",
|
|
@@ -2485,6 +2540,7 @@ function checkBrush(brush, profile) {
|
|
|
2485
2540
|
else if (nonZero(ex?.pickup) || !!ex?.dirty) add("mutation", "error", "Mutation brushes must lay their own colour without picking up paint from beneath.", "Remove behaviour.exchange (or set pickup to 0 and dirty to false), or remove mutation.");
|
|
2486
2541
|
else if (surface === "wet" && nonZero(brush.light?.amount)) add("mutation", "error", "Wet mutation brushes use the wet light channel as their fade tracer.", "Remove light (or set light.amount to 0), or remove mutation.");
|
|
2487
2542
|
}
|
|
2543
|
+
feature(profile.ferro ?? "none", nonZero(brush.ferro?.amount), "ferro", "ferrofluid spikes and magnets", "Remove ferro (or set ferro.amount to 0).");
|
|
2488
2544
|
const m = brush.motion;
|
|
2489
2545
|
feature(profile.motion, !!m && (!!m.live || nonZero(m.waveSpeed) || m.fps !== void 0), "motion", "animation", "Remove motion.");
|
|
2490
2546
|
const d = brush.dynamics ?? {};
|
|
@@ -2574,6 +2630,9 @@ function degrade(brush, profile, options = {}) {
|
|
|
2574
2630
|
case "iridescence":
|
|
2575
2631
|
delete b.iridescence;
|
|
2576
2632
|
break;
|
|
2633
|
+
case "ferro":
|
|
2634
|
+
delete b.ferro;
|
|
2635
|
+
break;
|
|
2577
2636
|
case "motion":
|
|
2578
2637
|
delete b.motion;
|
|
2579
2638
|
break;
|
|
@@ -2961,6 +3020,8 @@ export {
|
|
|
2961
3020
|
normalizeGhost,
|
|
2962
3021
|
DEFAULT_MUTATION,
|
|
2963
3022
|
normalizeMutation,
|
|
3023
|
+
DEFAULT_FERRO,
|
|
3024
|
+
normalizeFerro,
|
|
2964
3025
|
normalizeImpasto,
|
|
2965
3026
|
MULTI_COLOR_MODES,
|
|
2966
3027
|
DEFAULT_MULTI_COLOR,
|