rig-parts 2.9.0 → 2.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rig-parts",
3
- "version": "2.9.0",
3
+ "version": "2.9.1",
4
4
  "description": "AI-authored 2D character parts from one anime painting and its See-through layer decomposition: measured parts, weighted-mesh rig specs, verified through rig-c before anything is written. A CLI for agents that cannot see the image.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -78,6 +78,9 @@ import { type Mask, newMask, type Raster, RasterError } from './raster/types.ts'
78
78
  * part, BEHIND is made opaque there in `Cb` (in the painting's own colour
79
79
  * where FRONT's alpha is 0), so the flat composite at p is the painting, the
80
80
  * rim stays FRONT's, and BEHIND's hidden surface past the window is untouched.
81
+ * Where BEHIND already has alpha at least {@link EDGE_OWNER_ALPHA}, its own
82
+ * pre-step RGB is the fill colour. FRONT is unmixed against that same colour,
83
+ * including the clamping error calculation, to preserve the pair's composite.
81
84
  *
82
85
  * A pixel is left as it was — and counted, by reason — when a part drawn in
83
86
  * front of FRONT, or between BEHIND and FRONT, has alpha above
@@ -491,11 +494,16 @@ export function paintingEdges(parts: Raster[], srcr: Raster, options: PaintingEd
491
494
  decisions.forEach((q, k) => {
492
495
  const s = q.p * 4;
493
496
  const P = [srcr.data[s], srcr.data[s + 1], srcr.data[s + 2]];
494
- const unmixAt = (c: number, a: number): number => Math.min(255, Math.max(0, Math.round(q.cb[c] + ((P[c] - q.cb[c]) * 255) / a)));
497
+ // An existing BEHIND surface keeps its own colour. Unmix FRONT against
498
+ // that same surface so the pair still reconstructs the painting.
499
+ const cb = q.behind >= 0 && parts[q.behind].data[s + 3] >= EDGE_OWNER_ALPHA
500
+ ? [parts[q.behind].data[s], parts[q.behind].data[s + 1], parts[q.behind].data[s + 2]]
501
+ : q.cb;
502
+ const unmixAt = (c: number, a: number): number => Math.min(255, Math.max(0, Math.round(cb[c] + ((P[c] - cb[c]) * 255) / a)));
495
503
  // FRONT's colour is clamped to 0..255, so a pixel the two colours cannot mix at that coverage (a painted line darker or
496
504
  // lighter than both) would miss the painting: FRONT's coverage rises to the least that brings the composite within half
497
505
  // the contrast floor of it (at full coverage it is the painting).
498
- const miss = (a: number): number => Math.max(...[0, 1, 2].map((c) => Math.abs(Math.round((unmixAt(c, a) * a + q.cb[c] * (255 - a)) / 255) - P[c])));
506
+ const miss = (a: number): number => Math.max(...[0, 1, 2].map((c) => Math.abs(Math.round((unmixAt(c, a) * a + cb[c] * (255 - a)) / 255) - P[c])));
499
507
  let alpha = alphaOf[k];
500
508
  if (alpha > 0 && alpha < 255) while (alpha < 255 && 2 * miss(alpha) > EDGE_CONTRAST) alpha++;
501
509
  if (alpha >= 255 - OPAQUE_ALPHA_ABOVE) alpha = 255;
@@ -512,7 +520,7 @@ export function paintingEdges(parts: Raster[], srcr: Raster, options: PaintingEd
512
520
  else plan(q.front, q.p, unmix(0), unmix(1), unmix(2), alpha);
513
521
  if (q.behind >= 0 && alpha < 255) {
514
522
  if (alpha === 0) plan(q.behind, q.p, P[0], P[1], P[2], 255);
515
- else plan(q.behind, q.p, q.cb[0], q.cb[1], q.cb[2], 255);
523
+ else plan(q.behind, q.p, cb[0], cb[1], cb[2], 255);
516
524
  fills[q.behind].set(q.p, q.front);
517
525
  }
518
526
  });