@pixerate/editor 0.94.0 → 0.96.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.
Files changed (41) hide show
  1. package/THIRD_PARTY_NOTICES.md +2 -0
  2. package/dist/{chunk-ZOOEX3WT.cjs → chunk-CRMAXIUV.cjs} +369 -7
  3. package/dist/chunk-CRMAXIUV.cjs.map +1 -0
  4. package/dist/{chunk-D4ZCU72J.js → chunk-HEW6KDNI.js} +366 -4
  5. package/dist/chunk-HEW6KDNI.js.map +1 -0
  6. package/dist/{chunk-ISDCJHTO.js → chunk-IB53LVGV.js} +238 -285
  7. package/dist/chunk-IB53LVGV.js.map +1 -0
  8. package/dist/{chunk-747CGOT2.js → chunk-KFTQ2PKP.js} +29 -2
  9. package/dist/chunk-KFTQ2PKP.js.map +1 -0
  10. package/dist/{chunk-73L6TROJ.cjs → chunk-T422YCHR.cjs} +71 -44
  11. package/dist/chunk-T422YCHR.cjs.map +1 -0
  12. package/dist/{chunk-4F3YDIWW.cjs → chunk-ZZOTHJ4K.cjs} +264 -311
  13. package/dist/chunk-ZZOTHJ4K.cjs.map +1 -0
  14. package/dist/image-editor/index.cjs +1832 -632
  15. package/dist/image-editor/index.cjs.map +1 -1
  16. package/dist/image-editor/index.d.cts +179 -22
  17. package/dist/image-editor/index.d.ts +179 -22
  18. package/dist/image-editor/index.js +1463 -263
  19. package/dist/image-editor/index.js.map +1 -1
  20. package/dist/paint/index.cjs +22 -3
  21. package/dist/paint/index.cjs.map +1 -1
  22. package/dist/paint/index.d.cts +117 -2
  23. package/dist/paint/index.d.ts +117 -2
  24. package/dist/paint/index.js +32 -13
  25. package/dist/raster/index.cjs +7 -3
  26. package/dist/raster/index.cjs.map +1 -1
  27. package/dist/raster/index.d.cts +796 -5
  28. package/dist/raster/index.d.ts +796 -5
  29. package/dist/raster/index.js +6 -2
  30. package/dist/{surface-CuBg2c6k.d.cts → surface-DIA-XatS.d.cts} +23 -11
  31. package/dist/{surface-CuBg2c6k.d.ts → surface-DIA-XatS.d.ts} +23 -11
  32. package/package.json +1 -1
  33. package/skills/pixerate-image-editor/SKILL.md +39 -6
  34. package/dist/chunk-4F3YDIWW.cjs.map +0 -1
  35. package/dist/chunk-73L6TROJ.cjs.map +0 -1
  36. package/dist/chunk-747CGOT2.js.map +0 -1
  37. package/dist/chunk-D4ZCU72J.js.map +0 -1
  38. package/dist/chunk-ISDCJHTO.js.map +0 -1
  39. package/dist/chunk-ZOOEX3WT.cjs.map +0 -1
  40. package/dist/zlib-BJ6U7LuB.d.cts +0 -795
  41. package/dist/zlib-BjQkpMFi.d.ts +0 -795
@@ -1,9 +1,14 @@
1
1
  import {
2
2
  TILE,
3
3
  blendPixel,
4
+ createPigment,
5
+ lerpPigmentInto,
6
+ mixPigment8,
7
+ mixPigments,
8
+ setPigmentColor,
4
9
  tileCoord,
5
10
  tileKey
6
- } from "./chunk-D4ZCU72J.js";
11
+ } from "./chunk-HEW6KDNI.js";
7
12
 
8
13
  // src/paint/brush.ts
9
14
  var DEFAULT_BRUSH = {
@@ -381,6 +386,18 @@ function applyStrokeToTile(coverage, tileData, channels, tx, ty, s, options = {}
381
386
  }
382
387
  if (s.lockTransparency && out[i + 3] === 0) continue;
383
388
  const src = s.mode === "erase" ? bg : cs;
389
+ if (s.blend === "pigment") {
390
+ const bytes = s.mode === "erase" ? s.background : s.color;
391
+ const as = src[3] * k;
392
+ if (!(as > 0)) continue;
393
+ const ao = as + out[i + 3] / 255 * (1 - as);
394
+ mixPigment8(out[i], out[i + 1], out[i + 2], bytes[0], bytes[1], bytes[2], as / ao, cb);
395
+ out[i] = Math.round(cb[0] * 255);
396
+ out[i + 1] = Math.round(cb[1] * 255);
397
+ out[i + 2] = Math.round(cb[2] * 255);
398
+ if (!s.lockTransparency) out[i + 3] = Math.round(ao * 255);
399
+ continue;
400
+ }
384
401
  for (let ch = 0; ch < 4; ch++) cb[ch] = out[i + ch] / 255;
385
402
  const o = blendPixel(s.blend, cb, src, k);
386
403
  out[i] = Math.round(o[0] * 255);
@@ -395,293 +412,231 @@ function coverageTileCoord(key) {
395
412
  return tileCoord(key);
396
413
  }
397
414
 
398
- // src/paint/pigment.ts
399
- var PIGMENT_BANDS = 36;
400
- var PIGMENT_LAMBDA_MIN = 380;
401
- var PIGMENT_LAMBDA_STEP = 10;
402
- var PIGMENT_WAVELENGTHS = Float32Array.from({ length: PIGMENT_BANDS }, (_, i) => PIGMENT_LAMBDA_MIN + i * PIGMENT_LAMBDA_STEP);
403
- var N = PIGMENT_BANDS;
404
- var LUT_NODES = 17;
405
- var R_MIN = 1e-4;
406
- var SCATTER_MIN = 0.01;
407
- function toLinear(v) {
408
- return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
409
- }
410
- function toEncoded(v) {
411
- if (!(v > 0)) return 0;
412
- if (v >= 1) return 1;
413
- return v <= 31308e-7 ? 12.92 * v : 1.055 * Math.pow(v, 1 / 2.4) - 0.055;
414
- }
415
- function clamp01(v) {
416
- return v > 0 ? v < 1 ? v : 1 : 0;
415
+ // src/paint/mixer.ts
416
+ var MIXING_MODELS = ["rgb", "pigment"];
417
+ var DEFAULT_MIXER = {
418
+ wet: 0.5,
419
+ load: 0.5,
420
+ mix: 0.5,
421
+ loadEachStroke: true,
422
+ cleanEachStroke: true,
423
+ mixing: "rgb"
424
+ };
425
+ var MIXER_PICKUP_RATE = 0.25;
426
+ var MIXER_DRAIN = 0.08;
427
+ function normalizeMixer(raw = {}) {
428
+ const unit = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
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
+ };
417
438
  }
418
- function lobe(l, mu, s1, s2) {
419
- const t = (l - mu) / (l < mu ? s1 : s2);
420
- return Math.exp(-0.5 * t * t);
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 };
421
443
  }
422
- function planck(nm, kelvin) {
423
- const m = nm * 1e-9;
424
- return 1 / (m ** 5 * (Math.exp(0.01438776877 / (m * kelvin)) - 1));
444
+ function cleanMixer(load) {
445
+ return { reservoir: load.reservoir, amount: load.amount, pickup: null };
425
446
  }
426
- var weights = null;
427
- function rgbWeights() {
428
- if (weights) return weights;
429
- const w = new Float64Array(3 * N);
430
- const m = [3.2404542, -1.5371385, -0.4985314, -0.969266, 1.8760108, 0.041556, 0.0556434, -0.2040259, 1.0572252];
431
- for (let b = 0; b < N; b++) {
432
- const l = PIGMENT_LAMBDA_MIN + b * PIGMENT_LAMBDA_STEP;
433
- const e = planck(l, 6504);
434
- const x = e * (1.056 * lobe(l, 599.8, 37.9, 31) + 0.362 * lobe(l, 442, 16, 26.7) - 0.065 * lobe(l, 501.1, 20.4, 26.2));
435
- const y = e * (0.821 * lobe(l, 568.8, 46.9, 40.5) + 0.286 * lobe(l, 530.9, 16.3, 31.1));
436
- const z = e * (1.217 * lobe(l, 437, 11.8, 36) + 0.681 * lobe(l, 459, 26, 13.8));
437
- for (let c = 0; c < 3; c++) w[c * N + b] = m[c * 3] * x + m[c * 3 + 1] * y + m[c * 3 + 2] * z;
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);
453
+ this.reservoir = load.reservoir ? [load.reservoir[0], load.reservoir[1], load.reservoir[2]] : null;
454
+ this.amount = this.reservoir ? Math.min(1, Math.max(0, load.amount)) : 0;
455
+ this.pickup = load.pickup ? [load.pickup[0], load.pickup[1], load.pickup[2]] : null;
456
+ this.pig = this.settings.mixing === "pigment" ? { res: createPigment(this.reservoir ?? [0, 0, 0]), pick: createPigment(this.pickup ?? [0, 0, 0]), paint: createPigment([0, 0, 0]), canvas: createPigment([0, 0, 0]) } : null;
438
457
  }
439
- for (let c = 0; c < 3; c++) {
440
- let sum = 0;
441
- for (let b = 0; b < N; b++) sum += w[c * N + b];
442
- for (let b = 0; b < N; b++) w[c * N + b] /= sum;
458
+ brush;
459
+ target;
460
+ dabCount = 0;
461
+ bounds = null;
462
+ walker;
463
+ smoothPos = null;
464
+ buf = new Float32Array(0);
465
+ settings;
466
+ reservoir;
467
+ amount;
468
+ pickup;
469
+ pig;
470
+ out = [0, 0, 0];
471
+ /** The brush load after the dabs so far. */
472
+ get load() {
473
+ return { reservoir: this.reservoir ? [...this.reservoir] : null, amount: this.amount, pickup: this.pickup ? [...this.pickup] : null };
443
474
  }
444
- return weights = w;
445
- }
446
- function fitCoefficients(r, g, b, coeffs) {
447
- const w = rgbWeights();
448
- const target = [r, g, b];
449
- const res = new Float64Array(3);
450
- const jac = new Float64Array(9);
451
- const trial = new Float64Array(3);
452
- let err = residual(coeffs, target, w, res, jac);
453
- for (let it = 0; it < 60 && err > 1e-10; it++) {
454
- const step = solve3(jac, res);
455
- if (!step) break;
456
- let scale = 1;
457
- let next = Infinity;
458
- for (let k = 0; k < 12; k++) {
459
- for (let i = 0; i < 3; i++) trial[i] = coeffs[i] - scale * step[i];
460
- next = residual(trial, target, w, null, null);
461
- if (next < err) break;
462
- scale *= 0.5;
463
- }
464
- if (!(next < err)) break;
465
- coeffs.set(trial);
466
- err = residual(coeffs, target, w, res, jac);
475
+ push(points) {
476
+ const dabs = [];
477
+ for (const p of points) this.walker.push(this.smooth(p), dabs);
478
+ return this.paint(dabs);
467
479
  }
468
- return err;
469
- }
470
- function residual(c, target, w, res, jac) {
471
- let r0 = -target[0];
472
- let r1 = -target[1];
473
- let r2 = -target[2];
474
- if (jac) jac.fill(0);
475
- for (let b = 0; b < N; b++) {
476
- const x = b / (N - 1);
477
- const z = (c[0] * x + c[1]) * x + c[2];
478
- const q = 1 + z * z;
479
- const sq = Math.sqrt(q);
480
- const s = 0.5 + z / (2 * sq);
481
- r0 += w[b] * s;
482
- r1 += w[N + b] * s;
483
- r2 += w[2 * N + b] * s;
484
- if (jac) {
485
- const ds = 1 / (2 * q * sq);
486
- for (let k = 0; k < 3; k++) {
487
- const wd = w[k * N + b] * ds;
488
- jac[k * 3] += wd * x * x;
489
- jac[k * 3 + 1] += wd * x;
490
- jac[k * 3 + 2] += wd;
491
- }
492
- }
480
+ finish(last) {
481
+ const dabs = [];
482
+ if (last && this.smoothPos && (this.smoothPos.x !== last.x || this.smoothPos.y !== last.y)) this.walker.push(last, dabs);
483
+ this.walker.finish(dabs);
484
+ return this.paint(dabs);
493
485
  }
494
- if (res) {
495
- res[0] = r0;
496
- res[1] = r1;
497
- res[2] = r2;
486
+ smooth(p) {
487
+ const a = 1 - Math.min(0.95, Math.max(0, this.brush.smoothing));
488
+ const cur = this.smoothPos;
489
+ const n = cur && a < 1 ? { ...p, x: cur.x + (p.x - cur.x) * a, y: cur.y + (p.y - cur.y) * a } : p;
490
+ this.smoothPos = n;
491
+ return n;
498
492
  }
499
- return r0 * r0 + r1 * r1 + r2 * r2;
500
- }
501
- function solve3(a, v) {
502
- const [a0, a1, a2, a3, a4, a5, a6, a7, a8] = a;
503
- const c0 = a4 * a8 - a5 * a7;
504
- const c1 = a5 * a6 - a3 * a8;
505
- const c2 = a3 * a7 - a4 * a6;
506
- const det = a0 * c0 + a1 * c1 + a2 * c2;
507
- if (!(Math.abs(det) > 1e-300)) return null;
508
- const [v0, v1, v2] = v;
509
- return [
510
- (v0 * c0 + a1 * (a5 * v2 - v1 * a8) + a2 * (v1 * a7 - a4 * v2)) / det,
511
- (a0 * (v1 * a8 - a5 * v2) + v0 * c1 + a2 * (a3 * v2 - v1 * a6)) / det,
512
- (a0 * (a4 * v2 - v1 * a7) + a1 * (v1 * a6 - a3 * v2) + v0 * c2) / det
513
- ];
514
- }
515
- var candidate = new Float64Array(3);
516
- function fitNode(r, g, b, coeffs) {
517
- let best = fitCoefficients(r, g, b, coeffs);
518
- if (best <= 1e-12) return;
519
- candidate.fill(0);
520
- let err = fitCoefficients(r, g, b, candidate);
521
- if (err < best) {
522
- best = err;
523
- coeffs.set(candidate);
493
+ /** `a` mixed `t` of the way to `b` into `this.out`, by the mixing model. */
494
+ mix(a, pa, b, pb, t) {
495
+ const o = this.out;
496
+ if (pa && pb) lerpPigmentInto(pa, pb, t, o, 0);
497
+ else for (let c = 0; c < 3; c++) o[c] = a[c] + (b[c] - a[c]) * t;
498
+ return o;
524
499
  }
525
- if (best <= 1e-12) return;
526
- const max = Math.max(r, g, b);
527
- if (!(max > 0) || max >= 0.5) return;
528
- candidate.fill(0);
529
- const steps = 32;
530
- for (let i = 0; i <= steps; i++) {
531
- const scale = 0.5 * Math.pow(max / 0.5, i / steps) / max;
532
- err = fitCoefficients(r * scale, g * scale, b * scale, candidate);
500
+ paint(dabs) {
501
+ const rects = [];
502
+ for (const d of dabs) {
503
+ const rect = dabRect(d, this.brush.hardness);
504
+ const n = rect.width * rect.height;
505
+ if (this.buf.length < n) this.buf = new Float32Array(n);
506
+ const cov = this.buf.subarray(0, n);
507
+ rasterizeDab({ ...d, alpha: 1 }, this.brush.hardness, rect, cov);
508
+ this.dab(d, rect, cov);
509
+ rects.push(rect);
510
+ this.bounds = union(this.bounds, rect);
511
+ this.dabCount++;
512
+ }
513
+ return rects;
533
514
  }
534
- if (err < best) coeffs.set(candidate);
535
- }
536
- function sigmoidSpectrum(c, out, offset) {
537
- for (let b = 0; b < N; b++) {
538
- const x = b / (N - 1);
539
- const z = (c[0] * x + c[1]) * x + c[2];
540
- out[offset + b] = 0.5 + z / (2 * Math.sqrt(1 + z * z));
515
+ // The tile last looked up by `locate` (and whether it came from `writeTile`).
516
+ tx = NaN;
517
+ ty = NaN;
518
+ tile = null;
519
+ writable = false;
520
+ /** Index of document pixel (dx, dy) in `this.tile`, or -1 when reading an empty tile. */
521
+ locate(dx, dy, write) {
522
+ const t = this.target;
523
+ const lx = dx - t.offset.x;
524
+ const ly = dy - t.offset.y;
525
+ const tx = Math.floor(lx / TILE);
526
+ const ty = Math.floor(ly / TILE);
527
+ if (tx !== this.tx || ty !== this.ty || write && !this.writable) {
528
+ this.tx = tx;
529
+ this.ty = ty;
530
+ this.tile = write ? t.writeTile(tx, ty) : t.readTile(tx, ty);
531
+ this.writable = write;
532
+ }
533
+ return this.tile ? ((ly - ty * TILE) * TILE + (lx - tx * TILE)) * 4 : -1;
541
534
  }
542
- }
543
- var NODES = Float64Array.from({ length: LUT_NODES }, (_, i) => toLinear(i / (LUT_NODES - 1)));
544
- var table = null;
545
- function spectrumTable() {
546
- if (table) return table;
547
- const n = LUT_NODES;
548
- const t = new Float32Array(n * n * n * N);
549
- const row = new Float64Array(3);
550
- const coeffs = new Float64Array(3);
551
- for (let r = 0; r < n; r++) {
552
- for (let g = 0; g < n; g++) {
553
- coeffs.set(row);
554
- for (let b = 0; b < n; b++) {
555
- fitNode(NODES[r], NODES[g], NODES[b], coeffs);
556
- if (b === 0) row.set(coeffs);
557
- sigmoidSpectrum(coeffs, t, ((r * n + g) * n + b) * N);
535
+ dab(d, rect, cov) {
536
+ const { wet, mix, load } = this.settings;
537
+ const pig = this.pig;
538
+ const w = rect.width;
539
+ this.tx = NaN;
540
+ let ar = 0;
541
+ let ag = 0;
542
+ let ab = 0;
543
+ let wsum = 0;
544
+ let asum = 0;
545
+ for (let yy = 0; yy < rect.height; yy++) {
546
+ for (let xx = 0; xx < w; xx++) {
547
+ const k = cov[yy * w + xx];
548
+ if (k <= 0) continue;
549
+ wsum += k;
550
+ const i = this.locate(rect.x + xx, rect.y + yy, false);
551
+ if (i < 0) continue;
552
+ const px = this.tile;
553
+ const al = px[i + 3] / 255;
554
+ if (al <= 0) continue;
555
+ const ka = k * al;
556
+ ar += px[i] / 255 * ka;
557
+ ag += px[i + 1] / 255 * ka;
558
+ ab += px[i + 2] / 255 * ka;
559
+ asum += ka;
558
560
  }
559
561
  }
562
+ if (asum > 1e-6 && wet > 0) {
563
+ const canvas = [ar / asum, ag / asum, ab / asum];
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);
560
583
  }
561
- return table = t;
562
- }
563
- var cell = new Int32Array(3);
564
- var frac = new Float64Array(3);
565
- var linear = new Float64Array(3);
566
- function locate(channel, encoded) {
567
- const v = clamp01(encoded);
568
- const i = Math.min(LUT_NODES - 2, Math.floor(v * (LUT_NODES - 1)));
569
- const lo = NODES[i];
570
- const lin = toLinear(v);
571
- const f = (lin - lo) / (NODES[i + 1] - lo);
572
- cell[channel] = i;
573
- frac[channel] = f < 0 ? 0 : f > 1 ? 1 : f;
574
- linear[channel] = lin;
575
- }
576
- function rgbToReflectance(r, g, b, out = new Float32Array(N)) {
577
- const t = spectrumTable();
578
- const n = LUT_NODES;
579
- locate(0, r);
580
- locate(1, g);
581
- locate(2, b);
582
- const [ri, gi, bi] = cell;
583
- const [rf, gf, bf] = frac;
584
- out.fill(0);
585
- for (let corner = 0; corner < 8; corner++) {
586
- const dr = corner >> 2;
587
- const dg = corner >> 1 & 1;
588
- const db = corner & 1;
589
- const weight = (dr ? rf : 1 - rf) * (dg ? gf : 1 - gf) * (db ? bf : 1 - bf);
590
- if (weight === 0) continue;
591
- const base = (((ri + dr) * n + gi + dg) * n + bi + db) * N;
592
- for (let k = 0; k < N; k++) out[k] += weight * t[base + k];
593
- }
594
- return out;
595
- }
596
- function reflectanceToRgb(reflectance, out = [0, 0, 0]) {
597
- const w = rgbWeights();
598
- let r = 0;
599
- let g = 0;
600
- let b = 0;
601
- for (let k = 0; k < N; k++) {
602
- const v = reflectance[k];
603
- r += w[k] * v;
604
- g += w[N + k] * v;
605
- b += w[2 * N + k] * v;
606
- }
607
- out[0] = toEncoded(r);
608
- out[1] = toEncoded(g);
609
- out[2] = toEncoded(b);
610
- return out;
611
- }
612
- var spectrumScratch = new Float32Array(N);
613
- function createPigment(color, options = {}) {
614
- const pigment = { rgb: [0, 0, 0], strength: 1, ks: new Float32Array(N), scatter: 1 };
615
- return setPigmentColor(pigment, color[0], color[1], color[2], options.strength);
616
- }
617
- function setPigmentColor(pigment, r, g, b, strength = pigment.strength) {
618
- const R = rgbToReflectance(r, g, b, spectrumScratch);
619
- const ks = pigment.ks;
620
- for (let k = 0; k < N; k++) {
621
- const v = R[k] > R_MIN ? R[k] : R_MIN;
622
- ks[k] = (1 - v) * (1 - v) / (2 * v);
623
- }
624
- pigment.rgb[0] = clamp01(r);
625
- pigment.rgb[1] = clamp01(g);
626
- pigment.rgb[2] = clamp01(b);
627
- pigment.strength = Number.isFinite(strength) && strength > 0 ? strength : 1;
628
- const y = 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
629
- pigment.scatter = y > SCATTER_MIN ? y : SCATTER_MIN;
630
- return pigment;
631
- }
632
- function ksToRgb(ks, out, offset) {
633
- const w = rgbWeights();
634
- let r = 0;
635
- let g = 0;
636
- let b = 0;
637
- for (let k = 0; k < N; k++) {
638
- const q = ks[k];
639
- const v = 1 + q - Math.sqrt(q * q + 2 * q);
640
- r += w[k] * v;
641
- g += w[N + k] * v;
642
- b += w[2 * N + k] * v;
643
- }
644
- out[offset] = toEncoded(r);
645
- out[offset + 1] = toEncoded(g);
646
- out[offset + 2] = toEncoded(b);
647
- }
648
- var mixScratch = new Float64Array(N);
649
- function mixPigments(colors, weights2) {
650
- if (colors.length === 0) throw new Error("mixPigments needs at least one colour.");
651
- if (weights2.length !== colors.length) throw new Error(`mixPigments got ${colors.length} colours but ${weights2.length} weights; pass one weight per colour.`);
652
- const pigments = colors.map((c) => "ks" in c ? c : createPigment(c));
653
- let total = 0;
654
- for (let i = 0; i < pigments.length; i++) {
655
- const w = weights2[i];
656
- if (!(Number.isFinite(w) && w >= 0)) throw new Error(`Weight ${i} is ${w}; weights must be finite and \u2265 0.`);
657
- total += w * pigments[i].strength * pigments[i].scatter;
658
- }
659
- if (!(total > 0)) throw new Error("mixPigments needs at least one positive weight.");
660
- mixScratch.fill(0);
661
- for (let i = 0; i < pigments.length; i++) {
662
- const p = pigments[i];
663
- const c = weights2[i] * p.strength * p.scatter / total;
664
- if (c === 0) continue;
665
- for (let k = 0; k < N; k++) mixScratch[k] += c * p.ks[k];
584
+ /** Lays `paint` over the pixels under the dab at `dep` × coverage (× selection). */
585
+ deposit(rect, cov, paint, dep) {
586
+ const t = this.target;
587
+ const sel = t.selection;
588
+ const pig = this.pig;
589
+ const w = rect.width;
590
+ const o = this.out;
591
+ let lastKey = -1;
592
+ for (let yy = 0; yy < rect.height; yy++) {
593
+ const dy = rect.y + yy;
594
+ for (let xx = 0; xx < w; xx++) {
595
+ let k = cov[yy * w + xx] * dep;
596
+ if (k <= 0) continue;
597
+ const dx = rect.x + xx;
598
+ if (sel) {
599
+ if (dx < 0 || dy < 0 || dx >= sel.width || dy >= sel.height) continue;
600
+ k *= sel.data[dy * sel.width + dx] / 255;
601
+ if (k <= 0) continue;
602
+ }
603
+ if (k > 1) k = 1;
604
+ if (t.lockTransparency) {
605
+ const ri = this.locate(dx, dy, false);
606
+ if (ri < 0 || this.tile[ri + 3] === 0) continue;
607
+ }
608
+ const i = this.locate(dx, dy, true);
609
+ const px = this.tile;
610
+ const a = px[i + 3] / 255;
611
+ const na = t.lockTransparency ? a : a + (1 - a) * k;
612
+ const share = t.lockTransparency ? k : na > 0 ? k / na : 1;
613
+ if (a <= 0 || share >= 1) {
614
+ o[0] = paint[0];
615
+ o[1] = paint[1];
616
+ o[2] = paint[2];
617
+ } else if (pig) {
618
+ const key = px[i] << 16 | px[i + 1] << 8 | px[i + 2];
619
+ if (key !== lastKey) {
620
+ setPigmentColor(pig.canvas, px[i] / 255, px[i + 1] / 255, px[i + 2] / 255);
621
+ lastKey = key;
622
+ }
623
+ lerpPigmentInto(pig.canvas, pig.paint, share, o, 0);
624
+ } else {
625
+ for (let c = 0; c < 3; c++) o[c] = px[i + c] / 255 + (paint[c] - px[i + c] / 255) * share;
626
+ }
627
+ px[i] = Math.round(o[0] * 255);
628
+ px[i + 1] = Math.round(o[1] * 255);
629
+ px[i + 2] = Math.round(o[2] * 255);
630
+ px[i + 3] = Math.round(na * 255);
631
+ }
632
+ }
666
633
  }
667
- const out = [0, 0, 0];
668
- ksToRgb(mixScratch, out, 0);
669
- return out;
670
- }
671
- function lerpPigment(a, b, t) {
672
- const u = clamp01(t);
673
- return mixPigments([a, b], [1 - u, u]);
674
- }
675
- function lerpPigmentInto(a, b, t, out, offset = 0) {
676
- const u = t > 0 ? t < 1 ? t : 1 : 0;
677
- const wa = (1 - u) * a.strength * a.scatter;
678
- const wb = u * b.strength * b.scatter;
679
- const ca = wa / (wa + wb);
680
- const cb = 1 - ca;
681
- const ka = a.ks;
682
- const kb = b.ks;
683
- for (let k = 0; k < N; k++) mixScratch[k] = ca * ka[k] + cb * kb[k];
684
- ksToRgb(mixScratch, out, offset);
634
+ };
635
+ function union(a, b) {
636
+ if (!a) return { ...b };
637
+ const x = Math.min(a.x, b.x);
638
+ const y = Math.min(a.y, b.y);
639
+ 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 };
685
640
  }
686
641
 
687
642
  // src/paint/pigment-tools.ts
@@ -765,17 +720,15 @@ export {
765
720
  StrokeCoverage,
766
721
  applyStrokeToTile,
767
722
  coverageTileCoord,
768
- PIGMENT_BANDS,
769
- PIGMENT_LAMBDA_MIN,
770
- PIGMENT_LAMBDA_STEP,
771
- PIGMENT_WAVELENGTHS,
772
- rgbToReflectance,
773
- reflectanceToRgb,
774
- createPigment,
775
- setPigmentColor,
776
- mixPigments,
777
- lerpPigment,
778
- lerpPigmentInto,
723
+ MIXING_MODELS,
724
+ DEFAULT_MIXER,
725
+ MIXER_PICKUP_RATE,
726
+ MIXER_DRAIN,
727
+ normalizeMixer,
728
+ EMPTY_MIXER_LOAD,
729
+ loadMixer,
730
+ cleanMixer,
731
+ MixerStroke,
779
732
  createPigmentTools
780
733
  };
781
- //# sourceMappingURL=chunk-ISDCJHTO.js.map
734
+ //# sourceMappingURL=chunk-IB53LVGV.js.map