@ikijs/editor 0.7.0 → 0.8.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 +6 -1
- package/dist/index.d.mts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +71 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +71 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,7 +123,12 @@ decodes with canvas, `@ikijs/mcp` decodes with `sharp`, and both feed the same
|
|
|
123
123
|
pure function — including the optional `rowHalfWidths` (one entry per crop row,
|
|
124
124
|
half that row's opaque span in canvas px), which `@ikijs/mcp` measures for the
|
|
125
125
|
face so the plate turns on a radius that varies by row; absent, it turns on one
|
|
126
|
-
constant radius.
|
|
126
|
+
constant radius. It also takes the optional `denseCore`, the tight box of a
|
|
127
|
+
layer's alpha ≥ 128 pixels in image coords, not grown, which `@ikijs/mcp`
|
|
128
|
+
measures for the nose: its centre and width are the nose's turn landmark, so a
|
|
129
|
+
shaded nose is fitted by its drawing rather than its soft feather, and its top
|
|
130
|
+
edge at its centre x is the bridge top the nose tilts 6° about on the turn, its
|
|
131
|
+
tip toward the far side; absent, the crop stands in for both.
|
|
127
132
|
|
|
128
133
|
`*_L` / `*_R` are the **character's** sides — `eye_L` is the character's left
|
|
129
134
|
eye, which appears on the viewer's right.
|
package/dist/index.d.mts
CHANGED
|
@@ -888,6 +888,24 @@ interface LayerInput {
|
|
|
888
888
|
* [0, cropW / 2], the most an inclusive span inside the crop can be.
|
|
889
889
|
*/
|
|
890
890
|
rowHalfWidths?: number[];
|
|
891
|
+
/**
|
|
892
|
+
* The tight box of the layer's alpha ≥ 128 pixels, in image coords like
|
|
893
|
+
* `bbox` and NOT grown: the drawing a viewer reads inside a soft feather,
|
|
894
|
+
* which the crop's alpha ≥ 8 rule takes in whole. Optional, and only the
|
|
895
|
+
* NOSE's is read: its centre and width are the nose's turn landmark
|
|
896
|
+
* (`turnLandmarks`), so a shaded nose is fitted by the drawing rather than
|
|
897
|
+
* by its feather, and its top edge at its centre x is the bridge top the
|
|
898
|
+
* nose tilts about on the turn (`NOSE_TURN_TILT_DEG`). Absent — as the
|
|
899
|
+
* editor's own import leaves it — the crop stands in for both. Validated
|
|
900
|
+
* before anything reads it: a plain object whose x, y, w and h are finite,
|
|
901
|
+
* w and h positive, and the box inside `bbox`.
|
|
902
|
+
*/
|
|
903
|
+
denseCore?: {
|
|
904
|
+
x: number;
|
|
905
|
+
y: number;
|
|
906
|
+
w: number;
|
|
907
|
+
h: number;
|
|
908
|
+
};
|
|
891
909
|
}
|
|
892
910
|
/**
|
|
893
911
|
* Map an array of raw filenames to canonical `{ role, fileName }` pairs.
|
package/dist/index.d.ts
CHANGED
|
@@ -888,6 +888,24 @@ interface LayerInput {
|
|
|
888
888
|
* [0, cropW / 2], the most an inclusive span inside the crop can be.
|
|
889
889
|
*/
|
|
890
890
|
rowHalfWidths?: number[];
|
|
891
|
+
/**
|
|
892
|
+
* The tight box of the layer's alpha ≥ 128 pixels, in image coords like
|
|
893
|
+
* `bbox` and NOT grown: the drawing a viewer reads inside a soft feather,
|
|
894
|
+
* which the crop's alpha ≥ 8 rule takes in whole. Optional, and only the
|
|
895
|
+
* NOSE's is read: its centre and width are the nose's turn landmark
|
|
896
|
+
* (`turnLandmarks`), so a shaded nose is fitted by the drawing rather than
|
|
897
|
+
* by its feather, and its top edge at its centre x is the bridge top the
|
|
898
|
+
* nose tilts about on the turn (`NOSE_TURN_TILT_DEG`). Absent — as the
|
|
899
|
+
* editor's own import leaves it — the crop stands in for both. Validated
|
|
900
|
+
* before anything reads it: a plain object whose x, y, w and h are finite,
|
|
901
|
+
* w and h positive, and the box inside `bbox`.
|
|
902
|
+
*/
|
|
903
|
+
denseCore?: {
|
|
904
|
+
x: number;
|
|
905
|
+
y: number;
|
|
906
|
+
w: number;
|
|
907
|
+
h: number;
|
|
908
|
+
};
|
|
891
909
|
}
|
|
892
910
|
/**
|
|
893
911
|
* Map an array of raw filenames to canonical `{ role, fileName }` pairs.
|
package/dist/index.js
CHANGED
|
@@ -1837,6 +1837,45 @@ function validateLayerInputs(layers, canvas) {
|
|
|
1837
1837
|
}
|
|
1838
1838
|
}
|
|
1839
1839
|
}
|
|
1840
|
+
if (layer.denseCore !== void 0) {
|
|
1841
|
+
const where = `auto-rig: validateLayerInputs: role "${role}" denseCore`;
|
|
1842
|
+
const core = layer.denseCore;
|
|
1843
|
+
if (typeof core !== "object" || core === null || Array.isArray(core)) {
|
|
1844
|
+
throw new Error(`${where} must be a plain object { x, y, w, h }`);
|
|
1845
|
+
}
|
|
1846
|
+
for (const field of ["x", "y", "w", "h"]) {
|
|
1847
|
+
const v = core[field];
|
|
1848
|
+
const size = field === "w" || field === "h";
|
|
1849
|
+
if (typeof v !== "number" || !Number.isFinite(v) || size && v <= 0) {
|
|
1850
|
+
throw new Error(
|
|
1851
|
+
`${where}.${field} is ${String(v)}, not a finite number${size ? " > 0" : ""}`
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
const { x, y, w, h } = layer.denseCore;
|
|
1856
|
+
const right = bbox.x + bbox.w;
|
|
1857
|
+
const bottom = bbox.y + bbox.h;
|
|
1858
|
+
if (x < bbox.x) {
|
|
1859
|
+
throw new Error(
|
|
1860
|
+
`${where}.x (${x}) lies left of the bbox's left edge (${bbox.x})`
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
if (y < bbox.y) {
|
|
1864
|
+
throw new Error(
|
|
1865
|
+
`${where}.y (${y}) lies above the bbox's top edge (${bbox.y})`
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
if (x + w > right) {
|
|
1869
|
+
throw new Error(
|
|
1870
|
+
`${where}.w (${w}) puts its right edge at ${x + w}, past the bbox's right edge (${right})`
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
if (y + h > bottom) {
|
|
1874
|
+
throw new Error(
|
|
1875
|
+
`${where}.h (${h}) puts its bottom edge at ${y + h}, past the bbox's bottom edge (${bottom})`
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1840
1879
|
}
|
|
1841
1880
|
}
|
|
1842
1881
|
function validateStrandEdges(layers, strandEdges) {
|
|
@@ -2391,12 +2430,17 @@ function resolveTurnTargets(targets = {}) {
|
|
|
2391
2430
|
defaulted
|
|
2392
2431
|
};
|
|
2393
2432
|
}
|
|
2433
|
+
function noseDrawnBox(layer) {
|
|
2434
|
+
return layer.denseCore ?? layer.bbox;
|
|
2435
|
+
}
|
|
2394
2436
|
function turnLandmarks(layers, carriers) {
|
|
2395
2437
|
const markOf = (role) => {
|
|
2396
2438
|
const layer = layers.find((l) => l.role === role);
|
|
2397
2439
|
if (!layer) return void 0;
|
|
2398
|
-
const
|
|
2399
|
-
const
|
|
2440
|
+
const drawn = role === "nose" ? noseDrawnBox(layer) : layer.bbox;
|
|
2441
|
+
const t = bboxToTransform(drawn, layer.canvasW, layer.canvasH, role);
|
|
2442
|
+
const w = drawn === layer.bbox ? layer.cropW : drawn.w;
|
|
2443
|
+
const mark = { x: t.x, y: t.y, w };
|
|
2400
2444
|
const carrier = carriers?.get(role);
|
|
2401
2445
|
if (carrier !== void 0) mark.carrier = carrier;
|
|
2402
2446
|
return mark;
|
|
@@ -3347,6 +3391,7 @@ function solveTurnModel(targets, landmarks, lattice, faceCenterX, faceHalfWidth,
|
|
|
3347
3391
|
var EYE_STACK_PREFIXES = ["eye_", "iris_", "pupil_", "highlight_"];
|
|
3348
3392
|
var HAIR_FRONT_DEPTH = 0.1;
|
|
3349
3393
|
var MOUTH_TURN_TILT_DEG = 5;
|
|
3394
|
+
var NOSE_TURN_TILT_DEG = 6;
|
|
3350
3395
|
var NOD_TRAVEL = 30;
|
|
3351
3396
|
var NOD_BEND = 0.5;
|
|
3352
3397
|
var HAIR_BACK_NOD_DEPTH = -0.135;
|
|
@@ -3959,11 +4004,31 @@ function turnSetup(layers) {
|
|
|
3959
4004
|
y: mouthAt.y,
|
|
3960
4005
|
tiltDeg: MOUTH_TURN_TILT_DEG
|
|
3961
4006
|
};
|
|
4007
|
+
const noseLayer = layers.find((l) => l.role === "nose");
|
|
4008
|
+
let noseAnchor;
|
|
4009
|
+
if (noseLayer) {
|
|
4010
|
+
const drawn = noseDrawnBox(noseLayer);
|
|
4011
|
+
const at = bboxToTransform(
|
|
4012
|
+
drawn,
|
|
4013
|
+
noseLayer.canvasW,
|
|
4014
|
+
noseLayer.canvasH,
|
|
4015
|
+
"nose"
|
|
4016
|
+
);
|
|
4017
|
+
noseAnchor = {
|
|
4018
|
+
x: at.x,
|
|
4019
|
+
y: at.y + drawn.h / 2,
|
|
4020
|
+
tiltDeg: NOSE_TURN_TILT_DEG
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
3962
4023
|
const carriers = new Map(
|
|
3963
|
-
members.map((m) =>
|
|
3964
|
-
m.
|
|
3965
|
-
m.group === "mouthWarp" ?
|
|
3966
|
-
|
|
4024
|
+
members.map((m) => {
|
|
4025
|
+
const grid = groupGrids.get(m.group);
|
|
4026
|
+
const anchor = m.group === "mouthWarp" ? mouthAnchor : m.group === "noseWarp" ? noseAnchor : void 0;
|
|
4027
|
+
return [
|
|
4028
|
+
m.role,
|
|
4029
|
+
anchor === void 0 ? { grid, part: m.part } : { grid, part: m.part, anchor }
|
|
4030
|
+
];
|
|
4031
|
+
})
|
|
3967
4032
|
);
|
|
3968
4033
|
const maxShift = Math.max(turnShift.eye, turnShift.nose, turnShift.mouth);
|
|
3969
4034
|
let reach = 0;
|