@pixerate/editor 0.108.0 → 0.110.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/index.cjs +5 -5
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.js +9 -9
- package/dist/animation-editor/index.cjs +59 -59
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +2 -1
- package/dist/animation-editor/index.d.ts +2 -1
- package/dist/animation-editor/index.js +29 -29
- package/dist/blending-DXl06NSx.d.ts +486 -0
- package/dist/blending-aOE6Y3UE.d.cts +486 -0
- package/dist/canvas/index.cjs +27 -27
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.js +11 -11
- package/dist/{chunk-2FGRTYJN.cjs → chunk-477UVMNR.cjs} +3 -3
- package/dist/{chunk-2FGRTYJN.cjs.map → chunk-477UVMNR.cjs.map} +1 -1
- package/dist/chunk-7VOHOPLW.cjs +1136 -0
- package/dist/chunk-7VOHOPLW.cjs.map +1 -0
- package/dist/{chunk-HUQ7EHVI.js → chunk-BLWCAQCW.js} +723 -1975
- package/dist/chunk-BLWCAQCW.js.map +1 -0
- package/dist/{chunk-SOROZ2BF.js → chunk-DMRSJBJF.js} +1939 -318
- package/dist/chunk-DMRSJBJF.js.map +1 -0
- package/dist/chunk-FTBZPI77.cjs +1 -0
- package/dist/chunk-FTBZPI77.cjs.map +1 -0
- package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
- package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
- package/dist/chunk-JLYX4V57.cjs +2001 -0
- package/dist/chunk-JLYX4V57.cjs.map +1 -0
- package/dist/chunk-KB2YBCVS.js +2001 -0
- package/dist/chunk-KB2YBCVS.js.map +1 -0
- package/dist/{chunk-G4BIYE6R.js → chunk-LQEEDHOP.js} +40 -2
- package/dist/chunk-LQEEDHOP.js.map +1 -0
- package/dist/{chunk-FRD3FUXI.cjs → chunk-MGL4U5WA.cjs} +2144 -523
- package/dist/chunk-MGL4U5WA.cjs.map +1 -0
- package/dist/{chunk-T3QZ6S47.cjs → chunk-POGBUYWE.cjs} +131 -93
- package/dist/chunk-POGBUYWE.cjs.map +1 -0
- package/dist/chunk-VKK5VS2T.js +1 -0
- package/dist/{chunk-UTEVPQTS.cjs → chunk-W3GXSUSQ.cjs} +746 -1998
- package/dist/chunk-W3GXSUSQ.cjs.map +1 -0
- package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
- package/dist/chunk-XIOMMGPD.cjs.map +1 -0
- package/dist/{chunk-JK6M6V7G.js → chunk-Z3YWVUWT.js} +4 -4
- package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
- package/dist/chunk-Z4BC6M74.js.map +1 -0
- package/dist/{export-a9bnF7Cl.d.ts → export-CO2ryu3I.d.ts} +419 -18
- package/dist/{export-JGWQEPJn.d.cts → export-DRiLzP_j.d.cts} +419 -18
- package/dist/image-editor/index.cjs +1665 -1113
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +78 -14
- package/dist/image-editor/index.d.ts +78 -14
- package/dist/image-editor/index.js +843 -291
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.cjs +28 -4
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +3 -89
- package/dist/paint/index.d.ts +3 -89
- package/dist/paint/index.js +27 -3
- package/dist/paint-editor/index.cjs +1056 -287
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +304 -22
- package/dist/paint-editor/index.d.ts +304 -22
- package/dist/paint-editor/index.js +852 -83
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +7 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +3 -129
- package/dist/raster/index.d.ts +3 -129
- package/dist/raster/index.js +6 -2
- package/dist/{compositor-D92URMlm.d.cts → render-BEriFuqK.d.cts} +136 -2
- package/dist/{compositor-Kt_M4ruo.d.ts → render-tjzKaik-.d.ts} +136 -2
- package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
- package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -1
- package/dist/{svg-SseJTIrA.d.ts → svg-D_cfkhlB.d.cts} +3 -629
- package/dist/{svg-ClVIG4T7.d.cts → svg-wIaKLrU0.d.ts} +3 -629
- package/dist/types-DtL_qLFz.d.ts +630 -0
- package/dist/types-DyQq-WPY.d.cts +630 -0
- package/dist/vector/index.cjs +563 -563
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +4 -2
- package/dist/vector/index.d.ts +4 -2
- package/dist/vector/index.js +69 -69
- package/dist/video-editor/index.cjs +3 -3
- package/dist/video-editor/index.js +2 -2
- package/package.json +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +49 -13
- package/dist/chunk-3PHA3UKI.js.map +0 -1
- package/dist/chunk-6BU5WU4R.cjs +0 -1
- package/dist/chunk-6BU5WU4R.cjs.map +0 -1
- package/dist/chunk-DWGZUSES.cjs +0 -694
- package/dist/chunk-DWGZUSES.cjs.map +0 -1
- package/dist/chunk-FRD3FUXI.cjs.map +0 -1
- package/dist/chunk-G4BIYE6R.js.map +0 -1
- package/dist/chunk-HUFQD4CB.cjs +0 -734
- package/dist/chunk-HUFQD4CB.cjs.map +0 -1
- package/dist/chunk-HUQ7EHVI.js.map +0 -1
- package/dist/chunk-KKREERP2.js +0 -694
- package/dist/chunk-KKREERP2.js.map +0 -1
- package/dist/chunk-SOROZ2BF.js.map +0 -1
- package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
- package/dist/chunk-T3QZ6S47.cjs.map +0 -1
- package/dist/chunk-UTEVPQTS.cjs.map +0 -1
- package/dist/chunk-XYBJKUAU.js +0 -1
- package/dist/mixer-5ziHU8OS.d.cts +0 -282
- package/dist/mixer-DNqiYXYQ.d.ts +0 -282
- /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
- /package/dist/{chunk-JK6M6V7G.js.map → chunk-Z3YWVUWT.js.map} +0 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PIGMENT_BANDS,
|
|
2
3
|
TILE,
|
|
3
4
|
blendPixel,
|
|
4
5
|
createPigment,
|
|
5
6
|
lerpPigmentInto,
|
|
6
7
|
mixPigment8,
|
|
7
8
|
mixPigments,
|
|
9
|
+
pigment8,
|
|
8
10
|
setPigmentColor,
|
|
9
11
|
tileCoord,
|
|
10
12
|
tileKey
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-HNS7UHCM.js";
|
|
12
14
|
|
|
13
15
|
// src/paint/brush.ts
|
|
14
16
|
var DEFAULT_BRUSH = {
|
|
@@ -412,83 +414,51 @@ function coverageTileCoord(key) {
|
|
|
412
414
|
return tileCoord(key);
|
|
413
415
|
}
|
|
414
416
|
|
|
415
|
-
// src/paint/
|
|
417
|
+
// src/paint/exchange.ts
|
|
416
418
|
var MIXING_MODELS = ["rgb", "pigment"];
|
|
417
|
-
var
|
|
418
|
-
wet: 0.5,
|
|
419
|
-
load: 0.5,
|
|
420
|
-
mix: 0.5,
|
|
421
|
-
loadEachStroke: true,
|
|
422
|
-
cleanEachStroke: true,
|
|
423
|
-
mixing: "rgb"
|
|
424
|
-
};
|
|
419
|
+
var EMPTY_MIXER_LOAD = { reservoir: null, amount: 0, pickup: null };
|
|
425
420
|
var MIXER_PICKUP_RATE = 0.25;
|
|
426
421
|
var MIXER_DRAIN = 0.08;
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
const bool = (v, d) => typeof v === "boolean" ? v : d;
|
|
430
|
-
return {
|
|
431
|
-
wet: unit(raw.wet, DEFAULT_MIXER.wet),
|
|
432
|
-
load: unit(raw.load, DEFAULT_MIXER.load),
|
|
433
|
-
mix: unit(raw.mix, DEFAULT_MIXER.mix),
|
|
434
|
-
loadEachStroke: bool(raw.loadEachStroke, DEFAULT_MIXER.loadEachStroke),
|
|
435
|
-
cleanEachStroke: bool(raw.cleanEachStroke, DEFAULT_MIXER.cleanEachStroke),
|
|
436
|
-
mixing: raw.mixing === "pigment" ? "pigment" : "rgb"
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
var EMPTY_MIXER_LOAD = { reservoir: null, amount: 0, pickup: null };
|
|
440
|
-
function loadMixer(load, color) {
|
|
441
|
-
const c = (v) => Math.min(1, Math.max(0, Number.isFinite(v) ? v : 0));
|
|
442
|
-
return { reservoir: [c(color[0]), c(color[1]), c(color[2])], amount: 1, pickup: load.pickup };
|
|
422
|
+
function exchangeRates(pickup, mix, load, mixing) {
|
|
423
|
+
return { pickup: MIXER_PICKUP_RATE * pickup, mix: mix * pickup, drain: MIXER_DRAIN * (1 - load), mixing };
|
|
443
424
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
var MixerStroke = class {
|
|
448
|
-
constructor(brush, settings, load, target) {
|
|
449
|
-
this.brush = brush;
|
|
450
|
-
this.target = target;
|
|
451
|
-
this.settings = normalizeMixer(settings);
|
|
452
|
-
this.walker = new DabWalker(brush);
|
|
425
|
+
var PaintExchange = class {
|
|
426
|
+
constructor(rates, load) {
|
|
427
|
+
this.rates = rates;
|
|
453
428
|
this.reservoir = load.reservoir ? [load.reservoir[0], load.reservoir[1], load.reservoir[2]] : null;
|
|
454
429
|
this.amount = this.reservoir ? Math.min(1, Math.max(0, load.amount)) : 0;
|
|
455
|
-
this.
|
|
456
|
-
this.pig =
|
|
430
|
+
this.well = load.pickup ? [load.pickup[0], load.pickup[1], load.pickup[2]] : null;
|
|
431
|
+
this.pig = rates.mixing === "pigment" ? { res: createPigment(this.reservoir ?? [0, 0, 0]), well: createPigment(this.well ?? [0, 0, 0]), paint: createPigment([0, 0, 0]), canvas: createPigment([0, 0, 0]) } : null;
|
|
457
432
|
}
|
|
458
|
-
|
|
459
|
-
target;
|
|
460
|
-
dabCount = 0;
|
|
461
|
-
bounds = null;
|
|
462
|
-
walker;
|
|
463
|
-
smoothPos = null;
|
|
464
|
-
buf = new Float32Array(0);
|
|
465
|
-
settings;
|
|
433
|
+
rates;
|
|
466
434
|
reservoir;
|
|
467
435
|
amount;
|
|
468
|
-
|
|
436
|
+
well;
|
|
469
437
|
pig;
|
|
470
438
|
out = [0, 0, 0];
|
|
471
|
-
/**
|
|
439
|
+
/** What the brush carries now. */
|
|
472
440
|
get load() {
|
|
473
|
-
return { reservoir: this.reservoir ? [...this.reservoir] : null, amount: this.amount, pickup: this.
|
|
441
|
+
return { reservoir: this.reservoir ? [...this.reservoir] : null, amount: this.amount, pickup: this.well ? [...this.well] : null };
|
|
474
442
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
return this.paint(dabs);
|
|
443
|
+
/** The colour picked up so far, or `null`. */
|
|
444
|
+
get pickup() {
|
|
445
|
+
return this.well;
|
|
479
446
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
this.walker.finish(dabs);
|
|
484
|
-
return this.paint(dabs);
|
|
447
|
+
/** The prepared pigment of the last `paint` colour (pigment mixing only), for `deposit`. */
|
|
448
|
+
get paintPigment() {
|
|
449
|
+
return this.pig?.paint ?? null;
|
|
485
450
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
451
|
+
/** Recolours the reservoir without refilling it (a multi-coloured brush, per dab). */
|
|
452
|
+
setReservoirColor(r, g, b) {
|
|
453
|
+
if (!this.reservoir) {
|
|
454
|
+
this.reservoir = [r, g, b];
|
|
455
|
+
this.amount = 1;
|
|
456
|
+
} else {
|
|
457
|
+
this.reservoir[0] = r;
|
|
458
|
+
this.reservoir[1] = g;
|
|
459
|
+
this.reservoir[2] = b;
|
|
460
|
+
}
|
|
461
|
+
if (this.pig) setPigmentColor(this.pig.res, r, g, b);
|
|
492
462
|
}
|
|
493
463
|
/** `a` mixed `t` of the way to `b` into `this.out`, by the mixing model. */
|
|
494
464
|
mix(a, pa, b, pb, t) {
|
|
@@ -497,27 +467,84 @@ var MixerStroke = class {
|
|
|
497
467
|
else for (let c = 0; c < 3; c++) o[c] = a[c] + (b[c] - a[c]) * t;
|
|
498
468
|
return o;
|
|
499
469
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
470
|
+
/** `a` mixed `t` of the way to `b` (sRGB 0..1) by the mixing model, into `out`. */
|
|
471
|
+
mixColors(a, b, t, out) {
|
|
472
|
+
if (this.pig) {
|
|
473
|
+
setPigmentColor(this.pig.canvas, a[0], a[1], a[2]);
|
|
474
|
+
setPigmentColor(this.pig.paint, b[0], b[1], b[2]);
|
|
475
|
+
lerpPigmentInto(this.pig.canvas, this.pig.paint, t, out, 0);
|
|
476
|
+
} else for (let c = 0; c < 3; c++) out[c] = a[c] + (b[c] - a[c]) * t;
|
|
477
|
+
return out;
|
|
478
|
+
}
|
|
479
|
+
/** 1. Pick-up: the well moves towards the sampled colour (or takes it, when clean). */
|
|
480
|
+
pickUp(sample) {
|
|
481
|
+
if (!sample || !(this.rates.pickup > 0)) return;
|
|
482
|
+
const pig = this.pig;
|
|
483
|
+
if (!this.well) this.well = [sample.rgb[0], sample.rgb[1], sample.rgb[2]];
|
|
484
|
+
else {
|
|
485
|
+
if (pig) setPigmentColor(pig.canvas, sample.rgb[0], sample.rgb[1], sample.rgb[2]);
|
|
486
|
+
const o = this.mix(this.well, pig?.well ?? null, sample.rgb, pig?.canvas ?? null, this.rates.pickup * sample.contact);
|
|
487
|
+
this.well = [o[0], o[1], o[2]];
|
|
512
488
|
}
|
|
513
|
-
|
|
489
|
+
if (pig) setPigmentColor(pig.well, this.well[0], this.well[1], this.well[2]);
|
|
514
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* 2. The dab's paint: the reservoir mixed with the well, written to `out`. Returns the total
|
|
493
|
+
* weight (≤ 0: nothing to lay down); deposit `min(1, weight)` of it. With pigment mixing,
|
|
494
|
+
* `paintPigment` is the result's pigment afterwards.
|
|
495
|
+
*/
|
|
496
|
+
paint(out) {
|
|
497
|
+
const res = this.reservoir;
|
|
498
|
+
const wr = res ? this.amount * (1 - this.rates.mix) : 0;
|
|
499
|
+
const wp = this.well ? this.rates.mix : 0;
|
|
500
|
+
const total = wr + wp;
|
|
501
|
+
if (!(total > 1e-6)) return 0;
|
|
502
|
+
const pig = this.pig;
|
|
503
|
+
if (res && this.well) {
|
|
504
|
+
const m = this.mix(res, pig?.res ?? null, this.well, pig?.well ?? null, wp / total);
|
|
505
|
+
out[0] = m[0];
|
|
506
|
+
out[1] = m[1];
|
|
507
|
+
out[2] = m[2];
|
|
508
|
+
} else {
|
|
509
|
+
const c = res ?? this.well;
|
|
510
|
+
out[0] = c[0];
|
|
511
|
+
out[1] = c[1];
|
|
512
|
+
out[2] = c[2];
|
|
513
|
+
}
|
|
514
|
+
if (pig) setPigmentColor(pig.paint, out[0], out[1], out[2]);
|
|
515
|
+
return total;
|
|
516
|
+
}
|
|
517
|
+
/** 3. The reservoir drains. */
|
|
518
|
+
drain() {
|
|
519
|
+
this.amount *= 1 - this.rates.drain;
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
var TileMedium = class {
|
|
523
|
+
constructor(target, options = {}) {
|
|
524
|
+
this.target = target;
|
|
525
|
+
this.options = options;
|
|
526
|
+
}
|
|
527
|
+
target;
|
|
528
|
+
options;
|
|
515
529
|
// The tile last looked up by `locate` (and whether it came from `writeTile`).
|
|
516
530
|
tx = NaN;
|
|
517
531
|
ty = NaN;
|
|
518
532
|
tile = null;
|
|
519
533
|
writable = false;
|
|
520
|
-
|
|
534
|
+
canvasPig = createPigment([0, 0, 0]);
|
|
535
|
+
paintPig = createPigment([0, 0, 0]);
|
|
536
|
+
paintKey = -1;
|
|
537
|
+
cb = [0, 0, 0, 0];
|
|
538
|
+
src = [0, 0, 0, 1];
|
|
539
|
+
o = [0, 0, 0];
|
|
540
|
+
/** Forgets the cached tile (call when another writer may have replaced tiles). */
|
|
541
|
+
reset() {
|
|
542
|
+
this.tx = NaN;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* The tile holding document pixel (dx, dy) (`pixels`) and the pixel's byte index in it, or -1
|
|
546
|
+
* when reading an empty tile.
|
|
547
|
+
*/
|
|
521
548
|
locate(dx, dy, write) {
|
|
522
549
|
const t = this.target;
|
|
523
550
|
const lx = dx - t.offset.x;
|
|
@@ -532,9 +559,18 @@ var MixerStroke = class {
|
|
|
532
559
|
}
|
|
533
560
|
return this.tile ? ((ly - ty * TILE) * TILE + (lx - tx * TILE)) * 4 : -1;
|
|
534
561
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
562
|
+
/** The tile `locate` last found. */
|
|
563
|
+
get pixels() {
|
|
564
|
+
return this.tile;
|
|
565
|
+
}
|
|
566
|
+
/** Selection coverage at a document pixel, 0..1 (1 without a selection). */
|
|
567
|
+
selectionAt(dx, dy) {
|
|
568
|
+
const sel = this.target.selection;
|
|
569
|
+
if (!sel) return 1;
|
|
570
|
+
if (dx < 0 || dy < 0 || dx >= sel.width || dy >= sel.height) return 0;
|
|
571
|
+
return sel.data[dy * sel.width + dx] / 255;
|
|
572
|
+
}
|
|
573
|
+
sample(rect, cov) {
|
|
538
574
|
const w = rect.width;
|
|
539
575
|
this.tx = NaN;
|
|
540
576
|
let ar = 0;
|
|
@@ -559,40 +595,24 @@ var MixerStroke = class {
|
|
|
559
595
|
asum += ka;
|
|
560
596
|
}
|
|
561
597
|
}
|
|
562
|
-
if (asum > 1e-6
|
|
563
|
-
|
|
564
|
-
if (!this.pickup) this.pickup = canvas;
|
|
565
|
-
else {
|
|
566
|
-
if (pig) setPigmentColor(pig.canvas, canvas[0], canvas[1], canvas[2]);
|
|
567
|
-
const o = this.mix(this.pickup, pig?.pick ?? null, canvas, pig?.canvas ?? null, MIXER_PICKUP_RATE * wet * (asum / Math.max(1e-6, wsum)));
|
|
568
|
-
this.pickup = [o[0], o[1], o[2]];
|
|
569
|
-
}
|
|
570
|
-
if (pig) setPigmentColor(pig.pick, this.pickup[0], this.pickup[1], this.pickup[2]);
|
|
571
|
-
}
|
|
572
|
-
const res = this.reservoir;
|
|
573
|
-
const wr = res ? this.amount * (1 - mix * wet) : 0;
|
|
574
|
-
const wp = this.pickup ? mix * wet : 0;
|
|
575
|
-
const total = wr + wp;
|
|
576
|
-
if (total > 1e-6) {
|
|
577
|
-
const paint = res && this.pickup ? [...this.mix(res, pig?.res ?? null, this.pickup, pig?.pick ?? null, wp / total)] : [...res ?? this.pickup];
|
|
578
|
-
if (pig) setPigmentColor(pig.paint, paint[0], paint[1], paint[2]);
|
|
579
|
-
const dep = Math.min(1, total) * this.brush.flow * this.brush.opacity * d.opacity;
|
|
580
|
-
if (dep > 0) this.deposit(rect, cov, paint, dep);
|
|
581
|
-
}
|
|
582
|
-
this.amount *= 1 - MIXER_DRAIN * (1 - load);
|
|
598
|
+
if (!(asum > 1e-6)) return null;
|
|
599
|
+
return { rgb: [ar / asum, ag / asum, ab / asum], contact: asum / Math.max(1e-6, wsum) };
|
|
583
600
|
}
|
|
584
|
-
|
|
585
|
-
deposit(rect, cov, paint, dep) {
|
|
601
|
+
deposit(rect, cov, dep, paint, paintPigment, field = null) {
|
|
586
602
|
const t = this.target;
|
|
587
603
|
const sel = t.selection;
|
|
588
|
-
const pig = this.pig;
|
|
589
604
|
const w = rect.width;
|
|
590
|
-
const o = this.
|
|
605
|
+
const o = this.o;
|
|
606
|
+
const blend = this.options.blend;
|
|
607
|
+
const pigment = blend === void 0 ? paintPigment !== null : blend === "pigment";
|
|
608
|
+
const general = blend !== void 0 && blend !== "normal" && blend !== "pigment";
|
|
591
609
|
let lastKey = -1;
|
|
610
|
+
this.tx = NaN;
|
|
592
611
|
for (let yy = 0; yy < rect.height; yy++) {
|
|
593
612
|
const dy = rect.y + yy;
|
|
594
613
|
for (let xx = 0; xx < w; xx++) {
|
|
595
|
-
|
|
614
|
+
const p = yy * w + xx;
|
|
615
|
+
let k = cov[p] * dep;
|
|
596
616
|
if (k <= 0) continue;
|
|
597
617
|
const dx = rect.x + xx;
|
|
598
618
|
if (sel) {
|
|
@@ -607,22 +627,52 @@ var MixerStroke = class {
|
|
|
607
627
|
}
|
|
608
628
|
const i = this.locate(dx, dy, true);
|
|
609
629
|
const px = this.tile;
|
|
630
|
+
const pr = field ? field[p * 3] : paint[0];
|
|
631
|
+
const pg = field ? field[p * 3 + 1] : paint[1];
|
|
632
|
+
const pb = field ? field[p * 3 + 2] : paint[2];
|
|
633
|
+
if (general) {
|
|
634
|
+
const cb = this.cb;
|
|
635
|
+
for (let c = 0; c < 4; c++) cb[c] = px[i + c] / 255;
|
|
636
|
+
const src = this.src;
|
|
637
|
+
src[0] = pr;
|
|
638
|
+
src[1] = pg;
|
|
639
|
+
src[2] = pb;
|
|
640
|
+
const r = blendPixel(blend, cb, src, k);
|
|
641
|
+
px[i] = Math.round(r[0] * 255);
|
|
642
|
+
px[i + 1] = Math.round(r[1] * 255);
|
|
643
|
+
px[i + 2] = Math.round(r[2] * 255);
|
|
644
|
+
if (!t.lockTransparency) px[i + 3] = Math.round(r[3] * 255);
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
610
647
|
const a = px[i + 3] / 255;
|
|
611
648
|
const na = t.lockTransparency ? a : a + (1 - a) * k;
|
|
612
649
|
const share = t.lockTransparency ? k : na > 0 ? k / na : 1;
|
|
613
650
|
if (a <= 0 || share >= 1) {
|
|
614
|
-
o[0] =
|
|
615
|
-
o[1] =
|
|
616
|
-
o[2] =
|
|
617
|
-
} else if (
|
|
651
|
+
o[0] = pr;
|
|
652
|
+
o[1] = pg;
|
|
653
|
+
o[2] = pb;
|
|
654
|
+
} else if (pigment && field) {
|
|
655
|
+
mixPigment8(px[i], px[i + 1], px[i + 2], Math.round(pr * 255), Math.round(pg * 255), Math.round(pb * 255), share, o);
|
|
656
|
+
} else if (pigment) {
|
|
618
657
|
const key = px[i] << 16 | px[i + 1] << 8 | px[i + 2];
|
|
619
658
|
if (key !== lastKey) {
|
|
620
|
-
setPigmentColor(
|
|
659
|
+
setPigmentColor(this.canvasPig, px[i] / 255, px[i + 1] / 255, px[i + 2] / 255);
|
|
621
660
|
lastKey = key;
|
|
622
661
|
}
|
|
623
|
-
|
|
662
|
+
let pp = paintPigment;
|
|
663
|
+
if (!pp) {
|
|
664
|
+
const pkey = Math.round(pr * 255) << 16 | Math.round(pg * 255) << 8 | Math.round(pb * 255);
|
|
665
|
+
if (pkey !== this.paintKey) {
|
|
666
|
+
setPigmentColor(this.paintPig, pr, pg, pb);
|
|
667
|
+
this.paintKey = pkey;
|
|
668
|
+
}
|
|
669
|
+
pp = this.paintPig;
|
|
670
|
+
}
|
|
671
|
+
lerpPigmentInto(this.canvasPig, pp, share, o, 0);
|
|
624
672
|
} else {
|
|
625
|
-
|
|
673
|
+
o[0] = px[i] / 255 + (pr - px[i] / 255) * share;
|
|
674
|
+
o[1] = px[i + 1] / 255 + (pg - px[i + 1] / 255) * share;
|
|
675
|
+
o[2] = px[i + 2] / 255 + (pb - px[i + 2] / 255) * share;
|
|
626
676
|
}
|
|
627
677
|
px[i] = Math.round(o[0] * 255);
|
|
628
678
|
px[i + 1] = Math.round(o[1] * 255);
|
|
@@ -632,6 +682,102 @@ var MixerStroke = class {
|
|
|
632
682
|
}
|
|
633
683
|
}
|
|
634
684
|
};
|
|
685
|
+
|
|
686
|
+
// src/paint/mixer.ts
|
|
687
|
+
var DEFAULT_MIXER = {
|
|
688
|
+
wet: 0.5,
|
|
689
|
+
load: 0.5,
|
|
690
|
+
mix: 0.5,
|
|
691
|
+
loadEachStroke: true,
|
|
692
|
+
cleanEachStroke: true,
|
|
693
|
+
mixing: "rgb"
|
|
694
|
+
};
|
|
695
|
+
function normalizeMixer(raw = {}) {
|
|
696
|
+
const unit2 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
|
|
697
|
+
const bool = (v, d) => typeof v === "boolean" ? v : d;
|
|
698
|
+
return {
|
|
699
|
+
wet: unit2(raw.wet, DEFAULT_MIXER.wet),
|
|
700
|
+
load: unit2(raw.load, DEFAULT_MIXER.load),
|
|
701
|
+
mix: unit2(raw.mix, DEFAULT_MIXER.mix),
|
|
702
|
+
loadEachStroke: bool(raw.loadEachStroke, DEFAULT_MIXER.loadEachStroke),
|
|
703
|
+
cleanEachStroke: bool(raw.cleanEachStroke, DEFAULT_MIXER.cleanEachStroke),
|
|
704
|
+
mixing: raw.mixing === "pigment" ? "pigment" : "rgb"
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function mixerRates(settings) {
|
|
708
|
+
return exchangeRates(settings.wet, settings.mix, settings.load, settings.mixing);
|
|
709
|
+
}
|
|
710
|
+
function loadMixer(load, color) {
|
|
711
|
+
const c = (v) => Math.min(1, Math.max(0, Number.isFinite(v) ? v : 0));
|
|
712
|
+
return { reservoir: [c(color[0]), c(color[1]), c(color[2])], amount: 1, pickup: load.pickup };
|
|
713
|
+
}
|
|
714
|
+
function cleanMixer(load) {
|
|
715
|
+
return { reservoir: load.reservoir, amount: load.amount, pickup: null };
|
|
716
|
+
}
|
|
717
|
+
var MixerStroke = class {
|
|
718
|
+
constructor(brush, settings, load, target) {
|
|
719
|
+
this.brush = brush;
|
|
720
|
+
this.walker = new DabWalker(brush);
|
|
721
|
+
this.exchange = new PaintExchange(mixerRates(normalizeMixer(settings)), load);
|
|
722
|
+
this.medium = new TileMedium(target);
|
|
723
|
+
}
|
|
724
|
+
brush;
|
|
725
|
+
dabCount = 0;
|
|
726
|
+
bounds = null;
|
|
727
|
+
walker;
|
|
728
|
+
smoothPos = null;
|
|
729
|
+
buf = new Float32Array(0);
|
|
730
|
+
exchange;
|
|
731
|
+
medium;
|
|
732
|
+
paintColor = [0, 0, 0];
|
|
733
|
+
/** The brush load after the dabs so far. */
|
|
734
|
+
get load() {
|
|
735
|
+
return this.exchange.load;
|
|
736
|
+
}
|
|
737
|
+
push(points) {
|
|
738
|
+
const dabs = [];
|
|
739
|
+
for (const p of points) this.walker.push(this.smooth(p), dabs);
|
|
740
|
+
return this.paint(dabs);
|
|
741
|
+
}
|
|
742
|
+
finish(last) {
|
|
743
|
+
const dabs = [];
|
|
744
|
+
if (last && this.smoothPos && (this.smoothPos.x !== last.x || this.smoothPos.y !== last.y)) this.walker.push(last, dabs);
|
|
745
|
+
this.walker.finish(dabs);
|
|
746
|
+
return this.paint(dabs);
|
|
747
|
+
}
|
|
748
|
+
smooth(p) {
|
|
749
|
+
const a = 1 - Math.min(0.95, Math.max(0, this.brush.smoothing));
|
|
750
|
+
const cur = this.smoothPos;
|
|
751
|
+
const n = cur && a < 1 ? { ...p, x: cur.x + (p.x - cur.x) * a, y: cur.y + (p.y - cur.y) * a } : p;
|
|
752
|
+
this.smoothPos = n;
|
|
753
|
+
return n;
|
|
754
|
+
}
|
|
755
|
+
paint(dabs) {
|
|
756
|
+
const rects = [];
|
|
757
|
+
for (const d of dabs) {
|
|
758
|
+
const rect = dabRect(d, this.brush.hardness);
|
|
759
|
+
const n = rect.width * rect.height;
|
|
760
|
+
if (this.buf.length < n) this.buf = new Float32Array(n);
|
|
761
|
+
const cov = this.buf.subarray(0, n);
|
|
762
|
+
rasterizeDab({ ...d, alpha: 1 }, this.brush.hardness, rect, cov);
|
|
763
|
+
this.dab(d, rect, cov);
|
|
764
|
+
rects.push(rect);
|
|
765
|
+
this.bounds = union(this.bounds, rect);
|
|
766
|
+
this.dabCount++;
|
|
767
|
+
}
|
|
768
|
+
return rects;
|
|
769
|
+
}
|
|
770
|
+
dab(d, rect, cov) {
|
|
771
|
+
const ex = this.exchange;
|
|
772
|
+
ex.pickUp(this.medium.sample(rect, cov));
|
|
773
|
+
const total = ex.paint(this.paintColor);
|
|
774
|
+
if (total > 0) {
|
|
775
|
+
const dep = Math.min(1, total) * this.brush.flow * this.brush.opacity * d.opacity;
|
|
776
|
+
if (dep > 0) this.medium.deposit(rect, cov, dep, this.paintColor, ex.paintPigment);
|
|
777
|
+
}
|
|
778
|
+
ex.drain();
|
|
779
|
+
}
|
|
780
|
+
};
|
|
635
781
|
function union(a, b) {
|
|
636
782
|
if (!a) return { ...b };
|
|
637
783
|
const x = Math.min(a.x, b.x);
|
|
@@ -639,6 +785,243 @@ function union(a, b) {
|
|
|
639
785
|
return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
|
|
640
786
|
}
|
|
641
787
|
|
|
788
|
+
// src/paint/blending.ts
|
|
789
|
+
var DEFAULT_BLENDER = { strength: 0.5, distance: 0.35, mixing: "pigment" };
|
|
790
|
+
var DEFAULT_SMUDGE = { strength: 0.7, length: 0.85, mixing: "rgb" };
|
|
791
|
+
var unit = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
|
|
792
|
+
function normalizeBlender(raw = {}, base = DEFAULT_BLENDER) {
|
|
793
|
+
return { strength: unit(raw.strength, base.strength), distance: unit(raw.distance, base.distance), mixing: raw.mixing === "rgb" || raw.mixing === "pigment" ? raw.mixing : base.mixing };
|
|
794
|
+
}
|
|
795
|
+
function normalizeSmudge(raw = {}, base = DEFAULT_SMUDGE) {
|
|
796
|
+
return { strength: unit(raw.strength, base.strength), length: unit(raw.length, base.length), mixing: raw.mixing === "rgb" || raw.mixing === "pigment" ? raw.mixing : base.mixing };
|
|
797
|
+
}
|
|
798
|
+
function blendRadius(settings, radius) {
|
|
799
|
+
return Math.max(1, Math.round(settings.distance * 2 * radius));
|
|
800
|
+
}
|
|
801
|
+
function createBlenderScratch() {
|
|
802
|
+
return { sums: new Float64Array(0), mix: { rgb: [0, 0, 0], strength: 1, ks: new Float32Array(PIGMENT_BANDS), scatter: 1 }, out: [0, 0, 0] };
|
|
803
|
+
}
|
|
804
|
+
var N = PIGMENT_BANDS;
|
|
805
|
+
function blenderDab(medium, rect, cov, opacity, radius, settings, scratch) {
|
|
806
|
+
const R = blendRadius(settings, radius);
|
|
807
|
+
const W = rect.width + 2 * R;
|
|
808
|
+
const H = rect.height + 2 * R;
|
|
809
|
+
const pigment = settings.mixing === "pigment";
|
|
810
|
+
const C = pigment ? N + 2 : 4;
|
|
811
|
+
const stride = (W + 1) * C;
|
|
812
|
+
const size = stride * (H + 1);
|
|
813
|
+
if (scratch.sums.length < size) scratch.sums = new Float64Array(size);
|
|
814
|
+
const I = scratch.sums;
|
|
815
|
+
I.fill(0, 0, size);
|
|
816
|
+
medium.reset();
|
|
817
|
+
const row = new Float64Array(C);
|
|
818
|
+
for (let y = 0; y < H; y++) {
|
|
819
|
+
row.fill(0);
|
|
820
|
+
const dy = rect.y - R + y;
|
|
821
|
+
for (let x = 0; x < W; x++) {
|
|
822
|
+
const dx = rect.x - R + x;
|
|
823
|
+
const i = medium.locate(dx, dy, false);
|
|
824
|
+
if (i >= 0) {
|
|
825
|
+
const px = medium.pixels;
|
|
826
|
+
const a = px[i + 3] / 255;
|
|
827
|
+
if (a > 0) {
|
|
828
|
+
if (pigment) {
|
|
829
|
+
const p = pigment8(px[i], px[i + 1], px[i + 2]);
|
|
830
|
+
const sa = p.scatter * p.strength * a;
|
|
831
|
+
for (let k = 0; k < N; k++) row[k] += sa * p.ks[k];
|
|
832
|
+
row[N] += sa;
|
|
833
|
+
row[N + 1] += a;
|
|
834
|
+
} else {
|
|
835
|
+
row[0] += px[i] / 255 * a;
|
|
836
|
+
row[1] += px[i + 1] / 255 * a;
|
|
837
|
+
row[2] += px[i + 2] / 255 * a;
|
|
838
|
+
row[3] += a;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const o = (y + 1) * stride + (x + 1) * C;
|
|
843
|
+
const up = y * stride + (x + 1) * C;
|
|
844
|
+
for (let c = 0; c < C; c++) I[o + c] = I[up + c] + row[c];
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const area = (2 * R + 1) * (2 * R + 1);
|
|
848
|
+
const strength = settings.strength * opacity;
|
|
849
|
+
const lock = medium.target.lockTransparency;
|
|
850
|
+
const mixPig = scratch.mix;
|
|
851
|
+
const out = scratch.out;
|
|
852
|
+
const sum = (c, x1, y1, x2, y2) => I[y2 * stride + x2 * C + c] - I[y1 * stride + x2 * C + c] - I[y2 * stride + x1 * C + c] + I[y1 * stride + x1 * C + c];
|
|
853
|
+
const w = rect.width;
|
|
854
|
+
medium.reset();
|
|
855
|
+
for (let yy = 0; yy < rect.height; yy++) {
|
|
856
|
+
for (let xx = 0; xx < w; xx++) {
|
|
857
|
+
const k = cov[yy * w + xx];
|
|
858
|
+
if (k <= 0) continue;
|
|
859
|
+
const dx = rect.x + xx;
|
|
860
|
+
const dy = rect.y + yy;
|
|
861
|
+
let t = strength * k * medium.selectionAt(dx, dy);
|
|
862
|
+
if (t <= 0) continue;
|
|
863
|
+
if (t > 1) t = 1;
|
|
864
|
+
const x1 = xx;
|
|
865
|
+
const y1 = yy;
|
|
866
|
+
const x2 = xx + 2 * R + 1;
|
|
867
|
+
const y2 = yy + 2 * R + 1;
|
|
868
|
+
const asum = sum(pigment ? N + 1 : 3, x1, y1, x2, y2);
|
|
869
|
+
const ri = medium.locate(dx, dy, false);
|
|
870
|
+
const a = ri < 0 ? 0 : medium.pixels[ri + 3] / 255;
|
|
871
|
+
if (lock && a <= 0) continue;
|
|
872
|
+
const avgA = asum / area;
|
|
873
|
+
const na = lock ? a : (1 - t) * a + t * avgA;
|
|
874
|
+
if (asum <= 1e-9) {
|
|
875
|
+
if (lock || a <= 0) continue;
|
|
876
|
+
const i2 = medium.locate(dx, dy, true);
|
|
877
|
+
medium.pixels[i2 + 3] = Math.round(na * 255);
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
const share = lock ? t : na > 0 ? t * avgA / na : 1;
|
|
881
|
+
const i = medium.locate(dx, dy, true);
|
|
882
|
+
const px = medium.pixels;
|
|
883
|
+
if (pigment) {
|
|
884
|
+
const sa = sum(N, x1, y1, x2, y2);
|
|
885
|
+
for (let c = 0; c < N; c++) mixPig.ks[c] = sum(c, x1, y1, x2, y2) / sa;
|
|
886
|
+
mixPig.scatter = sa / asum;
|
|
887
|
+
if (a <= 0 || share >= 1) lerpPigmentInto(mixPig, mixPig, 0, out, 0);
|
|
888
|
+
else lerpPigmentInto(pigment8(px[i], px[i + 1], px[i + 2]), mixPig, share, out, 0);
|
|
889
|
+
} else {
|
|
890
|
+
for (let c = 0; c < 3; c++) {
|
|
891
|
+
const m = sum(c, x1, y1, x2, y2) / asum;
|
|
892
|
+
out[c] = a <= 0 || share >= 1 ? m : px[i + c] / 255 + (m - px[i + c] / 255) * share;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
px[i] = Math.round(out[0] * 255);
|
|
896
|
+
px[i + 1] = Math.round(out[1] * 255);
|
|
897
|
+
px[i + 2] = Math.round(out[2] * 255);
|
|
898
|
+
if (!lock) px[i + 3] = Math.round(na * 255);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
var SmudgeTip = class {
|
|
903
|
+
constructor(settings, diameter) {
|
|
904
|
+
this.settings = settings;
|
|
905
|
+
this.size = Math.max(2, Math.min(512, Math.ceil(diameter)));
|
|
906
|
+
this.patch = new Float32Array(this.size * this.size * 4);
|
|
907
|
+
}
|
|
908
|
+
settings;
|
|
909
|
+
size;
|
|
910
|
+
patch;
|
|
911
|
+
loaded = false;
|
|
912
|
+
out = [0, 0, 0];
|
|
913
|
+
cell(d, px, py) {
|
|
914
|
+
const s = this.size;
|
|
915
|
+
const r = Math.max(0.5, d.radius);
|
|
916
|
+
const bx = Math.min(s - 1, Math.max(0, Math.floor(((px - d.x) / r + 1) / 2 * s)));
|
|
917
|
+
const by = Math.min(s - 1, Math.max(0, Math.floor(((py - d.y) / r + 1) / 2 * s)));
|
|
918
|
+
return (by * s + bx) * 4;
|
|
919
|
+
}
|
|
920
|
+
/** Fills the patch from the pixels under the dab. */
|
|
921
|
+
load(medium, d) {
|
|
922
|
+
const s = this.size;
|
|
923
|
+
const r = Math.max(0.5, d.radius);
|
|
924
|
+
medium.reset();
|
|
925
|
+
for (let by = 0; by < s; by++) {
|
|
926
|
+
for (let bx = 0; bx < s; bx++) {
|
|
927
|
+
const dx = Math.floor(d.x + ((bx + 0.5) / s * 2 - 1) * r);
|
|
928
|
+
const dy = Math.floor(d.y + ((by + 0.5) / s * 2 - 1) * r);
|
|
929
|
+
const i = medium.locate(dx, dy, false);
|
|
930
|
+
const c = (by * s + bx) * 4;
|
|
931
|
+
if (i < 0) {
|
|
932
|
+
this.patch.fill(0, c, c + 4);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
const px = medium.pixels;
|
|
936
|
+
this.patch[c] = px[i] / 255;
|
|
937
|
+
this.patch[c + 1] = px[i + 1] / 255;
|
|
938
|
+
this.patch[c + 2] = px[i + 2] / 255;
|
|
939
|
+
this.patch[c + 3] = px[i + 3] / 255;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
this.loaded = true;
|
|
943
|
+
}
|
|
944
|
+
/** `a` mixed `t` of the way to `b` (both 0..1) into `out`. */
|
|
945
|
+
mix(a0, a1, a2, b0, b1, b2, t) {
|
|
946
|
+
const o = this.out;
|
|
947
|
+
if (this.settings.mixing === "pigment") {
|
|
948
|
+
mixPigment8(Math.round(a0 * 255), Math.round(a1 * 255), Math.round(a2 * 255), Math.round(b0 * 255), Math.round(b1 * 255), Math.round(b2 * 255), t, o);
|
|
949
|
+
} else {
|
|
950
|
+
o[0] = a0 + (b0 - a0) * t;
|
|
951
|
+
o[1] = a1 + (b1 - a1) * t;
|
|
952
|
+
o[2] = a2 + (b2 - a2) * t;
|
|
953
|
+
}
|
|
954
|
+
return o;
|
|
955
|
+
}
|
|
956
|
+
/** One dab: lays the carried patch over the tip's footprint (`cov` over `rect`), then picks up. */
|
|
957
|
+
dab(medium, d, rect, cov) {
|
|
958
|
+
if (!this.loaded) {
|
|
959
|
+
this.load(medium, d);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const { strength, length } = this.settings;
|
|
963
|
+
const lock = medium.target.lockTransparency;
|
|
964
|
+
const patch = this.patch;
|
|
965
|
+
const w = rect.width;
|
|
966
|
+
medium.reset();
|
|
967
|
+
for (let yy = 0; yy < rect.height; yy++) {
|
|
968
|
+
for (let xx = 0; xx < w; xx++) {
|
|
969
|
+
const cv = cov[yy * w + xx];
|
|
970
|
+
if (cv <= 0) continue;
|
|
971
|
+
const dx = rect.x + xx;
|
|
972
|
+
const dy = rect.y + yy;
|
|
973
|
+
let k = strength * cv * d.opacity * medium.selectionAt(dx, dy);
|
|
974
|
+
if (k <= 0) continue;
|
|
975
|
+
if (k > 1) k = 1;
|
|
976
|
+
const c = this.cell(d, dx + 0.5, dy + 0.5);
|
|
977
|
+
const ba = patch[c + 3];
|
|
978
|
+
const ri = medium.locate(dx, dy, false);
|
|
979
|
+
const a = ri < 0 ? 0 : medium.pixels[ri + 3] / 255;
|
|
980
|
+
if (lock && a <= 0) continue;
|
|
981
|
+
const px0 = ri >= 0 ? medium.pixels : null;
|
|
982
|
+
const o0 = px0 ? px0[ri] / 255 : 0;
|
|
983
|
+
const o1 = px0 ? px0[ri + 1] / 255 : 0;
|
|
984
|
+
const o2 = px0 ? px0[ri + 2] / 255 : 0;
|
|
985
|
+
if (a > 0 || ba > 0) {
|
|
986
|
+
const na = lock ? a : a + (ba - a) * k;
|
|
987
|
+
const share = lock ? k * ba : na > 0 ? k * ba / na : 0;
|
|
988
|
+
let r = o0;
|
|
989
|
+
let g = o1;
|
|
990
|
+
let b = o2;
|
|
991
|
+
if (a <= 0 || share >= 1) {
|
|
992
|
+
r = patch[c];
|
|
993
|
+
g = patch[c + 1];
|
|
994
|
+
b = patch[c + 2];
|
|
995
|
+
} else if (share > 0) {
|
|
996
|
+
const m = this.mix(o0, o1, o2, patch[c], patch[c + 1], patch[c + 2], share);
|
|
997
|
+
r = m[0];
|
|
998
|
+
g = m[1];
|
|
999
|
+
b = m[2];
|
|
1000
|
+
}
|
|
1001
|
+
const i = medium.locate(dx, dy, true);
|
|
1002
|
+
const out = medium.pixels;
|
|
1003
|
+
out[i] = Math.round(r * 255);
|
|
1004
|
+
out[i + 1] = Math.round(g * 255);
|
|
1005
|
+
out[i + 2] = Math.round(b * 255);
|
|
1006
|
+
out[i + 3] = Math.round(na * 255);
|
|
1007
|
+
}
|
|
1008
|
+
const pick = (1 - length) * Math.min(1, cv * d.opacity);
|
|
1009
|
+
if (pick > 0) {
|
|
1010
|
+
const pa = ba + (a - ba) * pick;
|
|
1011
|
+
const s = pa > 0 ? a * pick / pa : 0;
|
|
1012
|
+
if (s > 0) {
|
|
1013
|
+
const m = ba <= 0 || s >= 1 ? [o0, o1, o2] : this.mix(patch[c], patch[c + 1], patch[c + 2], o0, o1, o2, s);
|
|
1014
|
+
patch[c] = m[0];
|
|
1015
|
+
patch[c + 1] = m[1];
|
|
1016
|
+
patch[c + 2] = m[2];
|
|
1017
|
+
}
|
|
1018
|
+
patch[c + 3] = pa;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
|
|
642
1025
|
// src/paint/pigment-tools.ts
|
|
643
1026
|
var HEX = /^#?([0-9a-f]{6})$/i;
|
|
644
1027
|
function parseHex(value, index) {
|
|
@@ -654,7 +1037,7 @@ function createPigmentTools() {
|
|
|
654
1037
|
const mixColors = {
|
|
655
1038
|
name: "mix_colors",
|
|
656
1039
|
title: "Mix colours like paint",
|
|
657
|
-
description: "Mixes colours as paint would (Kubelka-Munk
|
|
1040
|
+
description: "Mixes colours as paint would (model pigment, the default: Kubelka-Munk mixing over reflectance spectra, so blue and yellow make green and white lightens a colour into a tint instead of greying it), or as light on screen (model rgb: the weighted RGB average). Changes nothing. Returns mixed (#rrggbb, by the chosen model), the other model's result for comparison (rgbLerp and pigment), the normalised weights and the model.",
|
|
658
1041
|
inputSchema: {
|
|
659
1042
|
type: "object",
|
|
660
1043
|
properties: {
|
|
@@ -678,6 +1061,11 @@ function createPigmentTools() {
|
|
|
678
1061
|
required: ["color", "weight"],
|
|
679
1062
|
additionalProperties: false
|
|
680
1063
|
}
|
|
1064
|
+
},
|
|
1065
|
+
model: {
|
|
1066
|
+
type: "string",
|
|
1067
|
+
enum: ["pigment", "rgb"],
|
|
1068
|
+
description: "pigment (default): mix like paint (Kubelka-Munk), as the pigment blend mode, Mixer Brush and mixing pad do; rgb: average the RGB values, as normal blending and rgb mixing brushes do (strength is ignored)."
|
|
681
1069
|
}
|
|
682
1070
|
},
|
|
683
1071
|
required: ["colors"],
|
|
@@ -689,14 +1077,16 @@ function createPigmentTools() {
|
|
|
689
1077
|
const rgbs = items.map((item, i) => parseHex(item.color, i));
|
|
690
1078
|
const total = items.reduce((sum, item) => sum + item.weight, 0);
|
|
691
1079
|
if (!(total > 0)) throw new Error("Every weight is 0; give at least one colour a weight above 0.");
|
|
692
|
-
const pigments = rgbs.map((
|
|
1080
|
+
const pigments = rgbs.map((rgb2, i) => createPigment(rgb2, { strength: items[i].strength }));
|
|
693
1081
|
const mixed = mixPigments(pigments, items.map((item) => item.weight));
|
|
694
|
-
const lerp = [0, 1, 2].map((c) => rgbs.reduce((sum,
|
|
1082
|
+
const lerp = [0, 1, 2].map((c) => rgbs.reduce((sum, rgb2, i) => sum + rgb2[c] * items[i].weight, 0) / total);
|
|
1083
|
+
const rgb = args.model === "rgb";
|
|
695
1084
|
return {
|
|
696
|
-
mixed: toHex(mixed),
|
|
1085
|
+
mixed: toHex(rgb ? lerp : mixed),
|
|
697
1086
|
rgbLerp: toHex(lerp),
|
|
1087
|
+
pigment: toHex(mixed),
|
|
698
1088
|
weights: items.map((item) => item.weight / total),
|
|
699
|
-
model: "kubelka-munk"
|
|
1089
|
+
model: rgb ? "rgb" : "kubelka-munk"
|
|
700
1090
|
};
|
|
701
1091
|
}
|
|
702
1092
|
};
|
|
@@ -721,14 +1111,26 @@ export {
|
|
|
721
1111
|
applyStrokeToTile,
|
|
722
1112
|
coverageTileCoord,
|
|
723
1113
|
MIXING_MODELS,
|
|
724
|
-
|
|
1114
|
+
EMPTY_MIXER_LOAD,
|
|
725
1115
|
MIXER_PICKUP_RATE,
|
|
726
1116
|
MIXER_DRAIN,
|
|
1117
|
+
exchangeRates,
|
|
1118
|
+
PaintExchange,
|
|
1119
|
+
TileMedium,
|
|
1120
|
+
DEFAULT_MIXER,
|
|
727
1121
|
normalizeMixer,
|
|
728
|
-
|
|
1122
|
+
mixerRates,
|
|
729
1123
|
loadMixer,
|
|
730
1124
|
cleanMixer,
|
|
731
1125
|
MixerStroke,
|
|
1126
|
+
DEFAULT_BLENDER,
|
|
1127
|
+
DEFAULT_SMUDGE,
|
|
1128
|
+
normalizeBlender,
|
|
1129
|
+
normalizeSmudge,
|
|
1130
|
+
blendRadius,
|
|
1131
|
+
createBlenderScratch,
|
|
1132
|
+
blenderDab,
|
|
1133
|
+
SmudgeTip,
|
|
732
1134
|
createPigmentTools
|
|
733
1135
|
};
|
|
734
|
-
//# sourceMappingURL=chunk-
|
|
1136
|
+
//# sourceMappingURL=chunk-Z4BC6M74.js.map
|