@ikijs/editor 0.11.0 → 0.12.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/README.md +4 -4
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +178 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +178 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,10 +135,10 @@ rather than its soft feather, and its top edge at its centre x is the bridge top
|
|
|
135
135
|
the nose tilts 6° about on the turn, its tip toward the far side; absent, the
|
|
136
136
|
crop stands in for both. And it takes the optional `rowRuns` (per crop row, the
|
|
137
137
|
row's alpha ≥ 128 runs as a flat list of canvas columns `[start, end, …]`, each
|
|
138
|
-
end exclusive), measured for `hair_front` and `hair_back` so the turn
|
|
139
|
-
nod can tell where back hair is painted behind the front hair; absent,
|
|
140
|
-
hair keeps its outline hold
|
|
141
|
-
|
|
138
|
+
end exclusive), measured for `face`, `hair_front` and `hair_back` so the turn
|
|
139
|
+
and the nod can tell where back hair is painted behind the front hair; absent,
|
|
140
|
+
the front hair keeps its outline hold and its crown's ease to the back hair on
|
|
141
|
+
the turn, and that ease looking down on the nod.
|
|
142
142
|
|
|
143
143
|
`createLayerSetMeasurer(canvas)` measures all of it — `rowHalfWidths`,
|
|
144
144
|
`rowRuns`, `denseCore`, `headHalfWidth`, `headEdges` and `strandEdges` — off
|
package/dist/index.d.mts
CHANGED
|
@@ -710,11 +710,12 @@ interface LayerInput {
|
|
|
710
710
|
cropH: number;
|
|
711
711
|
/** Face only: half the opaque span of each crop row, canvas px (0 = none). */
|
|
712
712
|
rowHalfWidths?: number[];
|
|
713
|
-
/** `hair_front` / `hair_back` only: per crop row, its opaque (alpha ≥ 128)
|
|
713
|
+
/** `face` / `hair_front` / `hair_back` only: per crop row, its opaque (alpha ≥ 128)
|
|
714
714
|
* runs as a flat list of canvas columns `[start0, end0, start1, end1, …]`,
|
|
715
715
|
* each end exclusive (`[]` for a row with none). The actual pixels, so a
|
|
716
716
|
* gap in the hair is a gap here: the turn and the nod read off it where
|
|
717
|
-
* back hair is painted behind the front hair
|
|
717
|
+
* back hair is painted behind the front hair, and the crown's turn reads the
|
|
718
|
+
* face's so it never bares skin. */
|
|
718
719
|
rowRuns?: number[][];
|
|
719
720
|
/** Nose only: the tight box of its alpha ≥ 128 pixels, canvas px. */
|
|
720
721
|
denseCore?: {
|
|
@@ -1000,7 +1001,7 @@ interface LayerSetMeasurer {
|
|
|
1000
1001
|
* it: only the set's opaque union, this layer's per-row opaque extent and
|
|
1001
1002
|
* the `LayerInput` it returns outlive the call, so the host may drop `rgba`
|
|
1002
1003
|
* as soon as it returns. Returns the layer's `LayerInput` — its crop
|
|
1003
|
-
* box, the face's `rowHalfWidths`, the hair layers' `rowRuns`, the nose's
|
|
1004
|
+
* box, the face's `rowHalfWidths`, the face's and hair layers' `rowRuns`, the nose's
|
|
1004
1005
|
* `denseCore` unless that is a speck of its crop (`isSpeckCore`) — or
|
|
1005
1006
|
* `null` for an empty layer, one with no pixel at or above
|
|
1006
1007
|
* `ALPHA_BBOX_THRESHOLD`, which it records nothing for: the host reports
|
package/dist/index.d.ts
CHANGED
|
@@ -710,11 +710,12 @@ interface LayerInput {
|
|
|
710
710
|
cropH: number;
|
|
711
711
|
/** Face only: half the opaque span of each crop row, canvas px (0 = none). */
|
|
712
712
|
rowHalfWidths?: number[];
|
|
713
|
-
/** `hair_front` / `hair_back` only: per crop row, its opaque (alpha ≥ 128)
|
|
713
|
+
/** `face` / `hair_front` / `hair_back` only: per crop row, its opaque (alpha ≥ 128)
|
|
714
714
|
* runs as a flat list of canvas columns `[start0, end0, start1, end1, …]`,
|
|
715
715
|
* each end exclusive (`[]` for a row with none). The actual pixels, so a
|
|
716
716
|
* gap in the hair is a gap here: the turn and the nod read off it where
|
|
717
|
-
* back hair is painted behind the front hair
|
|
717
|
+
* back hair is painted behind the front hair, and the crown's turn reads the
|
|
718
|
+
* face's so it never bares skin. */
|
|
718
719
|
rowRuns?: number[][];
|
|
719
720
|
/** Nose only: the tight box of its alpha ≥ 128 pixels, canvas px. */
|
|
720
721
|
denseCore?: {
|
|
@@ -1000,7 +1001,7 @@ interface LayerSetMeasurer {
|
|
|
1000
1001
|
* it: only the set's opaque union, this layer's per-row opaque extent and
|
|
1001
1002
|
* the `LayerInput` it returns outlive the call, so the host may drop `rgba`
|
|
1002
1003
|
* as soon as it returns. Returns the layer's `LayerInput` — its crop
|
|
1003
|
-
* box, the face's `rowHalfWidths`, the hair layers' `rowRuns`, the nose's
|
|
1004
|
+
* box, the face's `rowHalfWidths`, the face's and hair layers' `rowRuns`, the nose's
|
|
1004
1005
|
* `denseCore` unless that is a speck of its crop (`isSpeckCore`) — or
|
|
1005
1006
|
* `null` for an empty layer, one with no pixel at or above
|
|
1006
1007
|
* `ALPHA_BBOX_THRESHOLD`, which it records nothing for: the host reports
|
package/dist/index.js
CHANGED
|
@@ -1642,7 +1642,7 @@ function createLayerSetMeasurer(canvas) {
|
|
|
1642
1642
|
if (bbox === null) return null;
|
|
1643
1643
|
const rowLeft = new Int32Array(canvasH).fill(canvasW);
|
|
1644
1644
|
const rowRight = new Int32Array(canvasH).fill(-1);
|
|
1645
|
-
const rowRuns = role === "hair_front" || role === "hair_back" ? [] : void 0;
|
|
1645
|
+
const rowRuns = role === "face" || role === "hair_front" || role === "hair_back" ? [] : void 0;
|
|
1646
1646
|
for (let y = 0; y < canvasH; y++) {
|
|
1647
1647
|
let runs;
|
|
1648
1648
|
if (rowRuns !== void 0 && y >= bbox.y && y < bbox.y + bbox.h) {
|
|
@@ -2806,12 +2806,14 @@ function buildHeadFrame(layers, opts) {
|
|
|
2806
2806
|
shellRight = axisX + headHalf;
|
|
2807
2807
|
}
|
|
2808
2808
|
}
|
|
2809
|
-
const
|
|
2809
|
+
const runsOf = (role, y) => {
|
|
2810
2810
|
const l = byRole.get(role);
|
|
2811
2811
|
if (l?.rowRuns === void 0) return void 0;
|
|
2812
2812
|
const runs = l.rowRuns[Math.floor(canvasH / 2 - y) - l.bbox.y];
|
|
2813
2813
|
return runs === void 0 ? [] : runs.map((c) => c - canvasW / 2);
|
|
2814
2814
|
};
|
|
2815
|
+
const hairRuns = (role, y) => runsOf(role, y);
|
|
2816
|
+
const faceRuns = (y) => runsOf("face", y);
|
|
2815
2817
|
return {
|
|
2816
2818
|
canvasW,
|
|
2817
2819
|
canvasH,
|
|
@@ -2834,7 +2836,8 @@ function buildHeadFrame(layers, opts) {
|
|
|
2834
2836
|
shellRight,
|
|
2835
2837
|
hairShell,
|
|
2836
2838
|
holdBase: headHalf ?? plateHalf,
|
|
2837
|
-
hairRuns
|
|
2839
|
+
hairRuns,
|
|
2840
|
+
faceRuns
|
|
2838
2841
|
};
|
|
2839
2842
|
}
|
|
2840
2843
|
|
|
@@ -3036,22 +3039,42 @@ function hairFrontField(m) {
|
|
|
3036
3039
|
return core + (m.hairOuter - core) * out;
|
|
3037
3040
|
};
|
|
3038
3041
|
const ride = outerRide(m, inner, coreAt, followAt);
|
|
3042
|
+
const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride(x, y, s));
|
|
3043
|
+
const drift = Math.abs(m.hairBack);
|
|
3044
|
+
const crown = crownRide(m, cap, (x, y, s) => followOf(x, y, s) + drift);
|
|
3039
3045
|
return (x, y, ax, ay) => {
|
|
3040
3046
|
const s = Math.sign(ax);
|
|
3041
3047
|
const a = Math.abs(ax) / 30;
|
|
3042
|
-
const
|
|
3043
|
-
const rx = s * a * follow + a * (m.shellScale - 1) * (x - f.axisX);
|
|
3048
|
+
const rx = s * a * followOf(x, y, s) + a * (m.shellScale - 1) * (x - f.axisX);
|
|
3044
3049
|
const ry = nodDy(NOD.hairFront, ay, f.hh);
|
|
3045
3050
|
const bx = back(x, y, ax, ay)[0];
|
|
3046
3051
|
const by = nodDy(cap, ay, f.hh);
|
|
3047
3052
|
const w = crownBlend(m, y);
|
|
3048
|
-
|
|
3053
|
+
const wx = crown === void 0 ? w : w + crown(y, s);
|
|
3054
|
+
return [bx + wx * (rx - bx), by + w * (ry - by)];
|
|
3049
3055
|
};
|
|
3050
3056
|
}
|
|
3051
3057
|
function crownBlend(m, y) {
|
|
3052
3058
|
const top = m.frame.face.y1;
|
|
3053
3059
|
return 1 - smoothstep(top, Math.max(top + 1, m.hairTop), y);
|
|
3054
3060
|
}
|
|
3061
|
+
function gridRow(ys, y, snap = false) {
|
|
3062
|
+
const n = ys.length;
|
|
3063
|
+
let v = clamp((ys[0] - y) / (ys[0] - ys[n - 1]) * (n - 1), 0, n - 1);
|
|
3064
|
+
if (snap && Math.abs(v - Math.round(v)) < 1e-3) v = Math.round(v);
|
|
3065
|
+
const r = Math.min(n - 2, Math.floor(v));
|
|
3066
|
+
return { r, t: v - r };
|
|
3067
|
+
}
|
|
3068
|
+
function renderedCrownBlend(m, ys) {
|
|
3069
|
+
const wr = ys.map((y) => crownBlend(m, y));
|
|
3070
|
+
return {
|
|
3071
|
+
wr,
|
|
3072
|
+
blendAt: (y) => {
|
|
3073
|
+
const { r, t } = gridRow(ys, y);
|
|
3074
|
+
return wr[r] + (wr[r + 1] - wr[r]) * t;
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3055
3078
|
var CAP_COVER_LOSS = 0.01;
|
|
3056
3079
|
function capSlide(m) {
|
|
3057
3080
|
const f = m.frame;
|
|
@@ -3064,12 +3087,7 @@ function capSlide(m) {
|
|
|
3064
3087
|
const top = ys[0];
|
|
3065
3088
|
const n = ys.length;
|
|
3066
3089
|
const bangs = NOD.hairFront.down;
|
|
3067
|
-
const wr
|
|
3068
|
-
const blendAt = (y) => {
|
|
3069
|
-
const v = clamp((top - y) / (top - ys[n - 1]) * (n - 1), 0, n - 1);
|
|
3070
|
-
const r = Math.min(n - 2, Math.floor(v));
|
|
3071
|
-
return wr[r] + (wr[r + 1] - wr[r]) * (v - r);
|
|
3072
|
-
};
|
|
3090
|
+
const { wr, blendAt } = renderedCrownBlend(m, ys);
|
|
3073
3091
|
const flat = ys[wr.findIndex((w) => w >= 1)] ?? ys[n - 1];
|
|
3074
3092
|
const x0 = xs[0];
|
|
3075
3093
|
const tops = new Float64Array(Math.round(xs[xs.length - 1] - x0)).fill(NaN);
|
|
@@ -3198,13 +3216,157 @@ function outerRide(m, inner, coreAt, followAt) {
|
|
|
3198
3216
|
if (s === 0 || d <= inner) return 0;
|
|
3199
3217
|
const k = x < f.axisX ? 0 : 1;
|
|
3200
3218
|
const j = s < 0 ? 0 : 1;
|
|
3201
|
-
|
|
3202
|
-
if (Math.abs(v - Math.round(v)) < 1e-3) v = Math.round(v);
|
|
3203
|
-
const r = Math.min(n - 2, Math.floor(v));
|
|
3204
|
-
const t = v - r;
|
|
3219
|
+
const { r, t } = gridRow(ys, y, true);
|
|
3205
3220
|
return rowRide(r, k, j, d) * (1 - t) + rowRide(r + 1, k, j, d) * t;
|
|
3206
3221
|
};
|
|
3207
3222
|
}
|
|
3223
|
+
function crownRide(m, cap, relAt) {
|
|
3224
|
+
const f = m.frame;
|
|
3225
|
+
const grid = m.hairFrontGrid;
|
|
3226
|
+
if (grid === void 0) return void 0;
|
|
3227
|
+
const { xs, ys } = grid;
|
|
3228
|
+
const top = ys[0];
|
|
3229
|
+
if (f.hairRuns("hair_front", top) === void 0 || f.hairRuns("hair_back", top) === void 0) {
|
|
3230
|
+
return void 0;
|
|
3231
|
+
}
|
|
3232
|
+
const n = ys.length;
|
|
3233
|
+
const count = Math.round(top - ys[n - 1]);
|
|
3234
|
+
const hh = f.hh;
|
|
3235
|
+
const face = faceField(m);
|
|
3236
|
+
const { wr, blendAt } = renderedCrownBlend(m, ys);
|
|
3237
|
+
const backRows = /* @__PURE__ */ new Map();
|
|
3238
|
+
const backAt = (j) => {
|
|
3239
|
+
let runs = backRows.get(j);
|
|
3240
|
+
if (runs === void 0) {
|
|
3241
|
+
runs = f.hairRuns("hair_back", top - j - 0.5);
|
|
3242
|
+
backRows.set(j, runs);
|
|
3243
|
+
}
|
|
3244
|
+
return runs;
|
|
3245
|
+
};
|
|
3246
|
+
const faceAt = (j) => {
|
|
3247
|
+
const y = top - j - 0.5;
|
|
3248
|
+
return f.faceRuns(y) ?? (y > f.face.y0 && y < f.face.y1 ? [f.face.x0, f.face.x1] : []);
|
|
3249
|
+
};
|
|
3250
|
+
const passed = (i, dys) => [
|
|
3251
|
+
Math.floor(i - Math.max(0, ...dys)),
|
|
3252
|
+
Math.ceil(i + 1 - Math.min(0, ...dys))
|
|
3253
|
+
];
|
|
3254
|
+
const rel = new Float64Array(n * xs.length * 2).fill(NaN);
|
|
3255
|
+
const relOf = (r, c, k) => {
|
|
3256
|
+
const at = (r * xs.length + c) * 2 + k;
|
|
3257
|
+
if (Number.isNaN(rel[at])) rel[at] = relAt(xs[c], ys[r], k === 0 ? -1 : 1);
|
|
3258
|
+
return rel[at];
|
|
3259
|
+
};
|
|
3260
|
+
const cellR = (x, y, k) => {
|
|
3261
|
+
let c = 0;
|
|
3262
|
+
while (c < xs.length - 2 && xs[c + 1] < x) c++;
|
|
3263
|
+
const { r } = gridRow(ys, y);
|
|
3264
|
+
return Math.max(
|
|
3265
|
+
relOf(r, c, k),
|
|
3266
|
+
relOf(r, c + 1, k),
|
|
3267
|
+
relOf(r + 1, c, k),
|
|
3268
|
+
relOf(r + 1, c + 1, k)
|
|
3269
|
+
);
|
|
3270
|
+
};
|
|
3271
|
+
const someAngle = (pairs) => {
|
|
3272
|
+
let lo = 0;
|
|
3273
|
+
let hi = 1;
|
|
3274
|
+
for (const [c0, c1] of pairs) {
|
|
3275
|
+
if (c1 === 0) {
|
|
3276
|
+
if (c0 <= 0) return false;
|
|
3277
|
+
} else if (c1 > 0) lo = Math.max(lo, -c0 / c1);
|
|
3278
|
+
else hi = Math.min(hi, -c0 / c1);
|
|
3279
|
+
}
|
|
3280
|
+
return lo < hi;
|
|
3281
|
+
};
|
|
3282
|
+
const slack = [-1, 1].map(() => new Float64Array(count).fill(NaN));
|
|
3283
|
+
const slackAt = (i, k) => {
|
|
3284
|
+
if (!Number.isNaN(slack[k][i])) return slack[k][i];
|
|
3285
|
+
const y = top - i - 0.5;
|
|
3286
|
+
const w = blendAt(y);
|
|
3287
|
+
const nod = {
|
|
3288
|
+
up: w * NOD.hairFront.up + (1 - w) * cap.up,
|
|
3289
|
+
down: w * NOD.hairFront.down + (1 - w) * cap.down
|
|
3290
|
+
};
|
|
3291
|
+
const ays = [-30, 30];
|
|
3292
|
+
const [b0, b1] = passed(
|
|
3293
|
+
i,
|
|
3294
|
+
ays.map((ay) => nodDy(nod, ay, hh) - nodDy(NOD.hairBack, ay, hh))
|
|
3295
|
+
);
|
|
3296
|
+
const band = [];
|
|
3297
|
+
for (let j = b0; j < b1; j++) band.push(backAt(j));
|
|
3298
|
+
const faces = [];
|
|
3299
|
+
const [f0, f1] = passed(
|
|
3300
|
+
i,
|
|
3301
|
+
ays.map((ay) => nodDy(nod, ay, hh) - face(f.axisX, y, 0, ay)[1])
|
|
3302
|
+
);
|
|
3303
|
+
for (let j = f0; j < f1; j++)
|
|
3304
|
+
faces.push({ y: top - j - 0.5, runs: faceAt(j) });
|
|
3305
|
+
const front = f.hairRuns("hair_front", y);
|
|
3306
|
+
[-1, 1].forEach((s, side) => {
|
|
3307
|
+
let least = Infinity;
|
|
3308
|
+
const far = outerEdge(front, f.axisX, s);
|
|
3309
|
+
if (far !== void 0) {
|
|
3310
|
+
const R = cellR(far, y, side);
|
|
3311
|
+
const c = Math.min(...band.map((b) => runReach(b, far, s).outward));
|
|
3312
|
+
if (R > 0) least = Math.min(least, m.shellScale * c / R);
|
|
3313
|
+
}
|
|
3314
|
+
for (let t = s > 0 ? 0 : 1; t < front.length; t += 2) {
|
|
3315
|
+
const x = front[t];
|
|
3316
|
+
const R = cellR(x, y, side);
|
|
3317
|
+
if (R <= 0) continue;
|
|
3318
|
+
let c = Math.min(
|
|
3319
|
+
...band.map((b) => runReach(b, x, s > 0 ? -1 : 1).inward)
|
|
3320
|
+
);
|
|
3321
|
+
const sh = (m.shellScale - 1) * (x - f.axisX);
|
|
3322
|
+
const d0 = sh + s * m.hairBack;
|
|
3323
|
+
const d1 = d0 + s * R;
|
|
3324
|
+
const lo = Math.min(d0, d1);
|
|
3325
|
+
const hi = Math.max(d0, d1);
|
|
3326
|
+
const bares = faces.some(({ y: fy, runs }) => {
|
|
3327
|
+
for (let r = 0; r < runs.length; r += 2) {
|
|
3328
|
+
const e0 = runs[r];
|
|
3329
|
+
const e1 = runs[r + 1];
|
|
3330
|
+
const de0 = face(e0, fy, 30 * s, 0)[0];
|
|
3331
|
+
const de1 = face(e1, fy, 30 * s, 0)[0];
|
|
3332
|
+
if (someAngle([
|
|
3333
|
+
[e1 - x, de1 - lo],
|
|
3334
|
+
[x - e0, hi - de0]
|
|
3335
|
+
])) {
|
|
3336
|
+
return true;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
return false;
|
|
3340
|
+
});
|
|
3341
|
+
if (bares) c = 0;
|
|
3342
|
+
least = Math.min(least, m.shellScale * c / R);
|
|
3343
|
+
}
|
|
3344
|
+
slack[side][i] = least - w;
|
|
3345
|
+
});
|
|
3346
|
+
return slack[k][i];
|
|
3347
|
+
};
|
|
3348
|
+
const extra = [0, 1].map((k) => {
|
|
3349
|
+
const out = wr.slice();
|
|
3350
|
+
for (let r = 0; r < n; r++) {
|
|
3351
|
+
if (wr[r] >= 1) continue;
|
|
3352
|
+
const i0 = Math.max(0, Math.floor(top - ys[Math.max(0, r - 1)]));
|
|
3353
|
+
const i1 = Math.min(count, Math.ceil(top - ys[Math.min(n - 1, r + 1)]));
|
|
3354
|
+
let least = Infinity;
|
|
3355
|
+
for (let i = i0; i < i1; i++) least = Math.min(least, slackAt(i, k));
|
|
3356
|
+
out[r] = wr[r] + clamp(least, 0, 1 - wr[r]);
|
|
3357
|
+
}
|
|
3358
|
+
for (let r = n - 2; r >= 0; r--) {
|
|
3359
|
+
if (wr[r] < 1) out[r] = Math.max(wr[r], Math.min(out[r], out[r + 1]));
|
|
3360
|
+
}
|
|
3361
|
+
return out.map((v, r) => v - wr[r]);
|
|
3362
|
+
});
|
|
3363
|
+
return (y, s) => {
|
|
3364
|
+
if (s === 0) return 0;
|
|
3365
|
+
const e = extra[s < 0 ? 0 : 1];
|
|
3366
|
+
const { r, t } = gridRow(ys, y, true);
|
|
3367
|
+
return e[r] * (1 - t) + e[r + 1] * t;
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3208
3370
|
function familyField(m, family) {
|
|
3209
3371
|
switch (family) {
|
|
3210
3372
|
case "face":
|