@pixerate/editor 0.267.0 → 0.268.1
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 +62 -62
- package/dist/animation-editor/index.js +6 -6
- package/dist/brushes/index.cjs +198 -72
- package/dist/brushes/index.d.cts +4 -3
- package/dist/brushes/index.d.ts +4 -3
- package/dist/brushes/index.js +136 -10
- package/dist/brushes/library.d.cts +27 -1
- package/dist/brushes/library.d.ts +27 -1
- package/dist/brushes/profiles.d.cts +4 -0
- package/dist/brushes/profiles.d.ts +4 -0
- package/dist/brushes/schema.d.cts +4 -0
- package/dist/brushes/schema.d.ts +4 -0
- package/dist/brushes/sources.d.cts +114 -0
- package/dist/brushes/sources.d.ts +114 -0
- package/dist/brushes/types.d.cts +37 -5
- package/dist/brushes/types.d.ts +37 -5
- package/dist/brushes/ui.d.cts +40 -4
- package/dist/brushes/ui.d.ts +40 -4
- package/dist/{brushstrokeRender-NLFPEBGI.js → brushstrokeRender-A727TTNQ.js} +6 -6
- package/dist/{brushstrokeRender-L7JYRR2R.cjs → brushstrokeRender-WR2N5HHY.cjs} +22 -22
- package/dist/canvas/index.cjs +13 -13
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-J2TNIZ55.cjs → chunk-2WOGXEDX.cjs} +1 -1
- package/dist/{chunk-JGWVZPT6.js → chunk-3XDSNIGF.js} +72 -5
- package/dist/{chunk-EXB2EGBE.cjs → chunk-62MFUHSI.cjs} +16 -16
- package/dist/{chunk-GCVFEPJG.js → chunk-ACTHSQOI.js} +182 -0
- package/dist/{chunk-EF7LZAXQ.js → chunk-BAZMCFVL.js} +1 -1
- package/dist/{chunk-HIO6E7LC.cjs → chunk-BPJSI4NT.cjs} +2 -2
- package/dist/{chunk-XVOP3CGQ.cjs → chunk-EU2ZZA6L.cjs} +393 -62
- package/dist/{chunk-YIC6MAJ2.js → chunk-HFY5LG5J.js} +1 -1
- package/dist/{chunk-7UKFXXWL.js → chunk-HVO6XCI4.js} +1 -1
- package/dist/{chunk-LHPEG5AU.cjs → chunk-J4GXP53R.cjs} +140 -91
- package/dist/{chunk-OKHKA43L.cjs → chunk-JBB3GDBB.cjs} +330 -216
- package/dist/{chunk-AGNUD5BI.js → chunk-KZI3YWKW.js} +351 -20
- package/dist/{chunk-FGJHJIMN.js → chunk-LDOD7VCT.js} +59 -10
- package/dist/{chunk-EYSHTYLG.js → chunk-LETF75CA.js} +1 -1
- package/dist/{chunk-FQPUF2W2.cjs → chunk-NIBFKXMD.cjs} +217 -35
- package/dist/{chunk-G7NHE2VD.cjs → chunk-NPWWSCWT.cjs} +35 -35
- package/dist/{chunk-OSEXT7JL.js → chunk-QNY7BDFP.js} +1 -1
- package/dist/{chunk-PXDIAYB6.cjs → chunk-STLTOISW.cjs} +34 -214
- package/dist/{chunk-4IDUYB45.js → chunk-UVTO3D4E.js} +130 -16
- package/dist/{chunk-TXFWZ5ZJ.js → chunk-W6JDUXP3.js} +4 -184
- package/dist/{chunk-LA5Y4ETA.cjs → chunk-YOVXQHA3.cjs} +23 -23
- package/dist/{chunk-DT4YQUGT.cjs → chunk-YXP6X3SY.cjs} +101 -34
- package/dist/image-editor/adjustment-layers.d.cts +8 -0
- package/dist/image-editor/adjustment-layers.d.ts +8 -0
- package/dist/image-editor/index.cjs +857 -853
- package/dist/image-editor/index.js +30 -26
- package/dist/paint/index.cjs +5 -5
- package/dist/paint/index.js +11 -11
- package/dist/paint-editor/brush-library-tools.d.cts +12 -2
- package/dist/paint-editor/brush-library-tools.d.ts +12 -2
- package/dist/paint-editor/brush-model.d.cts +29 -1
- package/dist/paint-editor/brush-model.d.ts +29 -1
- package/dist/paint-editor/brush-source-art.d.cts +32 -0
- package/dist/paint-editor/brush-source-art.d.ts +32 -0
- package/dist/paint-editor/controller.d.cts +6 -1
- package/dist/paint-editor/controller.d.ts +6 -1
- package/dist/paint-editor/dab-stroke.d.cts +2 -0
- package/dist/paint-editor/dab-stroke.d.ts +2 -0
- package/dist/paint-editor/engine.d.cts +11 -2
- package/dist/paint-editor/engine.d.ts +11 -2
- package/dist/paint-editor/index.cjs +576 -481
- package/dist/paint-editor/index.d.cts +3 -1
- package/dist/paint-editor/index.d.ts +3 -1
- package/dist/paint-editor/index.js +127 -32
- package/dist/paint-editor/stamp-tip.d.cts +8 -5
- package/dist/paint-editor/stamp-tip.d.ts +8 -5
- package/dist/paint-editor/thumbnails.d.cts +6 -0
- package/dist/paint-editor/thumbnails.d.ts +6 -0
- package/dist/paint-editor/tip-grain.d.cts +34 -0
- package/dist/paint-editor/tip-grain.d.ts +34 -0
- package/dist/raster/index.cjs +2 -2
- package/dist/raster/index.js +9 -9
- package/dist/vector/index.cjs +1002 -1002
- package/dist/vector/index.js +7 -7
- package/package.json +110 -1
- package/skills/pixerate-brushes/SKILL.md +35 -8
- package/skills/pixerate-brushes-design/SKILL.md +8 -1
- package/skills/pixerate-image-editor/SKILL.md +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +6 -0
- package/skills/pixerate-paint-editor/references/brush-facets.md +3 -1
- package/skills/pixerate-paint-sketch-to-finish/SKILL.md +1 -0
|
@@ -18,11 +18,17 @@ var _chunkWOFXZ3XUcjs = require('./chunk-WOFXZ3XU.cjs');
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
var _chunkNIBFKXMDcjs = require('./chunk-NIBFKXMD.cjs');
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
var _chunk2VX55NFMcjs = require('./chunk-2VX55NFM.cjs');
|
|
25
26
|
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
var _chunkW5M3J7PVcjs = require('./chunk-W5M3J7PV.cjs');
|
|
31
|
+
|
|
26
32
|
// src/brushes/presets.ts
|
|
27
33
|
var BRUSH_PRESETS = [
|
|
28
34
|
{
|
|
@@ -908,6 +914,9 @@ var MULTI_COLOR_MODES = ["off", "tip", "dab"];
|
|
|
908
914
|
var DEFAULT_MULTI_COLOR = { mode: "off", colors: [], order: "sequence", length: 12, softness: 1 };
|
|
909
915
|
var DEFAULT_BEHAVIOR = { pickup: 0, mix: 0.5, dirty: false, mixing: "pigment", multicolor: DEFAULT_MULTI_COLOR };
|
|
910
916
|
var BRUSH_ROTATIONS = ["fixed", "stroke", "barrel"];
|
|
917
|
+
var DAB_FLIPS = ["none", "x", "y", "xy"];
|
|
918
|
+
var TIP_GRAIN_MODES = ["moving", "texturised"];
|
|
919
|
+
var DEFAULT_TIP_GRAIN = { mode: "texturised", scale: 1, depth: 0.6, contrast: 1, invert: false };
|
|
911
920
|
var DEFAULT_DYNAMICS = {
|
|
912
921
|
minSize: 0,
|
|
913
922
|
pressureFlow: 0,
|
|
@@ -935,7 +944,8 @@ function normalizeDynamics(raw = {}, base = DEFAULT_DYNAMICS) {
|
|
|
935
944
|
sizeJitter: unit2(raw.sizeJitter, base.sizeJitter),
|
|
936
945
|
angleJitter: unit2(raw.angleJitter, base.angleJitter),
|
|
937
946
|
opacityJitter: unit2(raw.opacityJitter, base.opacityJitter),
|
|
938
|
-
scatter: range(raw.scatter, 0, 4, base.scatter)
|
|
947
|
+
scatter: range(raw.scatter, 0, 4, base.scatter),
|
|
948
|
+
...countAndFlip(raw, base)
|
|
939
949
|
};
|
|
940
950
|
}
|
|
941
951
|
var BRUSH_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
@@ -959,6 +969,28 @@ function normalizeBehavior(raw = {}, base = DEFAULT_BEHAVIOR) {
|
|
|
959
969
|
multicolor: normalizeMultiColor(_nullishCoalesce(raw.multicolor, () => ( {})), base.multicolor)
|
|
960
970
|
};
|
|
961
971
|
}
|
|
972
|
+
function countAndFlip(raw, base) {
|
|
973
|
+
const n = typeof raw.count === "number" && Number.isFinite(raw.count) ? Math.min(16, Math.max(1, Math.round(raw.count))) : _nullishCoalesce(base.count, () => ( 1));
|
|
974
|
+
const flip = DAB_FLIPS.includes(raw.flip) ? raw.flip : _nullishCoalesce(base.flip, () => ( "none"));
|
|
975
|
+
return { ...n > 1 ? { count: n } : {}, ...flip !== "none" ? { flip } : {} };
|
|
976
|
+
}
|
|
977
|
+
var withTipGrain = (g) => g ? { tipGrain: g } : {};
|
|
978
|
+
function readTipGrain(raw, base) {
|
|
979
|
+
if (raw === null) return null;
|
|
980
|
+
if (!raw || typeof raw !== "object") return base;
|
|
981
|
+
const r = raw;
|
|
982
|
+
const source = typeof r.source === "string" && /^\S{1,200}$/.test(r.source) ? r.source : _optionalChain([base, 'optionalAccess', _2 => _2.source]);
|
|
983
|
+
if (!source) return base;
|
|
984
|
+
const b = _nullishCoalesce(base, () => ( { source, ...DEFAULT_TIP_GRAIN }));
|
|
985
|
+
return {
|
|
986
|
+
source,
|
|
987
|
+
mode: TIP_GRAIN_MODES.includes(r.mode) ? r.mode : b.mode,
|
|
988
|
+
scale: range(r.scale, 0.05, 20, b.scale),
|
|
989
|
+
depth: unit2(r.depth, b.depth),
|
|
990
|
+
contrast: range(r.contrast, 0, 3, b.contrast),
|
|
991
|
+
invert: typeof r.invert === "boolean" ? r.invert : b.invert
|
|
992
|
+
};
|
|
993
|
+
}
|
|
962
994
|
var DEFAULT_SPRAY = { density: 0.5, size: 0.12 };
|
|
963
995
|
function readStamp(raw, base) {
|
|
964
996
|
if (raw === null) return null;
|
|
@@ -1041,12 +1073,12 @@ function normalizePaintBrush(raw = {}, base) {
|
|
|
1041
1073
|
if (rb.dirty !== void 0) mixerIn.cleanEachStroke = !rb.dirty;
|
|
1042
1074
|
if (rb.mixing !== void 0) mixerIn.mixing = rb.mixing;
|
|
1043
1075
|
}
|
|
1044
|
-
const mixer =
|
|
1076
|
+
const mixer = _chunkNIBFKXMDcjs.normalizeMixer.call(void 0, mixerIn);
|
|
1045
1077
|
let behavior = normalizeBehavior(rb, b.behavior);
|
|
1046
1078
|
if (engine === "mixer") behavior = { ...behavior, pickup: mixer.wet, mix: mixer.mix, dirty: !mixer.cleanEachStroke, mixing: mixer.mixing };
|
|
1047
1079
|
let paint = _nullishCoalesce(b.paint, () => ( null));
|
|
1048
1080
|
if (raw.paint !== void 0) {
|
|
1049
|
-
const p =
|
|
1081
|
+
const p = _chunkNIBFKXMDcjs.readPaintSource.call(void 0, raw.paint);
|
|
1050
1082
|
paint = p && p.type === "gradient" ? p : null;
|
|
1051
1083
|
} else if (rb.multicolor !== void 0) {
|
|
1052
1084
|
const mc = normalizeMultiColor(rb.multicolor, _nullishCoalesce(paintToMultiColor(paint), () => ( DEFAULT_MULTI_COLOR)));
|
|
@@ -1058,7 +1090,7 @@ function normalizePaintBrush(raw = {}, base) {
|
|
|
1058
1090
|
name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim().slice(0, 80) : b.name,
|
|
1059
1091
|
category: PAINT_BRUSH_CATEGORIES.includes(raw.category) ? raw.category : b.category,
|
|
1060
1092
|
engine,
|
|
1061
|
-
tip:
|
|
1093
|
+
tip: _chunkNIBFKXMDcjs.normalizeBrush.call(void 0, { ...b.tip, ..._nullishCoalesce(raw.tip, () => ( {})) }),
|
|
1062
1094
|
grain: unit2(raw.grain, b.grain),
|
|
1063
1095
|
pressureSmoothing: range(raw.pressureSmoothing, 0, 0.95, b.pressureSmoothing),
|
|
1064
1096
|
rotation: BRUSH_ROTATIONS.includes(raw.rotation) ? raw.rotation : b.rotation,
|
|
@@ -1066,8 +1098,8 @@ function normalizePaintBrush(raw = {}, base) {
|
|
|
1066
1098
|
mixer,
|
|
1067
1099
|
loadsColor: typeof raw.loadsColor === "boolean" ? raw.loadsColor : b.loadsColor,
|
|
1068
1100
|
behavior,
|
|
1069
|
-
blender:
|
|
1070
|
-
smudge:
|
|
1101
|
+
blender: _chunkNIBFKXMDcjs.normalizeBlender.call(void 0, _nullishCoalesce(raw.blender, () => ( {})), b.blender),
|
|
1102
|
+
smudge: _chunkNIBFKXMDcjs.normalizeSmudge.call(void 0, _nullishCoalesce(raw.smudge, () => ( {})), b.smudge),
|
|
1071
1103
|
wet: normalizeWetBrush(_nullishCoalesce(raw.wet, () => ( {})), _nullishCoalesce(b.wet, () => ( DEFAULT_WET_BRUSH))),
|
|
1072
1104
|
impasto: normalizeImpasto(_nullishCoalesce(raw.impasto, () => ( {})), _nullishCoalesce(b.impasto, () => ( DEFAULT_IMPASTO))),
|
|
1073
1105
|
particle: normalizeParticle(_nullishCoalesce(raw.particle, () => ( {})), _nullishCoalesce(b.particle, () => ( DEFAULT_PARTICLE))),
|
|
@@ -1076,6 +1108,7 @@ function normalizePaintBrush(raw = {}, base) {
|
|
|
1076
1108
|
light: normalizeLight(_nullishCoalesce(raw.light, () => ( {})), _nullishCoalesce(b.light, () => ( DEFAULT_LIGHT))),
|
|
1077
1109
|
stamp: readStamp(raw.stamp, _nullishCoalesce(b.stamp, () => ( null))),
|
|
1078
1110
|
spray: readSpray(raw.spray, _nullishCoalesce(b.spray, () => ( null))),
|
|
1111
|
+
...withTipGrain(readTipGrain(raw.tipGrain, _nullishCoalesce(b.tipGrain, () => ( null)))),
|
|
1079
1112
|
paint,
|
|
1080
1113
|
...raw.builtin ? { builtin: true } : {}
|
|
1081
1114
|
};
|
|
@@ -1085,16 +1118,16 @@ var BASE_BRUSH = {
|
|
|
1085
1118
|
name: "Round",
|
|
1086
1119
|
category: "custom",
|
|
1087
1120
|
engine: "dab",
|
|
1088
|
-
tip: { ...
|
|
1121
|
+
tip: { ..._chunkNIBFKXMDcjs.DEFAULT_BRUSH },
|
|
1089
1122
|
grain: 0,
|
|
1090
1123
|
pressureSmoothing: 0,
|
|
1091
1124
|
rotation: "fixed",
|
|
1092
1125
|
dynamics: { ...DEFAULT_DYNAMICS },
|
|
1093
|
-
mixer: { ...
|
|
1126
|
+
mixer: { ..._chunkNIBFKXMDcjs.DEFAULT_MIXER },
|
|
1094
1127
|
loadsColor: true,
|
|
1095
1128
|
behavior: { ...DEFAULT_BEHAVIOR, multicolor: { ...DEFAULT_MULTI_COLOR, colors: [] } },
|
|
1096
|
-
blender: { ...
|
|
1097
|
-
smudge: { ...
|
|
1129
|
+
blender: { ..._chunkNIBFKXMDcjs.DEFAULT_BLENDER },
|
|
1130
|
+
smudge: { ..._chunkNIBFKXMDcjs.DEFAULT_SMUDGE },
|
|
1098
1131
|
wet: { ...DEFAULT_WET_BRUSH },
|
|
1099
1132
|
impasto: { ...DEFAULT_IMPASTO },
|
|
1100
1133
|
particle: { ...DEFAULT_PARTICLE },
|
|
@@ -1109,6 +1142,7 @@ var BASE_BRUSH = {
|
|
|
1109
1142
|
// src/brushes/resolve.ts
|
|
1110
1143
|
var ALL = ["dab", "eraser", "mixer", "blender", "smudge", "wet", "impasto", "particle", "bristle", "sticker"];
|
|
1111
1144
|
var EXCHANGE = ["dab", "wet", "impasto"];
|
|
1145
|
+
var DABS = ["dab", "eraser", "mixer", "blender", "smudge"];
|
|
1112
1146
|
var NOT = { toEngine: (v) => !v, toFacet: (v) => !v };
|
|
1113
1147
|
var EDITOR_STAMPS = {
|
|
1114
1148
|
toEngine: (v) => v.flatMap((ref) => {
|
|
@@ -1133,6 +1167,9 @@ var BRUSH_FIELDS = [
|
|
|
1133
1167
|
f("dynamics.pressureSmoothing", "pressureSmoothing"),
|
|
1134
1168
|
f("dynamics.rotation", "rotation"),
|
|
1135
1169
|
...each("dynamics", "dynamics", ["minSize", "pressureFlow", "tiltSize", "tiltRoundness", "tiltOpacity", "velocitySize", "velocityOpacity", "sizeJitter", "angleJitter", "opacityJitter", "scatter"]),
|
|
1170
|
+
f("dynamics.count", "dynamics.count", DABS),
|
|
1171
|
+
f("dynamics.flip", "dynamics.flip", DABS),
|
|
1172
|
+
f("tip.grain", "tipGrain", DABS),
|
|
1136
1173
|
f("behaviour.grain", "grain"),
|
|
1137
1174
|
...each("light", "light", ["amount", "pressure"]),
|
|
1138
1175
|
// Paint exchange: the dab, wet and impasto engines' behaviour, or the Mixer Brush's settings.
|
|
@@ -1195,13 +1232,13 @@ function setPath(o, path, value) {
|
|
|
1195
1232
|
var copy = (v) => v !== null && typeof v === "object" ? JSON.parse(JSON.stringify(v)) : v;
|
|
1196
1233
|
function resolveEngine(brush) {
|
|
1197
1234
|
if (brush.kind === "tool") return brush.tool === "eraser" || brush.tool === "blender" || brush.tool === "smudge" ? brush.tool : "wet";
|
|
1198
|
-
if (_optionalChain([brush, 'access',
|
|
1199
|
-
if (_optionalChain([brush, 'access',
|
|
1235
|
+
if (_optionalChain([brush, 'access', _3 => _3.marks, 'optionalAccess', _4 => _4.kind]) === "particles") return "particle";
|
|
1236
|
+
if (_optionalChain([brush, 'access', _5 => _5.marks, 'optionalAccess', _6 => _6.kind]) === "objects") return "sticker";
|
|
1200
1237
|
if (brush.tip.shape === "bristle") return "bristle";
|
|
1201
|
-
const surface = _optionalChain([brush, 'access',
|
|
1238
|
+
const surface = _optionalChain([brush, 'access', _7 => _7.behaviour, 'optionalAccess', _8 => _8.surface]);
|
|
1202
1239
|
if (surface === "thick") return "impasto";
|
|
1203
1240
|
if (surface === "wet") return "wet";
|
|
1204
|
-
if (_optionalChain([brush, 'access',
|
|
1241
|
+
if (_optionalChain([brush, 'access', _9 => _9.behaviour, 'optionalAccess', _10 => _10.exchange, 'optionalAccess', _11 => _11.reservoir])) return "mixer";
|
|
1205
1242
|
return "dab";
|
|
1206
1243
|
}
|
|
1207
1244
|
var VARIETY_CATEGORY = {
|
|
@@ -1217,11 +1254,11 @@ var VARIETY_CATEGORY = {
|
|
|
1217
1254
|
};
|
|
1218
1255
|
var MEDIUM_CATEGORY = { paint: "paint", ink: "ink", dry: "pencil", spray: "airbrush" };
|
|
1219
1256
|
function resolveWetMedium(brush) {
|
|
1220
|
-
return _nullishCoalesce(_optionalChain([brush, 'access',
|
|
1257
|
+
return _nullishCoalesce(_optionalChain([brush, 'access', _12 => _12.behaviour, 'optionalAccess', _13 => _13.wet, 'optionalAccess', _14 => _14.medium]), () => ( (WET_MEDIA.includes(brush.variety) ? brush.variety : void 0)));
|
|
1221
1258
|
}
|
|
1222
1259
|
function resolveCategory(brush, engine = resolveEngine(brush)) {
|
|
1223
1260
|
if (brush.kind === "tool") return brush.tool === "eraser" || brush.tool === "blender" || brush.tool === "smudge" ? brush.tool : "wet-tool";
|
|
1224
|
-
if ((_nullishCoalesce(_optionalChain([brush, 'access',
|
|
1261
|
+
if ((_nullishCoalesce(_optionalChain([brush, 'access', _15 => _15.light, 'optionalAccess', _16 => _16.amount]), () => ( 0))) > 0) return "light";
|
|
1225
1262
|
switch (engine) {
|
|
1226
1263
|
case "particle":
|
|
1227
1264
|
return "procedural";
|
|
@@ -1243,18 +1280,18 @@ function resolveBrush(brush, options = {}) {
|
|
|
1243
1280
|
const engine = resolveEngine(brush);
|
|
1244
1281
|
const input = { id: brush.id, name: brush.name, category: resolveCategory(brush, engine), engine };
|
|
1245
1282
|
for (const field of BRUSH_FIELDS) {
|
|
1246
|
-
if (!field.engines.includes(engine) || field.shapes && !field.shapes.includes(brush.tip.shape) || field.marks && !field.marks.includes(_nullishCoalesce(_optionalChain([brush, 'access',
|
|
1283
|
+
if (!field.engines.includes(engine) || field.shapes && !field.shapes.includes(brush.tip.shape) || field.marks && !field.marks.includes(_nullishCoalesce(_optionalChain([brush, 'access', _17 => _17.marks, 'optionalAccess', _18 => _18.kind]), () => ( "continuous")))) continue;
|
|
1247
1284
|
const v = getPath(brush, field.facet);
|
|
1248
1285
|
if (v !== void 0) setPath(input, field.engine, field.map ? field.map.toEngine(v) : copy(v));
|
|
1249
1286
|
}
|
|
1250
|
-
if (engine === "dab" && _optionalChain([brush, 'access',
|
|
1287
|
+
if (engine === "dab" && _optionalChain([brush, 'access', _19 => _19.marks, 'optionalAccess', _20 => _20.kind]) === "scatter") input.spray = { ..._nullishCoalesce(input.spray, () => ( {})) };
|
|
1251
1288
|
if (engine === "wet") {
|
|
1252
1289
|
setPath(input, "wet.tool", brush.kind === "tool" ? WET_TOOL_OF[brush.tool] : "paint");
|
|
1253
1290
|
const medium = resolveWetMedium(brush);
|
|
1254
1291
|
if (medium) setPath(input, "wet.medium", medium);
|
|
1255
1292
|
}
|
|
1256
1293
|
if (engine === "blender" || engine === "smudge") input.loadsColor = false;
|
|
1257
|
-
const paint = _optionalChain([brush, 'access',
|
|
1294
|
+
const paint = _optionalChain([brush, 'access', _21 => _21.colour, 'optionalAccess', _22 => _22.default]);
|
|
1258
1295
|
if (paint && typeof paint === "object" && "type" in paint && paint.type === "gradient") input.paint = copy(paint);
|
|
1259
1296
|
return normalizePaintBrush({ ...input, ...options.builtin ? { builtin: true } : {} });
|
|
1260
1297
|
}
|
|
@@ -1340,7 +1377,7 @@ function migrateBrushV1(raw) {
|
|
|
1340
1377
|
const brush = skeleton(b);
|
|
1341
1378
|
const defaults = resolveBrush(brush);
|
|
1342
1379
|
for (const field of BRUSH_FIELDS) {
|
|
1343
|
-
if (!field.engines.includes(b.engine) || field.shapes && !field.shapes.includes(brush.tip.shape) || field.marks && !field.marks.includes(_nullishCoalesce(_optionalChain([brush, 'access',
|
|
1380
|
+
if (!field.engines.includes(b.engine) || field.shapes && !field.shapes.includes(brush.tip.shape) || field.marks && !field.marks.includes(_nullishCoalesce(_optionalChain([brush, 'access', _23 => _23.marks, 'optionalAccess', _24 => _24.kind]), () => ( "continuous")))) continue;
|
|
1344
1381
|
const v = getPath(b, field.engine);
|
|
1345
1382
|
if (!equal(v, getPath(defaults, field.engine))) setPath(brush, field.facet, field.map ? field.map.toFacet(v) : copy2(v));
|
|
1346
1383
|
}
|
|
@@ -1355,6 +1392,8 @@ var BRUSH_TOOLS = ["eraser", "blender", "smudge", "water", "dryer", "blow", "blo
|
|
|
1355
1392
|
var BRUSH_TIP_SHAPES = ["round", "flat", "chisel", "bristle", "stamp"];
|
|
1356
1393
|
var BRUSH_MARKS_KINDS = ["continuous", "scatter", "particles", "objects"];
|
|
1357
1394
|
var BRUSH_SURFACES = ["dry", "wet", "thick"];
|
|
1395
|
+
var BRUSH_GRAIN_MODES = ["moving", "texturised"];
|
|
1396
|
+
var BRUSH_FLIPS = ["none", "x", "y", "xy"];
|
|
1358
1397
|
|
|
1359
1398
|
// src/brushes/schema.ts
|
|
1360
1399
|
var MAX_BRUSH_PALETTE = 16;
|
|
@@ -1376,8 +1415,22 @@ var BRUSH_STAMP_PATTERN = `^(?:[^:\\s]+:\\S+|${_chunkRDFHWAHLcjs.STAMP_KINDS.joi
|
|
|
1376
1415
|
var STAMP = {
|
|
1377
1416
|
type: "string",
|
|
1378
1417
|
pattern: BRUSH_STAMP_PATTERN,
|
|
1379
|
-
description: `A sticker ref provider:id (find_stickers lists them; the editor's own are ${_chunkRDFHWAHLcjs.STAMP_KINDS.map((k) => `editor:${k}`).join(", ")}). A bare editor stamp (star) means editor:star.`
|
|
1418
|
+
description: `A sticker ref provider:id (find_stickers lists them; the editor's own are ${_chunkRDFHWAHLcjs.STAMP_KINDS.map((k) => `editor:${k}`).join(", ")}), or an image source source:<id> (list_brush_sources lists them; add_brush_source makes one from an image). A bare editor stamp (star) means editor:star.`
|
|
1380
1419
|
};
|
|
1420
|
+
var TIP_GRAIN_PATTERNS = _chunkNIBFKXMDcjs.GRAIN_PATTERNS.filter((p) => p !== "smooth");
|
|
1421
|
+
var BRUSH_GRAIN_SOURCE_PATTERN = `^(?:source:[a-z0-9][a-z0-9-]{0,63}|${TIP_GRAIN_PATTERNS.join("|")})$`;
|
|
1422
|
+
var TIP_GRAIN = object(
|
|
1423
|
+
"Texture inside the tip (a grain source), separate from the paper's grain: each dab is multiplied by it. Continuous and scatter marks of round and stamp tips.",
|
|
1424
|
+
{
|
|
1425
|
+
source: { type: "string", pattern: BRUSH_GRAIN_SOURCE_PATTERN, description: `The texture: a built-in pattern (${TIP_GRAIN_PATTERNS.join(", ")}) or an image source source:<id> (list_brush_sources); light parts are peaks, which paint.` },
|
|
1426
|
+
mode: oneOf2(BRUSH_GRAIN_MODES, "texturised (default): the grain stays put on the canvas, a texture under the paint; moving: it rolls along with the tip, turning with it."),
|
|
1427
|
+
scale: num(0.05, 20, "Grain size: 1 is the pattern's or image's own size, 2 twice as coarse (default 1)."),
|
|
1428
|
+
depth: unit3("How much the grain takes out of each dab: 0 none, 1 only the peaks paint (default 0.6)."),
|
|
1429
|
+
contrast: num(0, 3, "Height contrast around the middle: below 1 softer, above 1 harsher (default 1)."),
|
|
1430
|
+
invert: bool("Swap peaks and valleys (default false).")
|
|
1431
|
+
},
|
|
1432
|
+
["source"]
|
|
1433
|
+
);
|
|
1381
1434
|
var BRISTLES = object("Bristle tips: the tuft. Missing fields take their defaults.", {
|
|
1382
1435
|
count: int(3, 128, "Bristles in the tuft (default 32)."),
|
|
1383
1436
|
stiffness: unit3("Stiff bristles follow the tip; soft ones lag and bend into curves at turns (default 0.5)."),
|
|
@@ -1389,9 +1442,9 @@ var BRISTLES = object("Bristle tips: the tuft. Missing fields take their default
|
|
|
1389
1442
|
var TIP = object(
|
|
1390
1443
|
"The tip: its shape and the dab it lays.",
|
|
1391
1444
|
{
|
|
1392
|
-
shape: oneOf2(BRUSH_TIP_SHAPES, "round, flat, chisel: an ellipse set by roundness and angle (the name is for people); bristle: a tuft of bristles (see bristle); stamp:
|
|
1393
|
-
size: num(1,
|
|
1394
|
-
hardness: unit3("Edge hardness, 0 soft .. 1 hard (default 1)."),
|
|
1445
|
+
shape: oneOf2(BRUSH_TIP_SHAPES, "round, flat, chisel: an ellipse set by roundness and angle (the name is for people); bristle: a tuft of bristles (see bristle); stamp: dabs shaped like a stamp, a sticker or an image source (see stamps), also laid as sprays, objects or particles."),
|
|
1446
|
+
size: num(1, _chunkNIBFKXMDcjs.MAX_BRUSH_SIZE, "Diameter in px (default 20)."),
|
|
1447
|
+
hardness: unit3("Edge hardness, 0 soft .. 1 hard (default 1). Image-source stamps fade towards their edges below 1."),
|
|
1395
1448
|
spacing: num(0.01, 10, "Dab spacing as a fraction of the diameter: 0.05 smooth, 1 dotted (default 0.1)."),
|
|
1396
1449
|
angle: num(-360, 360, "Tip angle in degrees, counter-clockwise; added to the stroke direction or barrel with dynamics.rotation (default 0)."),
|
|
1397
1450
|
roundness: num(0.01, 1, "Roundness: 1 round (default), lower a flat ellipse (pastel, chisel)."),
|
|
@@ -1407,8 +1460,9 @@ var TIP = object(
|
|
|
1407
1460
|
minItems: 1,
|
|
1408
1461
|
maxItems: MAX_STICKER_STAMPS,
|
|
1409
1462
|
items: STAMP,
|
|
1410
|
-
description: `Stamp tips:
|
|
1411
|
-
}
|
|
1463
|
+
description: `Stamp tips: stickers or image sources (source:<id>). Continuous dabs and sprays lay the first; objects take only stickers and pick one at random per mark (default editor:star; repeats count once); particles use the first ${MAX_DOT_TEXTURES} in marks.stampOrder, and repeats weight the order.`
|
|
1464
|
+
},
|
|
1465
|
+
grain: TIP_GRAIN
|
|
1412
1466
|
},
|
|
1413
1467
|
["shape"]
|
|
1414
1468
|
);
|
|
@@ -1478,7 +1532,9 @@ var DYNAMICS = object("Pen feel: pressure, tilt, velocity, jitter, rotation and
|
|
|
1478
1532
|
sizeJitter: unit3("Random size change per dab (default 0)."),
|
|
1479
1533
|
angleJitter: unit3("Random angle per dab, as a fraction of \xB1180\xB0 (default 0)."),
|
|
1480
1534
|
opacityJitter: unit3("Random opacity drop per dab (default 0)."),
|
|
1481
|
-
scatter: num(0, 4, "Random offset across the stroke, in diameters (default 0).")
|
|
1535
|
+
scatter: num(0, 4, "Random offset across the stroke, in diameters (default 0)."),
|
|
1536
|
+
count: int(1, 16, "Dabs laid at each spacing step, each with its own jitter and scatter (default 1). Dab brushes."),
|
|
1537
|
+
flip: oneOf2(BRUSH_FLIPS, "Mirror each dab at random across x, y or both (default none): variety for image and stamp tips.")
|
|
1482
1538
|
});
|
|
1483
1539
|
var EXCHANGE2 = object("Paint exchange with the canvas: picking up paint and mixing it into what the brush lays. Paint brushes on a dry, wet or thick surface and Mixer Brushes.", {
|
|
1484
1540
|
pickup: unit3("Pick-up ratio: how much of the paint under each dab the brush lifts and carries (default 0; with a reservoir 0.5)."),
|
|
@@ -1564,7 +1620,7 @@ var EFFECT = object(
|
|
|
1564
1620
|
);
|
|
1565
1621
|
var PAINT = {
|
|
1566
1622
|
description: "A colour a brush can carry: a solid colour, a light / dark pair, a gradient or effect paint.",
|
|
1567
|
-
anyOf: [SOLID, DUAL,
|
|
1623
|
+
anyOf: [SOLID, DUAL, _chunkNIBFKXMDcjs.PAINT_GRADIENT_JSON_SCHEMA, EFFECT]
|
|
1568
1624
|
};
|
|
1569
1625
|
var COLOUR = object("The brush's colours. The user's colour still wins unless own is set.", {
|
|
1570
1626
|
default: {
|
|
@@ -1600,6 +1656,7 @@ var BRUSH_JSON_SCHEMA = object(
|
|
|
1600
1656
|
);
|
|
1601
1657
|
var BRUSH_FACET_SCHEMAS = {
|
|
1602
1658
|
tip: TIP,
|
|
1659
|
+
tipGrain: TIP_GRAIN,
|
|
1603
1660
|
marks: { continuous: CONTINUOUS, scatter: SCATTER, particles: PARTICLES, objects: OBJECTS },
|
|
1604
1661
|
dynamics: DYNAMICS,
|
|
1605
1662
|
behaviour: BEHAVIOUR,
|
|
@@ -1609,6 +1666,139 @@ var BRUSH_FACET_SCHEMAS = {
|
|
|
1609
1666
|
paint: PAINT
|
|
1610
1667
|
};
|
|
1611
1668
|
|
|
1669
|
+
// src/brushes/sources.ts
|
|
1670
|
+
var BRUSH_SOURCE_PROVIDER = "source";
|
|
1671
|
+
var MAX_BRUSH_SOURCE_SIZE = 512;
|
|
1672
|
+
var MAX_BRUSH_SOURCE_INPUT_PIXELS = 4096 * 4096;
|
|
1673
|
+
var MAX_BRUSH_SOURCES = 500;
|
|
1674
|
+
var BRUSH_SOURCE_CHANNELS = ["auto", "alpha", "darkness", "lightness"];
|
|
1675
|
+
var BrushSourceError = class extends Error {
|
|
1676
|
+
constructor(message) {
|
|
1677
|
+
super(message);
|
|
1678
|
+
this.name = "BrushSourceError";
|
|
1679
|
+
}
|
|
1680
|
+
};
|
|
1681
|
+
function brushSourceRef(id) {
|
|
1682
|
+
return `${BRUSH_SOURCE_PROVIDER}:${id}`;
|
|
1683
|
+
}
|
|
1684
|
+
function isBrushSourceRef(ref) {
|
|
1685
|
+
return typeof ref === "string" && ref.startsWith(`${BRUSH_SOURCE_PROVIDER}:`);
|
|
1686
|
+
}
|
|
1687
|
+
function brushSourceIdOf(ref) {
|
|
1688
|
+
return isBrushSourceRef(ref) ? ref.slice(BRUSH_SOURCE_PROVIDER.length + 1) : null;
|
|
1689
|
+
}
|
|
1690
|
+
function brushSourceIds(brush) {
|
|
1691
|
+
const refs = [..._nullishCoalesce(brush.tip.stamps, () => ( [])), ...brush.tip.grain ? [brush.tip.grain.source] : []];
|
|
1692
|
+
return [...new Set(refs.map(brushSourceIdOf).filter((id) => id !== null))];
|
|
1693
|
+
}
|
|
1694
|
+
function summarizeBrushSource(s) {
|
|
1695
|
+
const { image: _i, ...rest } = s;
|
|
1696
|
+
return { ...rest, ref: brushSourceRef(s.id) };
|
|
1697
|
+
}
|
|
1698
|
+
function brushSourceIdFromName(name) {
|
|
1699
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "source";
|
|
1700
|
+
}
|
|
1701
|
+
var text = (v, max) => typeof v === "string" && v.trim() ? v.trim().slice(0, max) : void 0;
|
|
1702
|
+
function fit(width, height, mask, max) {
|
|
1703
|
+
const scale = Math.min(1, max / Math.max(width, height));
|
|
1704
|
+
if (scale >= 1) return { width, height, mask: Uint8Array.from(mask) };
|
|
1705
|
+
const w = Math.max(1, Math.round(width * scale));
|
|
1706
|
+
const h = Math.max(1, Math.round(height * scale));
|
|
1707
|
+
const out = new Uint8Array(w * h);
|
|
1708
|
+
for (let y = 0; y < h; y++) {
|
|
1709
|
+
const y0 = Math.floor(y * height / h);
|
|
1710
|
+
const y1 = Math.max(y0 + 1, Math.floor((y + 1) * height / h));
|
|
1711
|
+
for (let x = 0; x < w; x++) {
|
|
1712
|
+
const x0 = Math.floor(x * width / w);
|
|
1713
|
+
const x1 = Math.max(x0 + 1, Math.floor((x + 1) * width / w));
|
|
1714
|
+
let sum = 0;
|
|
1715
|
+
for (let yy = y0; yy < y1; yy++) for (let xx = x0; xx < x1; xx++) sum += mask[yy * width + xx];
|
|
1716
|
+
out[y * w + x] = Math.round(sum / ((y1 - y0) * (x1 - x0)));
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return { width: w, height: h, mask: out };
|
|
1720
|
+
}
|
|
1721
|
+
function brushSourceFromMask(meta, width, height, mask) {
|
|
1722
|
+
if (!BRUSH_ID.test(meta.id)) throw new BrushSourceError(`Source id ${JSON.stringify(meta.id)} is not valid: use lowercase letters, digits and dashes (up to 64).`);
|
|
1723
|
+
if (!(Number.isInteger(width) && Number.isInteger(height) && width > 0 && height > 0) || mask.length !== width * height) throw new BrushSourceError("The mask is not width \xD7 height bytes.");
|
|
1724
|
+
let any = false;
|
|
1725
|
+
for (let i = 0; i < mask.length && !any; i++) any = mask[i] > 0;
|
|
1726
|
+
if (!any) throw new BrushSourceError(`"${meta.name}" has no marks: every pixel is empty. Pick another channel (alpha, darkness or lightness) or set invert.`);
|
|
1727
|
+
const f2 = fit(width, height, mask, MAX_BRUSH_SOURCE_SIZE);
|
|
1728
|
+
const name = _nullishCoalesce(text(meta.name, 80), () => ( meta.id));
|
|
1729
|
+
const licence = text(meta.licence, 2e3);
|
|
1730
|
+
const origin = text(meta.origin, 200);
|
|
1731
|
+
return { id: meta.id, name, width: f2.width, height: f2.height, image: _chunkW5M3J7PVcjs.pngDataUrl.call(void 0, f2.width, f2.height, f2.mask, 1), ...licence ? { licence } : {}, ...origin ? { origin } : {} };
|
|
1732
|
+
}
|
|
1733
|
+
function makeBrushSource(input, problems = []) {
|
|
1734
|
+
const name = text(input.name, 80);
|
|
1735
|
+
if (!name) throw new BrushSourceError("A brush source needs a name.");
|
|
1736
|
+
const id = _nullishCoalesce(input.id, () => ( brushSourceIdFromName(name)));
|
|
1737
|
+
if (!BRUSH_ID.test(id)) throw new BrushSourceError(`Source id ${JSON.stringify(id)} is not valid: use lowercase letters, digits and dashes (up to 64), starting with a letter or digit.`);
|
|
1738
|
+
let bytes;
|
|
1739
|
+
if (typeof input.image === "string") {
|
|
1740
|
+
const parsed = _chunkW5M3J7PVcjs.parseDataUrl.call(void 0, input.image);
|
|
1741
|
+
if (!parsed) throw new BrushSourceError("image must be a data: URL (data:image/png;base64,\u2026) or PNG bytes.");
|
|
1742
|
+
bytes = parsed.bytes;
|
|
1743
|
+
} else bytes = input.image;
|
|
1744
|
+
let png;
|
|
1745
|
+
try {
|
|
1746
|
+
png = _chunkW5M3J7PVcjs.decodePng.call(void 0, bytes, { maxPixels: MAX_BRUSH_SOURCE_INPUT_PIXELS });
|
|
1747
|
+
} catch (e) {
|
|
1748
|
+
throw new BrushSourceError(`The image can't be read as a PNG (${e.message}); convert it to PNG, at most ${MAX_BRUSH_SOURCE_INPUT_PIXELS} pixels.`);
|
|
1749
|
+
}
|
|
1750
|
+
const { width, height, data } = png;
|
|
1751
|
+
const n = width * height;
|
|
1752
|
+
let channel = _nullishCoalesce(input.channel, () => ( "auto"));
|
|
1753
|
+
if (channel === "auto") {
|
|
1754
|
+
let transparent = false;
|
|
1755
|
+
for (let i = 0; i < n && !transparent; i++) transparent = data[i * 4 + 3] < 250;
|
|
1756
|
+
channel = transparent ? "alpha" : "darkness";
|
|
1757
|
+
problems.push(`channel auto read the marks from ${channel === "alpha" ? "the transparency" : "the dark parts (dark marks on a light background)"}.`);
|
|
1758
|
+
}
|
|
1759
|
+
const mask = new Uint8Array(n);
|
|
1760
|
+
for (let i = 0; i < n; i++) {
|
|
1761
|
+
const a = data[i * 4 + 3];
|
|
1762
|
+
const lum = (0.2126 * data[i * 4] + 0.7152 * data[i * 4 + 1] + 0.0722 * data[i * 4 + 2]) * (a / 255) + 255 * (1 - a / 255);
|
|
1763
|
+
const v = channel === "alpha" ? a : channel === "darkness" ? 255 - lum : lum;
|
|
1764
|
+
mask[i] = Math.round(input.invert ? 255 - v : v);
|
|
1765
|
+
}
|
|
1766
|
+
const source = brushSourceFromMask({ id, name, licence: input.licence, origin: input.origin }, width, height, mask);
|
|
1767
|
+
if (source.width !== width) problems.push(`The image was scaled from ${width}\xD7${height} to ${source.width}\xD7${source.height} (sources are kept at most ${MAX_BRUSH_SOURCE_SIZE} px).`);
|
|
1768
|
+
return source;
|
|
1769
|
+
}
|
|
1770
|
+
var GREY_PNG = /^data:image\/png;base64,[A-Za-z0-9+/]+=*$/;
|
|
1771
|
+
function readBrushSource(raw, at, problems) {
|
|
1772
|
+
const r = raw;
|
|
1773
|
+
if (!r || typeof r !== "object" || Array.isArray(r)) return void problems.push(`${at} is not a brush source; skipped.`), null;
|
|
1774
|
+
if (typeof r.id !== "string" || !BRUSH_ID.test(r.id)) return void problems.push(`${at} has no valid id; skipped.`), null;
|
|
1775
|
+
const ok = (v) => Number.isInteger(v) && v >= 1 && v <= MAX_BRUSH_SOURCE_SIZE;
|
|
1776
|
+
if (!ok(r.width) || !ok(r.height)) return void problems.push(`${at} ("${r.id}") has no valid width and height (1..${MAX_BRUSH_SOURCE_SIZE}); skipped.`), null;
|
|
1777
|
+
if (typeof r.image !== "string" || !GREY_PNG.test(r.image)) return void problems.push(`${at} ("${r.id}") has no PNG data: URL image; skipped.`), null;
|
|
1778
|
+
const name = _nullishCoalesce(text(r.name, 80), () => ( r.id));
|
|
1779
|
+
const licence = text(r.licence, 2e3);
|
|
1780
|
+
const origin = text(r.origin, 200);
|
|
1781
|
+
return { id: r.id, name, width: r.width, height: r.height, image: r.image, ...licence ? { licence } : {}, ...origin ? { origin } : {} };
|
|
1782
|
+
}
|
|
1783
|
+
var decoded = /* @__PURE__ */ new Map();
|
|
1784
|
+
function decodeBrushSource(source) {
|
|
1785
|
+
const hit = decoded.get(source.image);
|
|
1786
|
+
if (hit) return hit;
|
|
1787
|
+
const parsed = _chunkW5M3J7PVcjs.parseDataUrl.call(void 0, source.image);
|
|
1788
|
+
if (!parsed) throw new BrushSourceError("The source image is not a data: URL.");
|
|
1789
|
+
const png = _chunkW5M3J7PVcjs.decodePng.call(void 0, parsed.bytes, { keepGray: true, maxPixels: MAX_BRUSH_SOURCE_SIZE * MAX_BRUSH_SOURCE_SIZE });
|
|
1790
|
+
let mask;
|
|
1791
|
+
if (png.channels === 1) mask = png.data;
|
|
1792
|
+
else {
|
|
1793
|
+
mask = new Uint8ClampedArray(png.width * png.height);
|
|
1794
|
+
for (let i = 0; i < mask.length; i++) mask[i] = Math.round(png.data[i * 4] * png.data[i * 4 + 3] / 255);
|
|
1795
|
+
}
|
|
1796
|
+
const out = { width: png.width, height: png.height, mask };
|
|
1797
|
+
if (decoded.size > 256) decoded.delete(decoded.keys().next().value);
|
|
1798
|
+
decoded.set(source.image, out);
|
|
1799
|
+
return out;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1612
1802
|
// src/brushes/normalize.ts
|
|
1613
1803
|
var WET_BRUSH_TOOLS = ["water", "dryer", "blow", "blot", "wet-blend", "wet-drag"];
|
|
1614
1804
|
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -1676,7 +1866,7 @@ function readObject(value, schema, path, problems) {
|
|
|
1676
1866
|
const out = {};
|
|
1677
1867
|
for (const [key, v] of Object.entries(value)) {
|
|
1678
1868
|
if (v === void 0) continue;
|
|
1679
|
-
const child = _optionalChain([schema, 'access',
|
|
1869
|
+
const child = _optionalChain([schema, 'access', _25 => _25.properties, 'optionalAccess', _26 => _26[key]]);
|
|
1680
1870
|
if (!child) {
|
|
1681
1871
|
problems.push(`${path}.${key} is not a brush field; dropped.`);
|
|
1682
1872
|
continue;
|
|
@@ -1690,7 +1880,7 @@ function readPaint(value, path, problems) {
|
|
|
1690
1880
|
if (typeof value === "string") {
|
|
1691
1881
|
if (SOLID2.test(value.trim())) return value.trim().toLowerCase();
|
|
1692
1882
|
} else if (isObject(value) && value.type === "gradient") {
|
|
1693
|
-
const g =
|
|
1883
|
+
const g = _chunkNIBFKXMDcjs.readPaintSource.call(void 0, value);
|
|
1694
1884
|
if (g && g.type === "gradient") return g;
|
|
1695
1885
|
} else if (isObject(value) && value.type === "effect") {
|
|
1696
1886
|
if (!_chunkWOFXZ3XUcjs.validateJsonSchema.call(void 0, value, BRUSH_FACET_SCHEMAS.paint.anyOf[3]).length) return clone(value);
|
|
@@ -1766,15 +1956,15 @@ function normalizeBrushSpec(raw, problems = []) {
|
|
|
1766
1956
|
delete brush.marks;
|
|
1767
1957
|
}
|
|
1768
1958
|
}
|
|
1769
|
-
if (_optionalChain([brush, 'access',
|
|
1770
|
-
const surface = _optionalChain([brush, 'access',
|
|
1771
|
-
const why = brush.tip.shape === "bristle" ? "bristle tips lay their own marks" : surface === "wet" || surface === "thick" ? `${surface} paint is not sprayed` : _optionalChain([brush, 'access',
|
|
1959
|
+
if (_optionalChain([brush, 'access', _27 => _27.marks, 'optionalAccess', _28 => _28.kind]) === "scatter") {
|
|
1960
|
+
const surface = _optionalChain([brush, 'access', _29 => _29.behaviour, 'optionalAccess', _30 => _30.surface]);
|
|
1961
|
+
const why = brush.tip.shape === "bristle" ? "bristle tips lay their own marks" : surface === "wet" || surface === "thick" ? `${surface} paint is not sprayed` : _optionalChain([brush, 'access', _31 => _31.behaviour, 'optionalAccess', _32 => _32.exchange, 'optionalAccess', _33 => _33.reservoir]) ? "the Mixer Brush is not sprayed" : null;
|
|
1772
1962
|
if (why) {
|
|
1773
1963
|
drop("marks", why);
|
|
1774
1964
|
delete brush.marks;
|
|
1775
1965
|
}
|
|
1776
1966
|
}
|
|
1777
|
-
if (_optionalChain([brush, 'access',
|
|
1967
|
+
if (_optionalChain([brush, 'access', _34 => _34.marks, 'optionalAccess', _35 => _35.kind]) === "objects" && brush.tip.shape !== "stamp") {
|
|
1778
1968
|
problems.push("marks objects lay stamps: tip.shape set to stamp.");
|
|
1779
1969
|
brush.tip.shape = "stamp";
|
|
1780
1970
|
}
|
|
@@ -1787,13 +1977,26 @@ function normalizeBrushSpec(raw, problems = []) {
|
|
|
1787
1977
|
delete brush.tip.stamps;
|
|
1788
1978
|
}
|
|
1789
1979
|
if (brush.tip.stamps) {
|
|
1790
|
-
|
|
1980
|
+
let stamps = brush.tip.stamps.map((s) => _chunkRDFHWAHLcjs.normalizeStickerRef.call(void 0, s));
|
|
1981
|
+
if (_optionalChain([brush, 'access', _36 => _36.marks, 'optionalAccess', _37 => _37.kind]) === "objects" && stamps.some(isBrushSourceRef)) {
|
|
1982
|
+
problems.push("tip.stamps: image sources (source:\u2026) dropped: objects marks lay stickers; use continuous, scatter or particles marks for an image tip.");
|
|
1983
|
+
stamps = stamps.filter((s) => !isBrushSourceRef(s));
|
|
1984
|
+
}
|
|
1791
1985
|
if (stamps.length) brush.tip.stamps = stamps;
|
|
1792
1986
|
else {
|
|
1793
1987
|
drop("tip.stamps", "none of the stamps can be laid");
|
|
1794
1988
|
delete brush.tip.stamps;
|
|
1795
1989
|
}
|
|
1796
1990
|
}
|
|
1991
|
+
if (brush.tip.grain) {
|
|
1992
|
+
const surface = _optionalChain([brush, 'access', _38 => _38.behaviour, 'optionalAccess', _39 => _39.surface]);
|
|
1993
|
+
const marks = _optionalChain([brush, 'access', _40 => _40.marks, 'optionalAccess', _41 => _41.kind]);
|
|
1994
|
+
const why = !brush.tip.grain.source ? "it needs a source (a grain pattern or source:<id>)" : brush.tip.shape === "bristle" ? "bristle tips lay their own marks" : marks === "particles" || marks === "objects" ? `${marks} marks are not dabs` : surface === "wet" || surface === "thick" ? `${surface} paint lays its own texture` : kind === "tool" && WET_BRUSH_TOOLS.includes(brush.tool) ? "the wet tools work on the wet layer" : null;
|
|
1995
|
+
if (why) {
|
|
1996
|
+
drop("tip.grain", why);
|
|
1997
|
+
delete brush.tip.grain;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
1797
2000
|
const b = brush.behaviour;
|
|
1798
2001
|
if (b) {
|
|
1799
2002
|
const wetTool = kind === "tool" && WET_BRUSH_TOOLS.includes(brush.tool);
|
|
@@ -1824,7 +2027,7 @@ function normalizeBrushSpec(raw, problems = []) {
|
|
|
1824
2027
|
if (b.exchange && kind === "tool" && !wetTool) {
|
|
1825
2028
|
drop("behaviour.exchange", `the ${brush.tool} does not pick paint up`);
|
|
1826
2029
|
delete b.exchange;
|
|
1827
|
-
} else if (b.exchange && (_optionalChain([brush, 'access',
|
|
2030
|
+
} else if (b.exchange && (_optionalChain([brush, 'access', _42 => _42.marks, 'optionalAccess', _43 => _43.kind]) === "particles" || _optionalChain([brush, 'access', _44 => _44.marks, 'optionalAccess', _45 => _45.kind]) === "objects" || brush.tip.shape === "bristle")) {
|
|
1828
2031
|
drop("behaviour.exchange", "particle, object and bristle marks do not pick paint up");
|
|
1829
2032
|
delete b.exchange;
|
|
1830
2033
|
}
|
|
@@ -1850,7 +2053,9 @@ var PAINT_PROFILE = {
|
|
|
1850
2053
|
motion: "full",
|
|
1851
2054
|
colours: ALL_COLOURS,
|
|
1852
2055
|
velocity: "full",
|
|
1853
|
-
tilt: "full"
|
|
2056
|
+
tilt: "full",
|
|
2057
|
+
tipGrain: "full",
|
|
2058
|
+
dabCount: "full"
|
|
1854
2059
|
};
|
|
1855
2060
|
var IMAGE_PROFILE = {
|
|
1856
2061
|
id: "image",
|
|
@@ -1867,7 +2072,9 @@ var IMAGE_PROFILE = {
|
|
|
1867
2072
|
motion: "none",
|
|
1868
2073
|
colours: ["solid", "dual", "gradient"],
|
|
1869
2074
|
velocity: "ignored",
|
|
1870
|
-
tilt: "ignored"
|
|
2075
|
+
tilt: "ignored",
|
|
2076
|
+
tipGrain: "ignored",
|
|
2077
|
+
dabCount: "ignored"
|
|
1871
2078
|
};
|
|
1872
2079
|
var CANVAS_PROFILE = {
|
|
1873
2080
|
id: "canvas",
|
|
@@ -1884,7 +2091,9 @@ var CANVAS_PROFILE = {
|
|
|
1884
2091
|
motion: "none",
|
|
1885
2092
|
colours: ["solid", "dual", "gradient"],
|
|
1886
2093
|
velocity: "ignored",
|
|
1887
|
-
tilt: "full"
|
|
2094
|
+
tilt: "full",
|
|
2095
|
+
tipGrain: "ignored",
|
|
2096
|
+
dabCount: "ignored"
|
|
1888
2097
|
};
|
|
1889
2098
|
var VECTOR_PROFILE = { ...CANVAS_PROFILE, id: "vector", name: "Vector editor", kinds: ["mark", "tool"], tools: ["blender", "smudge"], surfaces: ["dry", "wet"], reservoir: "full", light: "ignored" };
|
|
1890
2099
|
var BRUSH_HOST_PROFILES = { paint: PAINT_PROFILE, image: IMAGE_PROFILE, canvas: CANVAS_PROFILE, vector: VECTOR_PROFILE };
|
|
@@ -1908,32 +2117,37 @@ function checkBrush(brush, profile) {
|
|
|
1908
2117
|
else if (brush.kind === "tool" && brush.tool && !profile.tools.includes(brush.tool)) {
|
|
1909
2118
|
add("tool", "error", `${host} has no ${brush.tool} tool.`, profile.tools.length ? `Use one of: ${list(profile.tools)}.` : `${host} has no tools; use a mark brush.`);
|
|
1910
2119
|
}
|
|
1911
|
-
if (!profile.tipShapes.includes(brush.tip.shape))
|
|
1912
|
-
|
|
2120
|
+
if (!profile.tipShapes.includes(brush.tip.shape)) {
|
|
2121
|
+
const image = brush.tip.shape === "stamp" && (_nullishCoalesce(brush.tip.stamps, () => ( []))).some(isBrushSourceRef);
|
|
2122
|
+
add("tip.shape", "error", `${host} can't lay a ${image ? "stamp tip (image tips are stamps)" : `${brush.tip.shape} tip`}.`, `Set tip.shape to one of: ${list(profile.tipShapes)}.`);
|
|
2123
|
+
}
|
|
2124
|
+
const marks = _nullishCoalesce(_optionalChain([brush, 'access', _46 => _46.marks, 'optionalAccess', _47 => _47.kind]), () => ( "continuous"));
|
|
1913
2125
|
if (!profile.marks.includes(marks)) add("marks", "error", `${host} can't lay ${marks} marks.`, `Use marks ${list(profile.marks)} (remove marks for continuous dabs).`);
|
|
1914
|
-
const surface = _nullishCoalesce(_optionalChain([brush, 'access',
|
|
2126
|
+
const surface = _nullishCoalesce(_optionalChain([brush, 'access', _48 => _48.behaviour, 'optionalAccess', _49 => _49.surface]), () => ( "dry"));
|
|
1915
2127
|
if (brush.kind === "mark" && !profile.surfaces.includes(surface)) add("behaviour.surface", "error", `${host} has no ${surface} surface.`, `Set behaviour.surface to ${list(profile.surfaces)}.`);
|
|
1916
|
-
const ex = _optionalChain([brush, 'access',
|
|
1917
|
-
feature(profile.reservoir, !!_optionalChain([ex, 'optionalAccess',
|
|
1918
|
-
feature(profile.exchange, !_optionalChain([ex, 'optionalAccess',
|
|
1919
|
-
feature(profile.grain, nonZero(_optionalChain([brush, 'access',
|
|
1920
|
-
feature(profile.light, nonZero(_optionalChain([brush, 'access',
|
|
2128
|
+
const ex = _optionalChain([brush, 'access', _50 => _50.behaviour, 'optionalAccess', _51 => _51.exchange]);
|
|
2129
|
+
feature(profile.reservoir, !!_optionalChain([ex, 'optionalAccess', _52 => _52.reservoir]), "behaviour.exchange.reservoir", "a Mixer Brush reservoir", "Remove behaviour.exchange.reservoir.");
|
|
2130
|
+
feature(profile.exchange, !_optionalChain([ex, 'optionalAccess', _53 => _53.reservoir]) && (nonZero(_optionalChain([ex, 'optionalAccess', _54 => _54.pickup])) || !!_optionalChain([ex, 'optionalAccess', _55 => _55.dirty])), "behaviour.exchange", "picking paint up", "Remove behaviour.exchange.");
|
|
2131
|
+
feature(profile.grain, nonZero(_optionalChain([brush, 'access', _56 => _56.behaviour, 'optionalAccess', _57 => _57.grain])), "behaviour.grain", "paper grain", "Set behaviour.grain to 0.");
|
|
2132
|
+
feature(profile.light, nonZero(_optionalChain([brush, 'access', _58 => _58.light, 'optionalAccess', _59 => _59.amount])), "light", "light paint (glow and spill)", "Remove light (or set light.amount to 0).");
|
|
1921
2133
|
const m = brush.motion;
|
|
1922
2134
|
feature(profile.motion, !!m && (!!m.live || nonZero(m.waveSpeed) || m.fps !== void 0), "motion", "animation", "Remove motion.");
|
|
1923
2135
|
const d = _nullishCoalesce(brush.dynamics, () => ( {}));
|
|
2136
|
+
feature(_nullishCoalesce(profile.tipGrain, () => ( "none")), !!brush.tip.grain, "tip.grain", "tip grain", "Remove tip.grain (behaviour.grain textures with the paper instead).");
|
|
2137
|
+
feature(_nullishCoalesce(profile.dabCount, () => ( "none")), (_nullishCoalesce(d.count, () => ( 1))) > 1, "dynamics.count", "several dabs per step", "Set dynamics.count to 1.");
|
|
1924
2138
|
feature(profile.velocity, nonZero(d.velocitySize) || nonZero(d.velocityOpacity), "dynamics.velocity", "velocity dynamics (its strokes carry no timing)", "Set dynamics.velocitySize and velocityOpacity to 0.");
|
|
1925
2139
|
feature(profile.tilt, nonZero(d.tiltSize) || nonZero(d.tiltRoundness) || nonZero(d.tiltOpacity), "dynamics.tilt", "tilt dynamics", "Set dynamics.tiltSize, tiltRoundness and tiltOpacity to 0.");
|
|
1926
2140
|
const colour = brush.colour;
|
|
1927
|
-
if (_optionalChain([colour, 'optionalAccess',
|
|
2141
|
+
if (_optionalChain([colour, 'optionalAccess', _60 => _60.default]) !== void 0) {
|
|
1928
2142
|
const kind = brushColourKind(colour.default);
|
|
1929
2143
|
if (!profile.colours.includes(kind)) add("colour.default", "error", `${host} can't paint ${kind} colours.`, `Use a ${list(profile.colours)} colour, or remove colour.default.`);
|
|
1930
2144
|
}
|
|
1931
|
-
const foreign = [...new Set((_nullishCoalesce(_optionalChain([colour, 'optionalAccess',
|
|
2145
|
+
const foreign = [...new Set((_nullishCoalesce(_optionalChain([colour, 'optionalAccess', _61 => _61.palette]), () => ( []))).map(brushColourKind).filter((k) => !profile.colours.includes(k)))];
|
|
1932
2146
|
if (foreign.length) add("colour.palette", "warning", `${host} skips the palette's ${list(foreign)} colours.`, `Nothing to do; or keep to ${list(profile.colours)} colours.`);
|
|
1933
2147
|
return { ok: !issues.some((i) => i.severity === "error"), issues };
|
|
1934
2148
|
}
|
|
1935
2149
|
function withKeptIssues(check, before) {
|
|
1936
|
-
const had = new Set((_nullishCoalesce(_optionalChain([before, 'optionalAccess',
|
|
2150
|
+
const had = new Set((_nullishCoalesce(_optionalChain([before, 'optionalAccess', _62 => _62.issues]), () => ( []))).filter((i) => i.severity === "error").map((i) => i.facet));
|
|
1937
2151
|
const kept = check.issues.filter((i) => i.severity === "error" && had.has(i.facet));
|
|
1938
2152
|
const issues = check.issues.filter((i) => !kept.includes(i));
|
|
1939
2153
|
return { ok: !issues.some((i) => i.severity === "error"), issues, kept };
|
|
@@ -1976,6 +2190,12 @@ function degrade(brush, profile, options = {}) {
|
|
|
1976
2190
|
case "behaviour.grain":
|
|
1977
2191
|
delete b.behaviour.grain;
|
|
1978
2192
|
break;
|
|
2193
|
+
case "tip.grain":
|
|
2194
|
+
delete b.tip.grain;
|
|
2195
|
+
break;
|
|
2196
|
+
case "dynamics.count":
|
|
2197
|
+
delete b.dynamics.count;
|
|
2198
|
+
break;
|
|
1979
2199
|
case "light":
|
|
1980
2200
|
delete b.light;
|
|
1981
2201
|
break;
|
|
@@ -2003,7 +2223,7 @@ function degrade(brush, profile, options = {}) {
|
|
|
2003
2223
|
}
|
|
2004
2224
|
dropped.push(issue);
|
|
2005
2225
|
}
|
|
2006
|
-
if (_optionalChain([b, 'access',
|
|
2226
|
+
if (_optionalChain([b, 'access', _63 => _63.behaviour, 'optionalAccess', _64 => _64.exchange]) && !Object.keys(b.behaviour.exchange).length) delete b.behaviour.exchange;
|
|
2007
2227
|
if (b.colour && !Object.keys(b.colour).length) delete b.colour;
|
|
2008
2228
|
const after = checkBrush(b, profile).issues.filter((i) => i.severity === "error");
|
|
2009
2229
|
return { brush: b, dropped, issues: after };
|
|
@@ -2024,6 +2244,15 @@ function isBrushLibrary(library) {
|
|
|
2024
2244
|
}
|
|
2025
2245
|
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2026
2246
|
var isV1 = (item) => "engine" in item || "category" in item || !("kind" in item);
|
|
2247
|
+
function parseBrushLibrarySources(raw, problems = []) {
|
|
2248
|
+
const file = isObject2(raw) ? raw : null;
|
|
2249
|
+
if (!file || file.sources === void 0) return [];
|
|
2250
|
+
if (!Array.isArray(file.sources)) {
|
|
2251
|
+
problems.push("sources is not a list; skipped.");
|
|
2252
|
+
return [];
|
|
2253
|
+
}
|
|
2254
|
+
return file.sources.map((s, i) => readBrushSource(s, `sources[${i}]`, problems)).filter((s) => s !== null);
|
|
2255
|
+
}
|
|
2027
2256
|
function parseBrushLibrary2(raw, problems = []) {
|
|
2028
2257
|
if (typeof raw === "string") {
|
|
2029
2258
|
try {
|
|
@@ -2038,7 +2267,7 @@ function parseBrushLibrary2(raw, problems = []) {
|
|
|
2038
2267
|
if (file && file.version !== void 0 && file.version !== 1 && file.version !== 2) {
|
|
2039
2268
|
throw new BrushLibraryError("invalid", `Brush library version ${JSON.stringify(file.version)} is not supported; this library reads versions 1 and 2.`);
|
|
2040
2269
|
}
|
|
2041
|
-
const v1File = _optionalChain([file, 'optionalAccess',
|
|
2270
|
+
const v1File = _optionalChain([file, 'optionalAccess', _65 => _65.version]) === 1;
|
|
2042
2271
|
const out = [];
|
|
2043
2272
|
items.forEach((item, i) => {
|
|
2044
2273
|
const at = `brushes[${i}]`;
|
|
@@ -2066,8 +2295,8 @@ function matches(entry, f2) {
|
|
|
2066
2295
|
if (f2.tool && b.tool !== f2.tool) return false;
|
|
2067
2296
|
if (f2.tag && !(_nullishCoalesce(b.tags, () => ( []))).includes(f2.tag)) return false;
|
|
2068
2297
|
if (f2.text) {
|
|
2069
|
-
const
|
|
2070
|
-
if (!words(f2.text).every((w) =>
|
|
2298
|
+
const text2 = words([b.id, b.name, _nullishCoalesce(b.description, () => ( "")), _nullishCoalesce(b.variety, () => ( "")), _nullishCoalesce(b.medium, () => ( "")), _nullishCoalesce(b.tool, () => ( "")), ..._nullishCoalesce(b.tags, () => ( []))].join(" "));
|
|
2299
|
+
if (!words(f2.text).every((w) => text2.some((t) => t.startsWith(w)))) return false;
|
|
2071
2300
|
}
|
|
2072
2301
|
if (f2.profile && !checkBrush(b, f2.profile).ok) return false;
|
|
2073
2302
|
return true;
|
|
@@ -2077,8 +2306,10 @@ function createBrushLibrary(options = {}) {
|
|
|
2077
2306
|
const presets = (_nullishCoalesce(options.presets, () => ( BRUSH_PRESETS))).map(copy3);
|
|
2078
2307
|
const builtinIds = new Set(presets.map((b) => b.id));
|
|
2079
2308
|
const key = `${_nullishCoalesce(options.namespace, () => ( "pixerate.brushes"))}:user`;
|
|
2309
|
+
const sourcesKey = `${_nullishCoalesce(options.namespace, () => ( "pixerate.brushes"))}:sources`;
|
|
2080
2310
|
const listeners = /* @__PURE__ */ new Set();
|
|
2081
2311
|
let user = null;
|
|
2312
|
+
let sources = null;
|
|
2082
2313
|
let queue = Promise.resolve();
|
|
2083
2314
|
const run = (task) => {
|
|
2084
2315
|
const result = queue.then(task, task);
|
|
@@ -2104,6 +2335,44 @@ function createBrushLibrary(options = {}) {
|
|
|
2104
2335
|
if (options.storage) await options.storage.set(key, JSON.stringify(file));
|
|
2105
2336
|
user = brushes;
|
|
2106
2337
|
};
|
|
2338
|
+
const loadSources = async () => {
|
|
2339
|
+
if (sources) return sources;
|
|
2340
|
+
const raw = options.storage ? await options.storage.get(sourcesKey) : null;
|
|
2341
|
+
let list2 = [];
|
|
2342
|
+
if (raw) {
|
|
2343
|
+
try {
|
|
2344
|
+
list2 = parseBrushLibrarySources(JSON.parse(raw));
|
|
2345
|
+
} catch (e6) {
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
sources = list2;
|
|
2349
|
+
return sources;
|
|
2350
|
+
};
|
|
2351
|
+
const storeSources = async (list2) => {
|
|
2352
|
+
if (options.storage) await options.storage.set(sourcesKey, JSON.stringify({ format: "pixerate-brush-sources", version: 1, sources: list2 }));
|
|
2353
|
+
sources = list2;
|
|
2354
|
+
};
|
|
2355
|
+
const putSources = async (incoming, overwrite, problems) => {
|
|
2356
|
+
let list2 = [...await loadSources()];
|
|
2357
|
+
const changed = [];
|
|
2358
|
+
for (const src of incoming) {
|
|
2359
|
+
const at = list2.findIndex((x) => x.id === src.id);
|
|
2360
|
+
if (at === -1) {
|
|
2361
|
+
if (list2.length >= MAX_BRUSH_SOURCES) {
|
|
2362
|
+
problems.push(`Source "${src.id}" skipped: the library holds at most ${MAX_BRUSH_SOURCES} sources.`);
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
list2.push(src);
|
|
2366
|
+
changed.push(src.id);
|
|
2367
|
+
} else if (list2[at].image === src.image) continue;
|
|
2368
|
+
else if (overwrite) {
|
|
2369
|
+
list2 = list2.map((x, i) => i === at ? src : x);
|
|
2370
|
+
changed.push(src.id);
|
|
2371
|
+
} else problems.push(`Source "${src.id}" exists already with another image; skipped (pass overwrite to replace it).`);
|
|
2372
|
+
}
|
|
2373
|
+
if (changed.length) await storeSources(list2);
|
|
2374
|
+
return changed;
|
|
2375
|
+
};
|
|
2107
2376
|
const entries = async () => [...presets.map((brush) => ({ brush, builtin: true })), ...(await load()).map((brush) => ({ brush, builtin: false }))];
|
|
2108
2377
|
const find = async (id) => {
|
|
2109
2378
|
const entry = (await entries()).find((e) => e.brush.id === id);
|
|
@@ -2165,12 +2434,17 @@ function createBrushLibrary(options = {}) {
|
|
|
2165
2434
|
}),
|
|
2166
2435
|
exportFile: (ids) => run(async () => {
|
|
2167
2436
|
const brushes = ids ? await Promise.all(ids.map(async (id) => (await find(id)).brush)) : await load();
|
|
2168
|
-
|
|
2437
|
+
const used = new Set(brushes.flatMap(brushSourceIds));
|
|
2438
|
+
const withSources = (await loadSources()).filter((s) => used.has(s.id));
|
|
2439
|
+
return { format: "pixerate-brushes", version: 2, brushes: brushes.map(copy3), ...withSources.length ? { sources: withSources.map(copy3) } : {} };
|
|
2169
2440
|
}),
|
|
2170
2441
|
importFile: (raw, o = {}) => run(async () => {
|
|
2171
2442
|
const problems = [];
|
|
2172
2443
|
const incoming = parseBrushLibrary2(raw, problems);
|
|
2173
2444
|
const result = { added: [], replaced: [], skipped: [], problems };
|
|
2445
|
+
const fileSources = parseBrushLibrarySources(typeof raw === "string" ? JSON.parse(raw) : raw, problems);
|
|
2446
|
+
const changedSources = fileSources.length ? await putSources(fileSources, !!o.overwrite, problems) : [];
|
|
2447
|
+
if (changedSources.length) result.sources = changedSources;
|
|
2174
2448
|
let brushes = [...await load()];
|
|
2175
2449
|
for (const brush of incoming) {
|
|
2176
2450
|
if (builtinIds.has(brush.id)) {
|
|
@@ -2190,12 +2464,45 @@ function createBrushLibrary(options = {}) {
|
|
|
2190
2464
|
problems.push(`"${brush.id}" exists already; skipped (pass overwrite to replace it).`);
|
|
2191
2465
|
}
|
|
2192
2466
|
}
|
|
2193
|
-
if (result.added.length || result.replaced.length)
|
|
2194
|
-
|
|
2195
|
-
notify();
|
|
2196
|
-
}
|
|
2467
|
+
if (result.added.length || result.replaced.length) await store(brushes);
|
|
2468
|
+
if (result.added.length || result.replaced.length || changedSources.length) notify();
|
|
2197
2469
|
return result;
|
|
2198
2470
|
}),
|
|
2471
|
+
listSources: () => run(async () => (await loadSources()).map(copy3)),
|
|
2472
|
+
getSource: (id) => run(async () => {
|
|
2473
|
+
const s = (await loadSources()).find((x) => x.id === id);
|
|
2474
|
+
return s ? copy3(s) : null;
|
|
2475
|
+
}),
|
|
2476
|
+
addSource: (input, o = {}) => run(async () => {
|
|
2477
|
+
const problems = [];
|
|
2478
|
+
let source;
|
|
2479
|
+
if (isObject2(input) && "width" in input) {
|
|
2480
|
+
const read2 = readBrushSource(input, "source", problems);
|
|
2481
|
+
if (!read2) throw new BrushLibraryError("invalid", problems.join(" "));
|
|
2482
|
+
source = read2;
|
|
2483
|
+
} else {
|
|
2484
|
+
const given = input;
|
|
2485
|
+
const taken = new Set((await loadSources()).map((x) => x.id));
|
|
2486
|
+
let id = given.id;
|
|
2487
|
+
if (id === void 0 && typeof given.name === "string") {
|
|
2488
|
+
const stem = brushSourceIdFromName(given.name);
|
|
2489
|
+
id = stem;
|
|
2490
|
+
for (let n = 2; taken.has(id); n++) id = `${stem}-${n}`;
|
|
2491
|
+
}
|
|
2492
|
+
try {
|
|
2493
|
+
source = makeBrushSource({ ...given, id }, problems);
|
|
2494
|
+
} catch (e) {
|
|
2495
|
+
throw new BrushLibraryError("invalid", e.message);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
const list2 = await loadSources();
|
|
2499
|
+
const at = list2.findIndex((x) => x.id === source.id);
|
|
2500
|
+
if (at !== -1 && !o.overwrite) throw new BrushLibraryError("exists", `A source "${source.id}" exists already; pick another id, or pass overwrite to replace its image (brushes using it change too).`);
|
|
2501
|
+
if (at === -1 && list2.length >= MAX_BRUSH_SOURCES) throw new BrushLibraryError("invalid", `The library holds at most ${MAX_BRUSH_SOURCES} sources.`);
|
|
2502
|
+
await storeSources(at === -1 ? [...list2, source] : list2.map((x, i) => i === at ? source : x));
|
|
2503
|
+
notify();
|
|
2504
|
+
return { source: copy3(source), problems, replaced: at !== -1 };
|
|
2505
|
+
}),
|
|
2199
2506
|
subscribe(listener) {
|
|
2200
2507
|
listeners.add(listener);
|
|
2201
2508
|
return () => {
|
|
@@ -2221,7 +2528,7 @@ function snapshotBrushLibrary(library, onChange) {
|
|
|
2221
2528
|
(list2) => {
|
|
2222
2529
|
entries = list2;
|
|
2223
2530
|
user = list2.filter((e) => !e.builtin).map((e) => e.brush);
|
|
2224
|
-
_optionalChain([onChange, 'optionalCall',
|
|
2531
|
+
_optionalChain([onChange, 'optionalCall', _66 => _66()]);
|
|
2225
2532
|
},
|
|
2226
2533
|
() => void 0
|
|
2227
2534
|
);
|
|
@@ -2328,4 +2635,28 @@ function snapshotBrushLibrary(library, onChange) {
|
|
|
2328
2635
|
|
|
2329
2636
|
|
|
2330
2637
|
|
|
2331
|
-
|
|
2638
|
+
|
|
2639
|
+
|
|
2640
|
+
|
|
2641
|
+
|
|
2642
|
+
|
|
2643
|
+
|
|
2644
|
+
|
|
2645
|
+
|
|
2646
|
+
|
|
2647
|
+
|
|
2648
|
+
|
|
2649
|
+
|
|
2650
|
+
|
|
2651
|
+
|
|
2652
|
+
|
|
2653
|
+
|
|
2654
|
+
|
|
2655
|
+
|
|
2656
|
+
|
|
2657
|
+
|
|
2658
|
+
|
|
2659
|
+
|
|
2660
|
+
|
|
2661
|
+
|
|
2662
|
+
exports.BRUSH_PRESETS = BRUSH_PRESETS; exports.MAX_STICKER_STAMPS = MAX_STICKER_STAMPS; exports.DEFAULT_STICKER = DEFAULT_STICKER; exports.normalizeStickerSettings = normalizeStickerSettings; exports.PARTICLE_KINDS = PARTICLE_KINDS; exports.PARTICLE_RENDERS = PARTICLE_RENDERS; exports.RIBBON_PAIRS = RIBBON_PAIRS; exports.DOT_TEXTURE_ORDERS = DOT_TEXTURE_ORDERS; exports.DOT_ROTATIONS = DOT_ROTATIONS; exports.MAX_DOT_TEXTURES = MAX_DOT_TEXTURES; exports.DEFAULT_PARTICLE = DEFAULT_PARTICLE; exports.normalizeParticle = normalizeParticle; exports.DEFAULT_BRISTLE = DEFAULT_BRISTLE; exports.normalizeBristle = normalizeBristle; exports.PAINT_BRUSH_ENGINES = PAINT_BRUSH_ENGINES; exports.PAINT_BRUSH_CATEGORIES = PAINT_BRUSH_CATEGORIES; exports.WET_TOOLS = WET_TOOLS; exports.WET_MEDIA = WET_MEDIA; exports.DEFAULT_WET_BRUSH = DEFAULT_WET_BRUSH; exports.normalizeWetBrush = normalizeWetBrush; exports.IMPASTO_TOOLS = IMPASTO_TOOLS; exports.DEFAULT_IMPASTO = DEFAULT_IMPASTO; exports.DEFAULT_LIGHT = DEFAULT_LIGHT; exports.normalizeLight = normalizeLight; exports.normalizeImpasto = normalizeImpasto; exports.MULTI_COLOR_MODES = MULTI_COLOR_MODES; exports.DEFAULT_MULTI_COLOR = DEFAULT_MULTI_COLOR; exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR; exports.BRUSH_ROTATIONS = BRUSH_ROTATIONS; exports.DAB_FLIPS = DAB_FLIPS; exports.TIP_GRAIN_MODES = TIP_GRAIN_MODES; exports.DEFAULT_TIP_GRAIN = DEFAULT_TIP_GRAIN; exports.DEFAULT_DYNAMICS = DEFAULT_DYNAMICS; exports.normalizeDynamics = normalizeDynamics; exports.BRUSH_ID = BRUSH_ID; exports.normalizeMultiColor = normalizeMultiColor; exports.normalizeBehavior = normalizeBehavior; exports.DEFAULT_SPRAY = DEFAULT_SPRAY; exports.isWetCapable = isWetCapable; exports.isWetBrush = isWetBrush; exports.usesPaintExchange = usesPaintExchange; exports.brushTakesPaint = brushTakesPaint; exports.paintToMultiColor = paintToMultiColor; exports.multiColorToPaint = multiColorToPaint; exports.normalizePaintBrush = normalizePaintBrush; exports.BRUSH_FIELDS = BRUSH_FIELDS; exports.getPath = getPath; exports.setPath = setPath; exports.resolveEngine = resolveEngine; exports.resolveWetMedium = resolveWetMedium; exports.resolveCategory = resolveCategory; exports.resolveBrush = resolveBrush; exports.BUILTIN_BRUSHES = BUILTIN_BRUSHES; exports.DEFAULT_BRUSH_ID = DEFAULT_BRUSH_ID; exports.brushToJSON = brushToJSON; exports.parseBrushLibrary = parseBrushLibrary; exports.migrateBrushV1 = migrateBrushV1; exports.BRUSH_KINDS = BRUSH_KINDS; exports.BRUSH_MEDIA = BRUSH_MEDIA; exports.BRUSH_TOOLS = BRUSH_TOOLS; exports.BRUSH_TIP_SHAPES = BRUSH_TIP_SHAPES; exports.BRUSH_MARKS_KINDS = BRUSH_MARKS_KINDS; exports.BRUSH_SURFACES = BRUSH_SURFACES; exports.BRUSH_GRAIN_MODES = BRUSH_GRAIN_MODES; exports.BRUSH_FLIPS = BRUSH_FLIPS; exports.MAX_BRUSH_PALETTE = MAX_BRUSH_PALETTE; exports.BRUSH_WORD = BRUSH_WORD; exports.TIP_GRAIN_PATTERNS = TIP_GRAIN_PATTERNS; exports.BRUSH_GRAIN_SOURCE_PATTERN = BRUSH_GRAIN_SOURCE_PATTERN; exports.BRUSH_JSON_SCHEMA = BRUSH_JSON_SCHEMA; exports.BRUSH_FACET_SCHEMAS = BRUSH_FACET_SCHEMAS; exports.BRUSH_SOURCE_PROVIDER = BRUSH_SOURCE_PROVIDER; exports.MAX_BRUSH_SOURCE_SIZE = MAX_BRUSH_SOURCE_SIZE; exports.MAX_BRUSH_SOURCE_INPUT_PIXELS = MAX_BRUSH_SOURCE_INPUT_PIXELS; exports.MAX_BRUSH_SOURCES = MAX_BRUSH_SOURCES; exports.BRUSH_SOURCE_CHANNELS = BRUSH_SOURCE_CHANNELS; exports.BrushSourceError = BrushSourceError; exports.brushSourceRef = brushSourceRef; exports.isBrushSourceRef = isBrushSourceRef; exports.brushSourceIdOf = brushSourceIdOf; exports.brushSourceIds = brushSourceIds; exports.summarizeBrushSource = summarizeBrushSource; exports.brushSourceIdFromName = brushSourceIdFromName; exports.brushSourceFromMask = brushSourceFromMask; exports.makeBrushSource = makeBrushSource; exports.readBrushSource = readBrushSource; exports.decodeBrushSource = decodeBrushSource; exports.WET_BRUSH_TOOLS = WET_BRUSH_TOOLS; exports.normalizeBrushSpec = normalizeBrushSpec; exports.PAINT_PROFILE = PAINT_PROFILE; exports.IMAGE_PROFILE = IMAGE_PROFILE; exports.CANVAS_PROFILE = CANVAS_PROFILE; exports.VECTOR_PROFILE = VECTOR_PROFILE; exports.BRUSH_HOST_PROFILES = BRUSH_HOST_PROFILES; exports.brushColourKind = brushColourKind; exports.checkBrush = checkBrush; exports.withKeptIssues = withKeptIssues; exports.checkBrushEdit = checkBrushEdit; exports.listPresets = listPresets; exports.degrade = degrade; exports.BrushLibraryError = BrushLibraryError; exports.isBrushLibrary = isBrushLibrary; exports.parseBrushLibrarySources = parseBrushLibrarySources; exports.parseBrushLibrary2 = parseBrushLibrary2; exports.createBrushLibrary = createBrushLibrary; exports.closest = closest; exports.snapshotBrushLibrary = snapshotBrushLibrary;
|