diff --git a/.gitignore b/.gitignore index 2ce1318..0dcac88 100644 --- a/.gitignore +++ b/.gitignore @@ -71,12 +71,3 @@ infra/.tmp-*.b64 # 아바타 v3 아트 파이프라인: 내려받는 MediaPipe 모델과 다시 만들 수 있는 진단 PNG docs/avatar-art/*/scripts/_models/ docs/avatar-art/*/preview/**/*.png - -# 리노컷 캐스트 원화 작업 중간물 — 원화 후보·codex 편집 원본·정렬본·패딩본·분할 레이어는 로컬에만 둔다. -# 저장소에는 파이프라인 입력(정규화된 base-front·base-faceless·raw/body)과 설정·측정·증거 JPG만 남긴다 -# (P1은 이 규칙 이전에 레이어까지 추적됐고 그대로 둔다). -docs/avatar-art/p[2-9]-linocut/base/candidates/ -docs/avatar-art/p[2-9]-linocut/base/source-front.png -docs/avatar-art/p[2-9]-linocut/base/base-faceless-padded.png -docs/avatar-art/p[2-9]-linocut/base/edits/*.png -docs/avatar-art/p[2-9]-linocut/layers/ diff --git a/apps/web/e2e/avatar-expression-lab.spec.ts b/apps/web/e2e/avatar-expression-lab.spec.ts index 42b7bee..0b7b790 100644 --- a/apps/web/e2e/avatar-expression-lab.spec.ts +++ b/apps/web/e2e/avatar-expression-lab.spec.ts @@ -53,35 +53,26 @@ test.describe("avatar expression lab", () => { ); await expect(panel.locator('[data-expression-card="true"]')).toHaveCount(28); - const cardRenderers = panel.locator( - '.axl__card .vg-avatar [data-avatar-renderer="linocut"]', - ); - await expect(cardRenderers).toHaveCount(28); - await expect - .poll( - async () => - cardRenderers.evaluateAll( - (els) => els.filter((el) => el.getAttribute("data-load-state") === "ready").length, - ), - { timeout: 20_000 }, - ) - .toBe(28); - const sectionMetrics = await panel.evaluate((el) => { const avatars = Array.from(el.querySelectorAll(".axl__card .vg-avatar")); const invalid = avatars .map((avatar) => { - const renderer = avatar.querySelector('[data-avatar-renderer="linocut"]'); - const svg = avatar.querySelector(".linocut-avatar"); + const neck = avatar.querySelector('[data-avatar-neck="true"]'); + const svg = avatar.querySelector("svg"); return { affect: avatar.getAttribute("data-affect"), - loadState: renderer?.getAttribute("data-load-state"), + primitives: avatar.querySelectorAll("svg path, svg ellipse, svg circle, svg line, svg rect") + .length, + neckBox: neck?.getBoundingClientRect().toJSON(), svgBox: svg?.getBoundingClientRect().toJSON(), }; }) .filter((item) => { return ( - item.loadState !== "ready" || + item.primitives < 12 || + !item.neckBox || + item.neckBox.width <= 8 || + item.neckBox.height <= 14 || !item.svgBox || item.svgBox.width <= 0 || item.svgBox.height <= 0 diff --git a/apps/web/e2e/avatar-expression.spec.ts b/apps/web/e2e/avatar-expression.spec.ts index 84b1e17..1262eaa 100644 --- a/apps/web/e2e/avatar-expression.spec.ts +++ b/apps/web/e2e/avatar-expression.spec.ts @@ -197,10 +197,6 @@ test.describe("persona avatar expression rig", () => { await expect(avatar).toBeVisible(); await expect(avatar).toHaveAttribute("data-affect", persona.expectedExpression); - const v3 = avatar.locator('[data-avatar-renderer="linocut"]'); - await expect(v3).toHaveAttribute("data-load-state", "ready", { timeout: 15_000 }); - await expect(v3.locator(".linocut-avatar")).toBeVisible(); - const metrics = await avatar.evaluate((el) => ({ expressionCount: Number(el.getAttribute("data-expression-count")), live2dSchema: el.getAttribute("data-live2d-schema"), @@ -211,7 +207,10 @@ test.describe("persona avatar expression rig", () => { live2dExpressionCount: Number(el.getAttribute("data-live2d-expression-count")), renderMode: el.getAttribute("data-render-mode"), rasterCount: el.querySelectorAll(".vg-raster").length, - v3Box: el.querySelector(".linocut-avatar")?.getBoundingClientRect().toJSON(), + primitiveCount: el.querySelectorAll("svg path, svg ellipse, svg circle, svg line, svg rect") + .length, + neckBox: el.querySelector('[data-avatar-neck="true"]')?.getBoundingClientRect().toJSON(), + svgBox: el.querySelector("svg")?.getBoundingClientRect().toJSON(), })); expect(metrics.expressionCount, `${persona.code} expression count`).toBeGreaterThanOrEqual(20); expect(metrics.live2dSchema, `${persona.code} Live2D schema`).toBe("vignette.live2d.v1"); @@ -226,8 +225,10 @@ test.describe("persona avatar expression rig", () => { expect(metrics.live2dExpressionCount, `${persona.code} Live2D expressions`).toBeGreaterThanOrEqual(20); expect(metrics.renderMode, `${persona.code} render mode`).toBe("svg"); expect(metrics.rasterCount, `${persona.code} raster rig should be disabled`).toBe(0); - expect(metrics.v3Box?.width, `${persona.code} linocut SVG`).toBeGreaterThan(0); - expect(metrics.v3Box?.height, `${persona.code} linocut SVG`).toBeGreaterThan(0); + expect(metrics.primitiveCount, `${persona.code} SVG primitives`).toBeGreaterThan(10); + expect(metrics.neckBox?.width, `${persona.code} SVG neck`).toBeGreaterThan(0); + expect(metrics.svgBox?.width, `${persona.code} SVG rig`).toBeGreaterThan(0); + expect(metrics.svgBox?.height, `${persona.code} SVG rig`).toBeGreaterThan(0); } }); @@ -260,10 +261,9 @@ test.describe("persona avatar expression rig", () => { const activeAvatar = page.locator(".sx-page--active .vg-avatar").first(); await expect(activeAvatar).toBeVisible(); - const activeV3 = activeAvatar.locator('[data-avatar-renderer="linocut"]'); - await expect(activeV3).toHaveAttribute("data-load-state", "ready", { timeout: 15_000 }); - await expect(activeV3.locator(".linocut-avatar")).toBeVisible(); + await expect(activeAvatar).toHaveAttribute("data-render-mode", "svg"); await expect(activeAvatar.locator(".vg-raster")).toHaveCount(0); + await expect(activeAvatar.locator(".vg-avatar__svg")).toBeVisible(); await expect(activeAvatar).toHaveAttribute("data-expression-count", "28"); await expect(activeAvatar).toHaveAttribute("data-live2d-expression-count", "28"); await expect(activeAvatar).toHaveAttribute("data-live2d-model", "vignette-p4-live2d"); @@ -316,10 +316,9 @@ test.describe("persona avatar expression rig", () => { await page.getByRole("button", { name: "회기 시작" }).click(); await expect(page.locator(".sx-page--active")).toBeVisible(); const activeAvatar = page.locator(".sx-page--active .vg-avatar").first(); - const activeV3 = activeAvatar.locator('[data-avatar-renderer="linocut"]'); - await expect(activeV3).toHaveAttribute("data-load-state", "ready", { timeout: 15_000 }); - await expect(activeV3.locator(".linocut-avatar")).toBeVisible(); + await expect(activeAvatar).toHaveAttribute("data-render-mode", "svg"); await expect(activeAvatar.locator(".vg-raster")).toHaveCount(0); + await expect(activeAvatar.locator(".vg-avatar__svg")).toBeVisible(); await expect(activeAvatar).toHaveAttribute("data-live2d-motion", "anxious"); await setLearnerUtterance(page, "조금 안정된 것 같아요."); diff --git a/apps/web/public/avatar/v3/p2/body.webp b/apps/web/public/avatar/v3/p2/body.webp deleted file mode 100644 index 391c563..0000000 Binary files a/apps/web/public/avatar/v3/p2/body.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/brow-left.webp b/apps/web/public/avatar/v3/p2/brow-left.webp deleted file mode 100644 index 8291391..0000000 Binary files a/apps/web/public/avatar/v3/p2/brow-left.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/brow-right.webp b/apps/web/public/avatar/v3/p2/brow-right.webp deleted file mode 100644 index abb1aaa..0000000 Binary files a/apps/web/public/avatar/v3/p2/brow-right.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/face-detail.webp b/apps/web/public/avatar/v3/p2/face-detail.webp deleted file mode 100644 index 5383556..0000000 Binary files a/apps/web/public/avatar/v3/p2/face-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/hair-front.webp b/apps/web/public/avatar/v3/p2/hair-front.webp deleted file mode 100644 index 6b0dc53..0000000 Binary files a/apps/web/public/avatar/v3/p2/hair-front.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/head.webp b/apps/web/public/avatar/v3/p2/head.webp deleted file mode 100644 index b87cdb7..0000000 Binary files a/apps/web/public/avatar/v3/p2/head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/jaw-detail.webp b/apps/web/public/avatar/v3/p2/jaw-detail.webp deleted file mode 100644 index 4d80292..0000000 Binary files a/apps/web/public/avatar/v3/p2/jaw-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/jaw-head.webp b/apps/web/public/avatar/v3/p2/jaw-head.webp deleted file mode 100644 index d04bde7..0000000 Binary files a/apps/web/public/avatar/v3/p2/jaw-head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/lip-lower.webp b/apps/web/public/avatar/v3/p2/lip-lower.webp deleted file mode 100644 index 5fc702d..0000000 Binary files a/apps/web/public/avatar/v3/p2/lip-lower.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/lip-shadow.webp b/apps/web/public/avatar/v3/p2/lip-shadow.webp deleted file mode 100644 index 62b17a0..0000000 Binary files a/apps/web/public/avatar/v3/p2/lip-shadow.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/lip-upper.webp b/apps/web/public/avatar/v3/p2/lip-upper.webp deleted file mode 100644 index 8199c13..0000000 Binary files a/apps/web/public/avatar/v3/p2/lip-upper.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p2/paper-grain.webp b/apps/web/public/avatar/v3/p2/paper-grain.webp deleted file mode 100644 index 305bb90..0000000 Binary files a/apps/web/public/avatar/v3/p2/paper-grain.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/body.webp b/apps/web/public/avatar/v3/p3/body.webp deleted file mode 100644 index 0ce7d30..0000000 Binary files a/apps/web/public/avatar/v3/p3/body.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/brow-left.webp b/apps/web/public/avatar/v3/p3/brow-left.webp deleted file mode 100644 index cda8bf7..0000000 Binary files a/apps/web/public/avatar/v3/p3/brow-left.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/brow-right.webp b/apps/web/public/avatar/v3/p3/brow-right.webp deleted file mode 100644 index a3a3b5e..0000000 Binary files a/apps/web/public/avatar/v3/p3/brow-right.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/face-detail.webp b/apps/web/public/avatar/v3/p3/face-detail.webp deleted file mode 100644 index 86d4b50..0000000 Binary files a/apps/web/public/avatar/v3/p3/face-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/hair-front.webp b/apps/web/public/avatar/v3/p3/hair-front.webp deleted file mode 100644 index abb1b18..0000000 Binary files a/apps/web/public/avatar/v3/p3/hair-front.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/head.webp b/apps/web/public/avatar/v3/p3/head.webp deleted file mode 100644 index 5c64bef..0000000 Binary files a/apps/web/public/avatar/v3/p3/head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/jaw-detail.webp b/apps/web/public/avatar/v3/p3/jaw-detail.webp deleted file mode 100644 index 8f701d3..0000000 Binary files a/apps/web/public/avatar/v3/p3/jaw-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/jaw-head.webp b/apps/web/public/avatar/v3/p3/jaw-head.webp deleted file mode 100644 index 4b114f7..0000000 Binary files a/apps/web/public/avatar/v3/p3/jaw-head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/lip-lower.webp b/apps/web/public/avatar/v3/p3/lip-lower.webp deleted file mode 100644 index 636486a..0000000 Binary files a/apps/web/public/avatar/v3/p3/lip-lower.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/lip-shadow.webp b/apps/web/public/avatar/v3/p3/lip-shadow.webp deleted file mode 100644 index 0d1d7d7..0000000 Binary files a/apps/web/public/avatar/v3/p3/lip-shadow.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/lip-upper.webp b/apps/web/public/avatar/v3/p3/lip-upper.webp deleted file mode 100644 index 9adec63..0000000 Binary files a/apps/web/public/avatar/v3/p3/lip-upper.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p3/paper-grain.webp b/apps/web/public/avatar/v3/p3/paper-grain.webp deleted file mode 100644 index c788e40..0000000 Binary files a/apps/web/public/avatar/v3/p3/paper-grain.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/body.webp b/apps/web/public/avatar/v3/p4/body.webp deleted file mode 100644 index 5d5fca0..0000000 Binary files a/apps/web/public/avatar/v3/p4/body.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/brow-left.webp b/apps/web/public/avatar/v3/p4/brow-left.webp deleted file mode 100644 index 41c6802..0000000 Binary files a/apps/web/public/avatar/v3/p4/brow-left.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/brow-right.webp b/apps/web/public/avatar/v3/p4/brow-right.webp deleted file mode 100644 index 2ea8b25..0000000 Binary files a/apps/web/public/avatar/v3/p4/brow-right.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/face-detail.webp b/apps/web/public/avatar/v3/p4/face-detail.webp deleted file mode 100644 index 7f549b0..0000000 Binary files a/apps/web/public/avatar/v3/p4/face-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/hair-front.webp b/apps/web/public/avatar/v3/p4/hair-front.webp deleted file mode 100644 index 69c27f8..0000000 Binary files a/apps/web/public/avatar/v3/p4/hair-front.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/head.webp b/apps/web/public/avatar/v3/p4/head.webp deleted file mode 100644 index b4cead5..0000000 Binary files a/apps/web/public/avatar/v3/p4/head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/jaw-detail.webp b/apps/web/public/avatar/v3/p4/jaw-detail.webp deleted file mode 100644 index 9d2df37..0000000 Binary files a/apps/web/public/avatar/v3/p4/jaw-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/jaw-head.webp b/apps/web/public/avatar/v3/p4/jaw-head.webp deleted file mode 100644 index 78853bf..0000000 Binary files a/apps/web/public/avatar/v3/p4/jaw-head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/lip-lower.webp b/apps/web/public/avatar/v3/p4/lip-lower.webp deleted file mode 100644 index d688a98..0000000 Binary files a/apps/web/public/avatar/v3/p4/lip-lower.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/lip-shadow.webp b/apps/web/public/avatar/v3/p4/lip-shadow.webp deleted file mode 100644 index bd16975..0000000 Binary files a/apps/web/public/avatar/v3/p4/lip-shadow.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/lip-upper.webp b/apps/web/public/avatar/v3/p4/lip-upper.webp deleted file mode 100644 index 15e43ff..0000000 Binary files a/apps/web/public/avatar/v3/p4/lip-upper.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p4/paper-grain.webp b/apps/web/public/avatar/v3/p4/paper-grain.webp deleted file mode 100644 index cd0eeb1..0000000 Binary files a/apps/web/public/avatar/v3/p4/paper-grain.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/body.webp b/apps/web/public/avatar/v3/p5/body.webp deleted file mode 100644 index 9bc0a21..0000000 Binary files a/apps/web/public/avatar/v3/p5/body.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/brow-left.webp b/apps/web/public/avatar/v3/p5/brow-left.webp deleted file mode 100644 index c87d599..0000000 Binary files a/apps/web/public/avatar/v3/p5/brow-left.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/brow-right.webp b/apps/web/public/avatar/v3/p5/brow-right.webp deleted file mode 100644 index ec8627f..0000000 Binary files a/apps/web/public/avatar/v3/p5/brow-right.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/face-detail.webp b/apps/web/public/avatar/v3/p5/face-detail.webp deleted file mode 100644 index 7621782..0000000 Binary files a/apps/web/public/avatar/v3/p5/face-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/hair-front.webp b/apps/web/public/avatar/v3/p5/hair-front.webp deleted file mode 100644 index aea0a1c..0000000 Binary files a/apps/web/public/avatar/v3/p5/hair-front.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/head.webp b/apps/web/public/avatar/v3/p5/head.webp deleted file mode 100644 index 66d6fbc..0000000 Binary files a/apps/web/public/avatar/v3/p5/head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/jaw-detail.webp b/apps/web/public/avatar/v3/p5/jaw-detail.webp deleted file mode 100644 index 478dc07..0000000 Binary files a/apps/web/public/avatar/v3/p5/jaw-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/jaw-head.webp b/apps/web/public/avatar/v3/p5/jaw-head.webp deleted file mode 100644 index 559d8a3..0000000 Binary files a/apps/web/public/avatar/v3/p5/jaw-head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/lip-lower.webp b/apps/web/public/avatar/v3/p5/lip-lower.webp deleted file mode 100644 index 1ff4eda..0000000 Binary files a/apps/web/public/avatar/v3/p5/lip-lower.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/lip-shadow.webp b/apps/web/public/avatar/v3/p5/lip-shadow.webp deleted file mode 100644 index 731338f..0000000 Binary files a/apps/web/public/avatar/v3/p5/lip-shadow.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/lip-upper.webp b/apps/web/public/avatar/v3/p5/lip-upper.webp deleted file mode 100644 index ba1bc24..0000000 Binary files a/apps/web/public/avatar/v3/p5/lip-upper.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p5/paper-grain.webp b/apps/web/public/avatar/v3/p5/paper-grain.webp deleted file mode 100644 index d2e49af..0000000 Binary files a/apps/web/public/avatar/v3/p5/paper-grain.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/body.webp b/apps/web/public/avatar/v3/p6/body.webp deleted file mode 100644 index 3fef69e..0000000 Binary files a/apps/web/public/avatar/v3/p6/body.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/brow-left.webp b/apps/web/public/avatar/v3/p6/brow-left.webp deleted file mode 100644 index 14d3956..0000000 Binary files a/apps/web/public/avatar/v3/p6/brow-left.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/brow-right.webp b/apps/web/public/avatar/v3/p6/brow-right.webp deleted file mode 100644 index 63fb879..0000000 Binary files a/apps/web/public/avatar/v3/p6/brow-right.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/face-detail.webp b/apps/web/public/avatar/v3/p6/face-detail.webp deleted file mode 100644 index 7674265..0000000 Binary files a/apps/web/public/avatar/v3/p6/face-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/hair-front.webp b/apps/web/public/avatar/v3/p6/hair-front.webp deleted file mode 100644 index 5a0460b..0000000 Binary files a/apps/web/public/avatar/v3/p6/hair-front.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/head.webp b/apps/web/public/avatar/v3/p6/head.webp deleted file mode 100644 index bd08906..0000000 Binary files a/apps/web/public/avatar/v3/p6/head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/jaw-detail.webp b/apps/web/public/avatar/v3/p6/jaw-detail.webp deleted file mode 100644 index f080491..0000000 Binary files a/apps/web/public/avatar/v3/p6/jaw-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/jaw-head.webp b/apps/web/public/avatar/v3/p6/jaw-head.webp deleted file mode 100644 index 4d4c3d8..0000000 Binary files a/apps/web/public/avatar/v3/p6/jaw-head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/lip-lower.webp b/apps/web/public/avatar/v3/p6/lip-lower.webp deleted file mode 100644 index f0250b8..0000000 Binary files a/apps/web/public/avatar/v3/p6/lip-lower.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/lip-shadow.webp b/apps/web/public/avatar/v3/p6/lip-shadow.webp deleted file mode 100644 index 1c3dd2d..0000000 Binary files a/apps/web/public/avatar/v3/p6/lip-shadow.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/lip-upper.webp b/apps/web/public/avatar/v3/p6/lip-upper.webp deleted file mode 100644 index 243b50b..0000000 Binary files a/apps/web/public/avatar/v3/p6/lip-upper.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p6/paper-grain.webp b/apps/web/public/avatar/v3/p6/paper-grain.webp deleted file mode 100644 index 4191067..0000000 Binary files a/apps/web/public/avatar/v3/p6/paper-grain.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/body.webp b/apps/web/public/avatar/v3/p7/body.webp deleted file mode 100644 index 47cf9d9..0000000 Binary files a/apps/web/public/avatar/v3/p7/body.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/brow-left.webp b/apps/web/public/avatar/v3/p7/brow-left.webp deleted file mode 100644 index 5312a76..0000000 Binary files a/apps/web/public/avatar/v3/p7/brow-left.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/brow-right.webp b/apps/web/public/avatar/v3/p7/brow-right.webp deleted file mode 100644 index fec940d..0000000 Binary files a/apps/web/public/avatar/v3/p7/brow-right.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/face-detail.webp b/apps/web/public/avatar/v3/p7/face-detail.webp deleted file mode 100644 index 4f7abbc..0000000 Binary files a/apps/web/public/avatar/v3/p7/face-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/hair-front.webp b/apps/web/public/avatar/v3/p7/hair-front.webp deleted file mode 100644 index 7a72966..0000000 Binary files a/apps/web/public/avatar/v3/p7/hair-front.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/head.webp b/apps/web/public/avatar/v3/p7/head.webp deleted file mode 100644 index 3c715ca..0000000 Binary files a/apps/web/public/avatar/v3/p7/head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/jaw-detail.webp b/apps/web/public/avatar/v3/p7/jaw-detail.webp deleted file mode 100644 index 701e8ac..0000000 Binary files a/apps/web/public/avatar/v3/p7/jaw-detail.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/jaw-head.webp b/apps/web/public/avatar/v3/p7/jaw-head.webp deleted file mode 100644 index c90fc74..0000000 Binary files a/apps/web/public/avatar/v3/p7/jaw-head.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/lip-lower.webp b/apps/web/public/avatar/v3/p7/lip-lower.webp deleted file mode 100644 index 67b5176..0000000 Binary files a/apps/web/public/avatar/v3/p7/lip-lower.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/lip-shadow.webp b/apps/web/public/avatar/v3/p7/lip-shadow.webp deleted file mode 100644 index eabf657..0000000 Binary files a/apps/web/public/avatar/v3/p7/lip-shadow.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/lip-upper.webp b/apps/web/public/avatar/v3/p7/lip-upper.webp deleted file mode 100644 index 14428b0..0000000 Binary files a/apps/web/public/avatar/v3/p7/lip-upper.webp and /dev/null differ diff --git a/apps/web/public/avatar/v3/p7/paper-grain.webp b/apps/web/public/avatar/v3/p7/paper-grain.webp deleted file mode 100644 index aeaea47..0000000 Binary files a/apps/web/public/avatar/v3/p7/paper-grain.webp and /dev/null differ diff --git a/apps/web/src/components/avatar/engine/demeanorDefaults.ts b/apps/web/src/components/avatar/engine/demeanorDefaults.ts index 56a30b5..658309c 100644 --- a/apps/web/src/components/avatar/engine/demeanorDefaults.ts +++ b/apps/web/src/components/avatar/engine/demeanorDefaults.ts @@ -88,11 +88,9 @@ const P2 = demeanor({ speech: { articulation: 0.85, syllablesPerSec: 6.4, gesture: 0.9, gazeAvertProb: 0.5 }, }); -/* P3 지우 — 28세 미혼모, 역할 부담, 따뜻하지만 지친 톤. - 눈 감김(eyeOpen)은 −0.15에서 −0.05로 줄였다(2026-10-01): 리노컷 원화가 지친 눈매를 이미 - 그리고 있어 기본 자세로 더 감기면 원화보다 졸린 눈이 된다(결정문 §8.4.1 "중립 = 원화"). */ +/* P3 지우 — 28세 미혼모, 역할 부담, 따뜻하지만 지친 톤. */ const P3 = demeanor({ - basePose: { eyeOpen: -0.05, eyeSmile: 0.08, shoulderRaise: -0.15 }, + basePose: { eyeOpen: -0.15, eyeSmile: 0.08, shoulderRaise: -0.15 }, expressivity: 0.9, blinkIntervalMs: [2200, 4600], blinkDurationMs: 140, @@ -145,10 +143,9 @@ const P5 = demeanor({ speech: { articulation: 0.65, syllablesPerSec: 5.8, gesture: 0.6, gazeAvertProb: 0.6 }, }); -/* P6 하린 — 고3, 진로갈등, 똑부러지지만 지친 톤. - 눈 감김 기본 자세(−0.08)는 뺐다(2026-10-01, P3과 같은 이유 — 원화의 다크서클이 지침을 맡는다). */ +/* P6 하린 — 고3, 진로갈등, 똑부러지지만 지친 톤. */ const P6 = demeanor({ - basePose: {}, + basePose: { eyeOpen: -0.08 }, expressivity: 0.9, blinkIntervalMs: [2000, 4200], blinkDurationMs: 120, diff --git a/apps/web/src/components/avatar/v3/LinocutAvatar.tsx b/apps/web/src/components/avatar/v3/LinocutAvatar.tsx index 4f91148..49cbcc2 100644 --- a/apps/web/src/components/avatar/v3/LinocutAvatar.tsx +++ b/apps/web/src/components/avatar/v3/LinocutAvatar.tsx @@ -16,8 +16,6 @@ import { shrunkFaceOvalPointsAttr, lipStripClipRect, LIP_STRIP_COUNT, - browSpriteStripClipRect, - BROW_STRIP_COUNT, jawWarpStripRects, jawWarpStripClipPolygons, jawWarpHeadHoleClipD, @@ -77,13 +75,9 @@ function applyEye(refs: Refs, side: "left" | "right", eye: LinocutFrame["eyeLeft if (crease) { crease.setAttribute("d", eye.creaseD); crease.setAttribute("opacity", String(eye.creaseOpacity)); - crease.setAttribute("stroke-width", String(eye.creaseStrokeWidth)); } const lowerLid = refs[`${p}-lower-lid`] as SVGPathElement | null; - if (lowerLid) { - lowerLid.setAttribute("d", eye.lowerLidD); - lowerLid.setAttribute("stroke-width", String(eye.lowerLidStrokeWidth)); - } + if (lowerLid) lowerLid.setAttribute("d", eye.lowerLidD); const shadow = refs[`${p}-shadow`] as SVGPathElement | null; if (shadow) shadow.setAttribute("d", eye.shadowD); @@ -214,16 +208,6 @@ function applyFrame(refs: Refs, frame: LinocutFrame): void { browRightShadow.setAttribute("cy", String(frame.browRight.shadowCy)); browRightShadow.setAttribute("opacity", String(frame.browRight.shadowOpacity)); } - /* 눈썹 스프라이트 띠 변형(결정문 §8.4.1): sprite가 없는 리그는 spriteStrips가 - 빈 배열이라 ref도 없어(JSX가 아예 안 그린다) 아래는 그냥 건너뛴다. */ - for (let i = 0; i < BROW_STRIP_COUNT; i++) { - const stripLeft = refs[`brow-left-strip-${i}`] as SVGImageElement | null; - const sl = frame.browLeft.spriteStrips[i]; - if (stripLeft && sl) stripLeft.setAttribute("transform", `translate(${sl.dx} ${sl.dy})`); - const stripRight = refs[`brow-right-strip-${i}`] as SVGImageElement | null; - const sr = frame.browRight.spriteStrips[i]; - if (stripRight && sr) stripRight.setAttribute("transform", `translate(${sr.dx} ${sr.dy})`); - } const glabella = refs.glabella as SVGPathElement | null; if (glabella) { @@ -410,8 +394,6 @@ export default function LinocutAvatar({ if (rig.lipTexture) hrefs.push(rig.lipTexture.upper.href, rig.lipTexture.lower.href); if (rig.lipTexture?.shadow) hrefs.push(rig.lipTexture.shadow.href); if (rig.jaw) hrefs.push(rig.jaw.head.href, rig.jaw.detail.href); - if (rig.landmarks.browLeft.sprite) hrefs.push(rig.landmarks.browLeft.sprite.href); - if (rig.landmarks.browRight.sprite) hrefs.push(rig.landmarks.browRight.sprite.href); return hrefs; }, [rig, texture]); @@ -883,68 +865,10 @@ export default function LinocutAvatar({ /> - {/* 눈썹(결정문 §8.4.1): rig.landmarks.browLeft/Right.sprite가 있으면 - 원화 눈썹 스프라이트를 세로 BROW_STRIP_COUNT개 띠로 나눠 그린다 - (벡터 몸통·털 획은 그리지 않는다 — 안쪽 올림 그늘은 아래에서 - 그대로 그린다). 없으면 기존 벡터 경로를 그린다. */} - {!rig.landmarks.browLeft.sprite ? ( - <> - - - - ) : ( - Array.from({ length: BROW_STRIP_COUNT }, (_, i) => { - const sprite = rig.landmarks.browLeft.sprite!; - const rect = browSpriteStripClipRect(sprite, i); - const stripClipId = `${uid}-brow-left-strip-clip-${i}`; - return ( - - - - - - - ); - }) - )} - {!rig.landmarks.browRight.sprite ? ( - <> - - - - ) : ( - Array.from({ length: BROW_STRIP_COUNT }, (_, i) => { - const sprite = rig.landmarks.browRight.sprite!; - const rect = browSpriteStripClipRect(sprite, i); - const stripClipId = `${uid}-brow-right-strip-clip-${i}`; - return ( - - - - - - - ); - }) - )} + + + + diff --git a/apps/web/src/components/avatar/v3/linocutGeometry.ts b/apps/web/src/components/avatar/v3/linocutGeometry.ts index 54c71ef..4f0b037 100644 --- a/apps/web/src/components/avatar/v3/linocutGeometry.ts +++ b/apps/web/src/components/avatar/v3/linocutGeometry.ts @@ -364,11 +364,6 @@ export interface EyeGeometry { highlightCx: number; highlightCy: number; highlightRadius: number; - /** 쌍꺼풀 주름(creaseD) stroke-width = sizeScale × lineScale × 1.2(기존 CSS 고정값). - 홍채·흰자에는 lineScale을 곱하지 않는다(결정문 §8.4.1). */ - creaseStrokeWidth: number; - /** 아랫눈꺼풀(lowerLidD) stroke-width = sizeScale × lineScale × 1.2. */ - lowerLidStrokeWidth: number; radialStrokesD: string; tearPoolD: string; tearPoolOpacity: number; @@ -395,16 +390,6 @@ interface EyeChannels { const CRESCENT_SAMPLES = 14; const CREASE_SAMPLES = 8; const LOWER_LID_SAMPLES = 10; -/** 쌍꺼풀·아랫눈꺼풀 stroke-width 기준값(기존 CSS 고정값 1.2와 같다, linocut-avatar.css - .linocut-avatar__crease/.lower-lid). eye.lineScale이 없으면(기본 1) 전과 같다. */ -const CREASE_LOWER_LID_BASE_STROKE_WIDTH = 1.2; - -/** 윗눈꺼풀 리본 두께 기준식(t_base(s), s는 O→I 곡선 매개변수 0~1). lineScale=1일 때 - curveTop에서 안쪽(눈 틈 쪽)으로 더하는 두께이자, lineScale>1일 때 바깥쪽으로 추가로 - 더하는 두께의 단위이기도 하다(T2, 아래 buildEye 참고). */ -function lidThicknessAt(s: number): number { - return 1.5 + 4 * (1 - smoothstep(0.45, 1, s)); -} function buildEye(eye: RigEye, ch: EyeChannels, isScreenRight: boolean, sizeScale: number): EyeGeometry { const O = toPoint(eye.outer); @@ -413,9 +398,6 @@ function buildEye(eye: RigEye, ch: EyeChannels, isScreenRight: boolean, sizeScal const D = toPoint(eye.lowerLidBottom); const C = toPoint(eye.iris.center); const r = eye.iris.radius; - /* 원화 윗눈꺼풀 잉크 두께 ÷ P1 두께(결정문 §8.4.1) — 눈꺼풀(아래 t)·쌍꺼풀·아랫눈꺼풀 - 굵기에만 곱한다. 홍채·흰자(irisRadius·irisRingWidth 등)에는 곱하지 않는다. */ - const lineScale = eye.lineScale ?? 1; /* 크게 뜬 눈(eyeOpen>1) 증폭: 그대로 두면 놀람이 잘 안 읽힌다(1차 반려 "표정 간 차이가 작다"와 연결). e는 eyeOpen<=1이면 그대로, 넘으면 1.8배 증폭(최대 1.5)한다. */ @@ -442,21 +424,11 @@ function buildEye(eye: RigEye, ch: EyeChannels, isScreenRight: boolean, sizeScal const outlineD = `${pathQ(O, upperControl, I)} Q ${lowerControl[0]} ${lowerControl[1]} ${O[0]} ${O[1]} Z`; - /* 윗눈꺼풀 선: s=0(바깥 O)→1(안쪽 I), t_base(s) = lidThicknessAt(s). - 2차 보정(T2, 2단계-B-2a 반려 — 안쪽 경계가 홍채 위를 덮었다): lineScale로 늘어나는 - 두께 (lineScale−1)·t_base는 바깥쪽(위, curveTop 기준 음의 y)으로만 더한다. 안쪽 - 경계(crescentBottom, 눈 틈·홍채 쪽)는 lineScale과 무관하게 항상 curveTop + t_base다 - (= lineScale=1일 때의 자리와 같다). lineScale=1이면 extra=0이라 crescentTop=curveTop, - crescentBottom은 전과 바이트 단위로 같다. */ - const curveTop = sampleQuad(O, upperControl, I, 0, 1, CRESCENT_SAMPLES); - const crescentTop = curveTop.map((p, i) => { + /* 윗눈꺼풀 선: s=0(바깥 O)→1(안쪽 I), t(s) = 1.5 + 4·(1 − smoothstep(0.45,1,s)). */ + const crescentTop = sampleQuad(O, upperControl, I, 0, 1, CRESCENT_SAMPLES); + const crescentBottom = crescentTop.map((p, i) => { const s = i / (CRESCENT_SAMPLES - 1); - const extra = sizeScale * (lineScale - 1) * lidThicknessAt(s); - return [p[0], p[1] - extra] as Point; - }); - const crescentBottom = curveTop.map((p, i) => { - const s = i / (CRESCENT_SAMPLES - 1); - const t = sizeScale * lidThicknessAt(s); + const t = sizeScale * (1.5 + 4 * (1 - smoothstep(0.45, 1, s))); return [p[0], p[1] + t] as Point; }); const tailDir = (() => { @@ -475,20 +447,11 @@ function buildEye(eye: RigEye, ch: EyeChannels, isScreenRight: boolean, sizeScal "Z", ].join(" "); - /* 쌍꺼풀 주름: s 0.15~0.85, 윗곡선 위로 9 + 5·max(0, openU−1)만큼. lineScale>1이면 - 윗눈꺼풀 리본이 바깥쪽으로 그만큼 두꺼워지므로(위 crescentTop), 주름도 같은 양만큼 - 더 위로 비켜 겹치지 않게 한다(T2). */ + /* 쌍꺼풀 주름: s 0.15~0.85, 윗곡선 위로 9 + 5·max(0, openU−1)만큼, 불투명도 0.6 기준. */ const creaseOffset = 9 + 5 * Math.max(0, openU - 1); - const creasePts = Array.from({ length: CREASE_SAMPLES }, (_, i) => { - const t = lerp(0.15, 0.85, i / (CREASE_SAMPLES - 1)); - const p = quadPointAt(O, upperControl, I, t); - const extra = sizeScale * (lineScale - 1) * lidThicknessAt(t); - return [p[0], p[1] - creaseOffset - extra] as Point; - }); + const creasePts = sampleQuad(O, upperControl, I, 0.15, 0.85, CREASE_SAMPLES).map((p) => [p[0], p[1] - creaseOffset] as Point); const creaseD = creasePts.map((p, i) => `${i === 0 ? "M" : "L"} ${p[0]} ${p[1]}`).join(" "); const creaseOpacity = (openU < 0.35 ? clamp(openU / 0.35) : 1) * 0.6; - const creaseStrokeWidth = sizeScale * lineScale * CREASE_LOWER_LID_BASE_STROKE_WIDTH; - const lowerLidStrokeWidth = sizeScale * lineScale * CREASE_LOWER_LID_BASE_STROKE_WIDTH; /* 아랫눈꺼풀: s 0~0.8 가는 선. */ const lowerPts = sampleQuad(O, lowerControl, I, 0, 0.8, LOWER_LID_SAMPLES); @@ -560,8 +523,6 @@ function buildEye(eye: RigEye, ch: EyeChannels, isScreenRight: boolean, sizeScal highlightCx, highlightCy, highlightRadius: 0.13 * r, - creaseStrokeWidth, - lowerLidStrokeWidth, radialStrokesD: radialSegments.join(" "), tearPoolD, tearPoolOpacity: pool, @@ -588,11 +549,6 @@ export interface BrowGeometry { shadowCx: number; shadowCy: number; shadowOpacity: number; - /** 눈썹 스프라이트 띠 변형(결정문 §8.4.1, rig.landmarks.browLeft/Right.sprite가 - 있는 리그 한정). 없으면 빈 배열 — 렌더러가 기존 벡터 몸통·털 획(bodyD· - hairStrokesD)을 그린다. 있으면 렌더러가 이 배열로 스프라이트 띠를 옮기고 - 벡터 몸통·털 획은 그리지 않는다(안쪽 올림 그늘은 그대로 그린다). */ - spriteStrips: BrowStripMatrix[]; } interface BrowChannels { @@ -608,26 +564,14 @@ const BROW_STOPS: Array = [ [1, 1.0], ]; -interface BrowDisplacement { - innerDy: number; - peakDy: number; - outerDy: number; - innerDx: number; - innerP: Point; - peakP: Point; - outerP: Point; -} - -/** browAngle a는 부호에 따라 다른 계수를 쓴다(AU1 안쪽 올림은 바깥이 따라 오르지 않는다, - §8.4 2단계-B-1d 개정): a>0이면 안쪽 −60a·꼭짓점 −4a·바깥 +18a, a<0(AU4)이면 - 안쪽 −50a·꼭짓점 −12a·바깥 +14a. buildBrow(벡터 몸통·털 획)와 - computeBrowSpriteStripTransforms(스프라이트 띠) 둘 다 이 변위를 쓴다(§8.4.1 — - "같은 계수로 구한" 요구, 중복 계산 아님). */ -function computeBrowDisplacement(brow: RigBrow, ch: BrowChannels, side: "left" | "right"): BrowDisplacement { +function buildBrow(brow: RigBrow, ch: BrowChannels, side: "left" | "right", sizeScale: number): BrowGeometry { const inner = toPoint(brow.inner); const peak = toPoint(brow.peak); const outer = toPoint(brow.outer); + /* browAngle a는 부호에 따라 다른 계수를 쓴다(AU1 안쪽 올림은 바깥이 따라 오르지 않는다, + §8.4 2단계-B-1d 개정): a>0이면 안쪽 −60a·꼭짓점 −4a·바깥 +18a, a<0(AU4)이면 + 안쪽 −50a·꼭짓점 −12a·바깥 +14a. */ const dyAll = -ch.y * 45; const a = ch.angle; const innerCoef = a > 0 ? -60 : -50; @@ -638,91 +582,13 @@ function computeBrowDisplacement(brow: RigBrow, ch: BrowChannels, side: "left" | const peakDy = dyAll + peakCoef * a - ch.form * 10; const innerDx = side === "left" ? -ch.x * 16 : ch.x * 16; - return { - innerDy, - peakDy, - outerDy, - innerDx, - innerP: [inner[0] + innerDx, inner[1] + innerDy], - peakP: [peak[0], peak[1] + peakDy], - outerP: [outer[0], outer[1] + outerDy], - }; -} - -export const BROW_STRIP_COUNT = 16; - -export interface BrowStripMatrix { - dx: number; - dy: number; -} - -/** 세 점 (x0,y0)(x1,y1)(x2,y2)를 "정확히" 지나는 2차 다항식 y=f(x)의 x에서의 - 값(라그랑주 보간). quadControlThrough(베지어 제어점)는 t=0.5가 mid의 x를 - 지난다는 보장이 없어(x 간격이 고르지 않으면 어긋난다) 눈썹처럼 inner·peak·outer - 간격이 비대칭인 랜드마크에는 안 맞는다. */ -function quadLagrangeAt(p0: Point, p1: Point, p2: Point, x: number): number { - const [x0, y0] = p0; - const [x1, y1] = p1; - const [x2, y2] = p2; - const l0 = ((x - x1) * (x - x2)) / ((x0 - x1) * (x0 - x2)); - const l1 = ((x - x0) * (x - x2)) / ((x1 - x0) * (x1 - x2)); - const l2 = ((x - x0) * (x - x1)) / ((x2 - x0) * (x2 - x1)); - return y0 * l0 + y1 * l1 + y2 * l2; -} - -/** 눈썹 스프라이트 띠 변형(결정문 §8.4.1): 원화 눈썹 스프라이트를 세로 - BROW_STRIP_COUNT개 띠로 나눠 computeBrowDisplacement와 같은 계수로 구한 - inner·peak·outer 변위만큼만 옮긴다(스케일 없음 — 원화 픽셀을 늘리면 잉크 - 굵기가 왜곡된다). 띠 i의 중심 x에서: dy(x)는 (inner.x,innerDy)·(peak.x,peakDy)· - (outer.x,outerDy)를 지나는 2차 보간(quadLagrangeAt), dx는 innerDx × - (안쪽→바깥 선형 가중 1→0)이다. brow.sprite가 없으면 null(렌더러가 기존 벡터 - 경로를 쓴다). */ -export function computeBrowSpriteStripTransforms( - brow: RigBrow, - ch: BrowChannels, - side: "left" | "right", - stripCount: number = BROW_STRIP_COUNT, -): BrowStripMatrix[] | null { - if (!brow.sprite) return null; - const disp = computeBrowDisplacement(brow, ch, side); - const innerX = brow.inner[0]; - const outerX = brow.outer[0]; - const span = outerX - innerX || 1; - const p0: Point = [innerX, disp.innerDy]; - const p1: Point = [brow.peak[0], disp.peakDy]; - const p2: Point = [outerX, disp.outerDy]; - - const sprite = brow.sprite; - const stripW = sprite.w / stripCount; - const out: BrowStripMatrix[] = []; - for (let i = 0; i < stripCount; i++) { - const cx = sprite.x + (i + 0.5) * stripW; - const weight = clamp(1 - (cx - innerX) / span, 0, 1); - out.push({ dx: disp.innerDx * weight, dy: quadLagrangeAt(p0, p1, p2, cx) }); - } - return out; -} - -/** 눈썹 스프라이트 띠 clip 사각형(스프라이트 폭 기준 등분, 이웃과 1px 겹침) — - lipStripClipRect와 같은 패턴. 채널과 무관해 렌더러가 마운트 시 한 번만 - 계산해 에 쓴다. */ -export function browSpriteStripClipRect(sprite: RigLayer, index: number, stripCount: number = BROW_STRIP_COUNT) { - const stripW = sprite.w / stripCount; - return { x: sprite.x + index * stripW - 0.5, y: sprite.y, w: stripW + 1, h: sprite.h }; -} - -function buildBrow(brow: RigBrow, ch: BrowChannels, side: "left" | "right", sizeScale: number): BrowGeometry { - const disp = computeBrowDisplacement(brow, ch, side); - const { innerP, peakP, outerP } = disp; + const innerP: Point = [inner[0] + innerDx, inner[1] + innerDy]; + const peakP: Point = [peak[0], peak[1] + peakDy]; + const outerP: Point = [outer[0], outer[1] + outerDy]; /* "세 점을 지나는 곡선(12점 이상 표본)", 꺾인 직선 금지(1차 반려 사유). 작은 화면 - 보정(§8.4 2단계-B-1d)으로 반두께·털 획 길이·굵기에 sizeScale을 곱한다. - rig에 원화 실측 반두께(halfWidth)가 있으면 그것을, 없으면 P1 설계값(BROW_STOPS)을 - 쓴다(캐스트 확장, 결정문 §8.4). */ - const stops: Array = brow.halfWidth - ? [[0, brow.halfWidth[0]], [0.5, brow.halfWidth[1]], [1, brow.halfWidth[2]]] - : BROW_STOPS; - const scaledStops = stops.map(([t, hw]) => [t, hw * sizeScale] as const); + 보정(§8.4 2단계-B-1d)으로 반두께·털 획 길이·굵기에 sizeScale을 곱한다. */ + const scaledStops = BROW_STOPS.map(([t, hw]) => [t, hw * sizeScale] as const); const { samples, halfWidths } = curveRibbonData([innerP, peakP, outerP], scaledStops, 6); const bodyD = polylineRibbon(samples, halfWidths); const seedBase = side === "left" ? 0 : 100; @@ -730,17 +596,12 @@ function buildBrow(brow: RigBrow, ch: BrowChannels, side: "left" | "right", size const hairStrokeWidth = 1.3 * sizeScale; /* 안쪽 올림 그늘(§8.4): a>0.3이면 눈썹 머리 위에 흐린 잉크 그늘(가우시안 흐림은 - 렌더러의 SVG 필터가 맡는다, 여기서는 위치·불투명도만). 스프라이트가 있는 리그도 - 이 그늘은 그대로 그린다(§8.4.1 — 벡터 몸통·털 획만 스프라이트로 대체한다). */ - const shadowOpacity = 0.18 * clamp((ch.angle - 0.3) / 0.7); + 렌더러의 SVG 필터가 맡는다, 여기서는 위치·불투명도만). */ + const shadowOpacity = 0.18 * clamp((a - 0.3) / 0.7); const shadowCx = innerP[0]; const shadowCy = innerP[1] - 18; - /* 눈썹 스프라이트 띠 변형(§8.4.1): sprite가 없으면 빈 배열 — 렌더러가 위 - bodyD·hairStrokesD(벡터 경로)를 그대로 그린다. */ - const spriteStrips = computeBrowSpriteStripTransforms(brow, ch, side) ?? []; - - return { bodyD, hairStrokesD, hairStrokeWidth, innerP, peakP, shadowCx, shadowCy, shadowOpacity, spriteStrips }; + return { bodyD, hairStrokesD, hairStrokeWidth, innerP, peakP, shadowCx, shadowCy, shadowOpacity }; } /* ── 보조 주름 ─────────────────────────────────────────────────────── */ diff --git a/apps/web/src/components/avatar/v3/linocutRig.ts b/apps/web/src/components/avatar/v3/linocutRig.ts index 85dae68..885a144 100644 --- a/apps/web/src/components/avatar/v3/linocutRig.ts +++ b/apps/web/src/components/avatar/v3/linocutRig.ts @@ -28,9 +28,6 @@ export interface RigEye { upperLidTop: RigPoint; lowerLidBottom: RigPoint; iris: { center: RigPoint; radius: number }; - /** 눈꺼풀·쌍꺼풀·아랫눈꺼풀 선 굵기 배율 — 원화 윗눈꺼풀 잉크 두께 ÷ P1 두께(결정문 §8.4.1). - 굵은 판화 선 캐릭터의 벡터 눈이 가늘고 흐려지는 것을 막는다. 없으면 1. */ - lineScale?: number; } /** 눈썹 잉크 띠의 **중심선** 위 세 점(결정문 §8.4). 띠의 윗가장자리·아래 모서리를 찍으면 @@ -39,13 +36,6 @@ export interface RigBrow { inner: RigPoint; peak: RigPoint; outer: RigPoint; - /** 원화 잉크 띠의 반두께(px) — [안쪽, 꼭짓점, 바깥]. 캐스트마다 눈썹 굵기가 달라(남성·성인은 - 더 굵다) 원화에서 잰 값을 쓴다. 없으면 P1 설계값 [4.5, 3.2, 1.0](결정문 §8.4 캐스트 확장). */ - halfWidth?: readonly [number, number, number]; - /** 원화 눈썹 잉크 스프라이트(결정문 §8.4.1). 알파는 잉크 농도(피부는 투명)이고 앞머리 가닥은 - 빠져 있다. 있으면 렌더러는 벡터 눈썹 몸통·털 획 대신 이 픽셀을 세로 띠로 나눠, 각 띠를 - inner·peak·outer 변위를 잇는 곡선 값만큼 옮긴다(입술 결과 같은 "중립 렌더 = 원화" 원칙). */ - sprite?: RigLayer; } export interface RigLandmarks { diff --git a/apps/web/src/components/avatar/v3/rigs/index.ts b/apps/web/src/components/avatar/v3/rigs/index.ts index 19bbbca..998afa5 100644 --- a/apps/web/src/components/avatar/v3/rigs/index.ts +++ b/apps/web/src/components/avatar/v3/rigs/index.ts @@ -1,50 +1,16 @@ /* ===================================================================== 아바타 v3 리그 레지스트리 — 결정문 §8.5 "분기". 페르소나 코드 → 리노컷 - 리그. 세션용(linocutRigFor)은 오케스트레이터가 표정 시트를 판정한 페르소나만 - 켠다 — 2026-10-01 P1~P7 전원(결정문 §8.4.1). 목록에 없으면 null(기존 SVG). - 빌드 플래그 VITE_AVATAR_V3=0 이면 항상 null(기본은 켜짐). + 리그. 지금은 P1만 있고 나머지는 null(기존 SVG로 남는다). 빌드 플래그 + VITE_AVATAR_V3=0 이면 항상 null(기본은 켜짐). ===================================================================== */ import type { LinocutRig } from "../linocutRig"; import { P1_LINOCUT_RIG } from "./p1Rig"; -import { P2_LINOCUT_RIG } from "./p2Rig"; -import { P3_LINOCUT_RIG } from "./p3Rig"; -import { P4_LINOCUT_RIG } from "./p4Rig"; -import { P5_LINOCUT_RIG } from "./p5Rig"; -import { P6_LINOCUT_RIG } from "./p6Rig"; -import { P7_LINOCUT_RIG } from "./p7Rig"; -const LINOCUT_RIGS: Partial> = { - P1: P1_LINOCUT_RIG, - P2: P2_LINOCUT_RIG, - P3: P3_LINOCUT_RIG, - P4: P4_LINOCUT_RIG, - P5: P5_LINOCUT_RIG, - P6: P6_LINOCUT_RIG, - P7: P7_LINOCUT_RIG, -}; +const LINOCUT_RIGS: Partial> = { P1: P1_LINOCUT_RIG }; export function linocutRigFor(code: string | null | undefined): LinocutRig | null { if (import.meta.env.VITE_AVATAR_V3 === "0") return null; if (!code) return null; return LINOCUT_RIGS[code.toUpperCase()] ?? null; } - -/* Lab(AvatarLab.tsx)용 — 공통 파이프라인(scripts/export_rig.py)이 만드는 ./pN Rig.ts를 - 전부 모아 rig.persona로 색인한다. 세션 화면(linocutRigFor)과는 무관하다 — - persona.json이 아직 없는 pNRig.ts가 생기는 즉시 Lab 선택지에 나타나지만, - 세션에는 영향이 없다(세션은 위 LINOCUT_RIGS 목록만 본다). */ -const labRigModules = import.meta.glob("./p*Rig.ts", { eager: true }) as Record>; - -export function labLinocutRigs(): Partial> { - const out: Partial> = {}; - for (const mod of Object.values(labRigModules)) { - for (const exported of Object.values(mod)) { - const rig = exported as Partial | null; - if (rig && typeof rig === "object" && rig.schemaVersion === "vignette.avatar.v3.rig.v1" && typeof rig.persona === "string") { - out[rig.persona.toUpperCase()] = rig as LinocutRig; - } - } - } - return out; -} diff --git a/apps/web/src/components/avatar/v3/rigs/p2Rig.ts b/apps/web/src/components/avatar/v3/rigs/p2Rig.ts deleted file mode 100644 index 350609b..0000000 --- a/apps/web/src/components/avatar/v3/rigs/p2Rig.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* 생성 파일 — docs/avatar-art/linocut-pipeline/scripts/export_rig.py 가 만든다. 손으로 고치지 않는다. */ -import type { LinocutRig } from "../linocutRig"; - -export const P2_LINOCUT_RIG: LinocutRig = { - schemaVersion: "vignette.avatar.v3.rig.v1", - persona: "P2", - canvas: { w: 1005, h: 1566 }, - layers: { - body: { href: "/avatar/v3/p2/body.webp", x: 0, y: 751, w: 1005, h: 815 }, - head: { href: "/avatar/v3/p2/head.webp", x: 164, y: 84, w: 667, h: 877 }, - hairFront: { href: "/avatar/v3/p2/hair-front.webp", x: 240, y: 398, w: 498, h: 460 }, - faceDetail: { href: "/avatar/v3/p2/face-detail.webp", x: 274, y: 405, w: 449, h: 550 }, - }, - grain: { href: "/avatar/v3/p2/paper-grain.webp", size: 256 }, - lipTexture: { - upper: { href: "/avatar/v3/p2/lip-upper.webp", x: 404, y: 772, w: 182, h: 51 }, - lower: { href: "/avatar/v3/p2/lip-lower.webp", x: 404, y: 813, w: 181, h: 48 }, - shadow: { href: "/avatar/v3/p2/lip-shadow.webp", x: 414, y: 817, w: 155, h: 52 }, - }, - jaw: { - head: { href: "/avatar/v3/p2/jaw-head.webp", x: 246, y: 658, w: 506, h: 333 }, - detail: { href: "/avatar/v3/p2/jaw-detail.webp", x: 246, y: 658, w: 506, h: 333 }, - }, - pivots: { - neck: [496.4, 1021.2], - body: [502, 1566], - face: [492, 658.9], - }, - crops: { - portrait: [0, 0, 1005, 1566], - bust: [0, 40, 1005, 1005], - face: [181.6, 364.6, 630.6, 630.6], - }, - faceOval: [[492.2, 398.4], [558, 399.3], [614.2, 407.7], [665.7, 426.4], [699, 456.1], [719.9, 494.6], [731.3, 536], [736, 586.9], [734.6, 635.4], [730.5, 685.8], [721.5, 739], [704.8, 795.1], [682.2, 840.4], [657.5, 873.5], [627.1, 903.3], [601, 924.5], [572.8, 943.3], [539.3, 957.3], [496.4, 961.4], [453.8, 957.3], [421, 943.3], [393.2, 924.7], [367.4, 904.2], [338.1, 874.9], [315, 842], [293.7, 797.6], [277.1, 741.8], [267, 688.9], [261.6, 639.6], [257.9, 591.7], [260.7, 541.3], [270.6, 499.5], [290.1, 460.8], [321.9, 430.6], [371.3, 410.5], [426.6, 401]], - landmarks: { - eyeLeft: { - inner: [436.9, 589.7], - outer: [345.9, 578.6], - upperLidTop: [382.5, 563.5], - lowerLidBottom: [387, 595.5], - iris: { center: [391, 576], radius: 20 }, - lineScale: 2.5, - }, - eyeRight: { - inner: [553.4, 588.3], - outer: [643.7, 575.1], - upperLidTop: [606.6, 560.8], - lowerLidBottom: [603.5, 593], - iris: { center: [598.5, 573.5], radius: 20.5 }, - lineScale: 2.5, - }, - browLeft: { inner: [459.2, 529.9], peak: [359.4, 517.4], outer: [307.8, 526.2], halfWidth: [12.8, 16.5, 2.4], sprite: { href: "/avatar/v3/p2/brow-left.webp", x: 307, y: 500, w: 158, h: 55 } }, - browRight: { inner: [522.2, 526.9], peak: [630.8, 512.7], outer: [686.8, 520.9], halfWidth: [15.3, 12.2, 6.8], sprite: { href: "/avatar/v3/p2/brow-right.webp", x: 515, y: 492, w: 176, h: 57 } }, - noseTip: [494.7, 718.4], - mouthCornerLeft: [417.4, 818.3], - mouthCornerRight: [572.3, 816.9], - upperLipTop: [494.6, 787.6], - lowerLipBottom: [494.1, 843.9], - mouthCenter: [494.6, 812.3], - chinTip: [496.4, 950.7], - }, - palette: { - ink: "#1E1E1D", - sclera: "#D8CEBD", - iris: "#4F3B2C", - irisRing: "#1E1F1F", - lipUpper: "#9D7253", - lipLower: "#A87857", - mouthLine: "#242220", - mouthInner: "#3B201B", - teeth: "#E9E0CF", - blush: "#C0624A", - tear: "#EEE5D3", - pallor: "#9AA3A6", - paper: "#EEE5D3", - motifPetal: "#D0A362", - motifLeaf: "#53626C", - }, - backdrop: { - cognitive: "#ECE3D1", - positive: "#F1DEC2", - negative: "#DCE0E2", - defensive: "#E6DAD3", - energy: "#E2E0D0", - }, -}; diff --git a/apps/web/src/components/avatar/v3/rigs/p3Rig.ts b/apps/web/src/components/avatar/v3/rigs/p3Rig.ts deleted file mode 100644 index b9dffe5..0000000 --- a/apps/web/src/components/avatar/v3/rigs/p3Rig.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* 생성 파일 — docs/avatar-art/linocut-pipeline/scripts/export_rig.py 가 만든다. 손으로 고치지 않는다. */ -import type { LinocutRig } from "../linocutRig"; - -export const P3_LINOCUT_RIG: LinocutRig = { - schemaVersion: "vignette.avatar.v3.rig.v1", - persona: "P3", - canvas: { w: 1005, h: 1566 }, - layers: { - body: { href: "/avatar/v3/p3/body.webp", x: 0, y: 848, w: 1005, h: 718 }, - head: { href: "/avatar/v3/p3/head.webp", x: 132, y: 156, w: 742, h: 888 }, - hairFront: { href: "/avatar/v3/p3/hair-front.webp", x: 237, y: 398, w: 540, h: 535 }, - faceDetail: { href: "/avatar/v3/p3/face-detail.webp", x: 279, y: 397, w: 443, h: 542 }, - }, - grain: { href: "/avatar/v3/p3/paper-grain.webp", size: 256 }, - lipTexture: { - upper: { href: "/avatar/v3/p3/lip-upper.webp", x: 424, y: 756, w: 173, h: 51 }, - lower: { href: "/avatar/v3/p3/lip-lower.webp", x: 423, y: 792, w: 171, h: 54 }, - shadow: { href: "/avatar/v3/p3/lip-shadow.webp", x: 433, y: 791, w: 148, h: 63 }, - }, - jaw: { - head: { href: "/avatar/v3/p3/jaw-head.webp", x: 243, y: 644, w: 512, h: 332 }, - detail: { href: "/avatar/v3/p3/jaw-detail.webp", x: 243, y: 644, w: 512, h: 332 }, - }, - pivots: { - neck: [518, 1005], - body: [502, 1566], - face: [492, 652], - }, - crops: { - portrait: [0, 0, 1005, 1566], - bust: [0, 40, 1005, 1005], - face: [185.9, 356.9, 621.9, 621.9], - }, - faceOval: [[475.8, 393.7], [540.7, 390.2], [596.5, 395.8], [649.6, 413.2], [685.4, 442.2], [709.3, 480.3], [724.7, 521.2], [733.9, 571.8], [736.4, 619.7], [736.4, 669.4], [731, 722.5], [718.2, 777.9], [698.5, 822.4], [674.5, 854.4], [643.8, 883.5], [618.4, 904.1], [591.4, 922.9], [559.9, 938.4], [519, 945.4], [477.9, 944.3], [445.2, 933.5], [416.7, 919.2], [389.2, 903.2], [355.4, 879.7], [328.1, 851.8], [302.7, 811.3], [282.3, 758], [269.2, 706.1], [262.4, 657.2], [257.2, 610.1], [258.2, 560], [266.7, 517.2], [283.8, 476], [312.7, 441.9], [359, 416], [411.7, 401.4]], - landmarks: { - eyeLeft: { - inner: [435.1, 593.8], - outer: [346.3, 589.4], - upperLidTop: [382.5, 570.1], - lowerLidBottom: [386.7, 602.2], - iris: { center: [389, 580], radius: 20 }, - lineScale: 1, - }, - eyeRight: { - inner: [552.1, 583.6], - outer: [640.8, 564.7], - upperLidTop: [600.8, 552.1], - lowerLidBottom: [601, 583.9], - iris: { center: [593.5, 558.5], radius: 19.5 }, - lineScale: 1, - }, - browLeft: { inner: [438, 513.2], peak: [354.9, 519.8], outer: [312, 530.8], halfWidth: [7, 8.3, 3.8], sprite: { href: "/avatar/v3/p3/brow-left.webp", x: 314, y: 502, w: 129, h: 38 } }, - browRight: { inner: [521.3, 505.4], peak: [609.3, 496.5], outer: [654.7, 501], halfWidth: [3.9, 7, 2], sprite: { href: "/avatar/v3/p3/brow-right.webp", x: 517, y: 486, w: 142, h: 32 } }, - noseTip: [499.6, 704.4], - mouthCornerLeft: [432.6, 803.4], - mouthCornerRight: [583.6, 792.2], - upperLipTop: [505.2, 769.8], - lowerLipBottom: [509.4, 832.4], - mouthCenter: [507.2, 796.4], - chinTip: [518.3, 935.2], - }, - palette: { - ink: "#242322", - sclera: "#D8CEBD", - iris: "#4F3B2C", - irisRing: "#1E1F1F", - lipUpper: "#B4835D", - lipLower: "#BD8A62", - mouthLine: "#76553F", - mouthInner: "#3B201B", - teeth: "#E9E0CF", - blush: "#C0624A", - tear: "#EEE5D3", - pallor: "#9AA3A6", - paper: "#EEE5D3", - motifPetal: "#D0A362", - motifLeaf: "#53626C", - }, - backdrop: { - cognitive: "#ECE3D1", - positive: "#F1DEC2", - negative: "#DCE0E2", - defensive: "#E6DAD3", - energy: "#E2E0D0", - }, -}; diff --git a/apps/web/src/components/avatar/v3/rigs/p4Rig.ts b/apps/web/src/components/avatar/v3/rigs/p4Rig.ts deleted file mode 100644 index 27fd550..0000000 --- a/apps/web/src/components/avatar/v3/rigs/p4Rig.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* 생성 파일 — docs/avatar-art/linocut-pipeline/scripts/export_rig.py 가 만든다. 손으로 고치지 않는다. */ -import type { LinocutRig } from "../linocutRig"; - -export const P4_LINOCUT_RIG: LinocutRig = { - schemaVersion: "vignette.avatar.v3.rig.v1", - persona: "P4", - canvas: { w: 1005, h: 1566 }, - layers: { - body: { href: "/avatar/v3/p4/body.webp", x: 0, y: 436, w: 1005, h: 1130 }, - head: { href: "/avatar/v3/p4/head.webp", x: 161, y: 97, w: 661, h: 924 }, - hairFront: { href: "/avatar/v3/p4/hair-front.webp", x: 234, y: 405, w: 534, h: 485 }, - faceDetail: { href: "/avatar/v3/p4/face-detail.webp", x: 266, y: 394, w: 470, h: 551 }, - }, - grain: { href: "/avatar/v3/p4/paper-grain.webp", size: 256 }, - lipTexture: { - upper: { href: "/avatar/v3/p4/lip-upper.webp", x: 410, y: 777, w: 179, h: 46 }, - lower: { href: "/avatar/v3/p4/lip-lower.webp", x: 410, y: 816, w: 178, h: 51 }, - shadow: { href: "/avatar/v3/p4/lip-shadow.webp", x: 420, y: 814, w: 159, h: 61 }, - }, - jaw: { - head: { href: "/avatar/v3/p4/jaw-head.webp", x: 250, y: 668, w: 504, h: 313 }, - detail: { href: "/avatar/v3/p4/jaw-detail.webp", x: 250, y: 668, w: 504, h: 313 }, - }, - pivots: { - neck: [502.7, 1011.1], - body: [502, 1566], - face: [496, 655], - }, - crops: { - portrait: [0, 0, 1005, 1566], - bust: [0, 40, 1005, 1005], - face: [184.9, 354.2, 631.2, 631.2], - }, - faceOval: [[495.7, 388], [569.6, 388.6], [631.4, 396.4], [687.2, 414.1], [721.3, 442.7], [739.9, 479.9], [748.2, 519.6], [748.9, 568.1], [743.7, 615], [735.8, 663.9], [723.6, 715.1], [704.8, 769.9], [681.9, 816.1], [657.7, 851.5], [628.5, 883.7], [603.9, 907.3], [576.9, 929.1], [544.6, 946.1], [502.7, 951.6], [461.8, 946.3], [430.4, 929.7], [403.7, 908.3], [378.7, 885.5], [348.8, 854], [324, 819.1], [300.1, 773.9], [280.7, 719], [267.6, 667.8], [259.3, 619.2], [252.7, 572.5], [252.2, 524.3], [258.8, 484.1], [275.1, 446.8], [306.7, 417.8], [360.2, 398.8], [421.5, 390.1]], - landmarks: { - eyeLeft: { - inner: [430.6, 594.2], - outer: [337.6, 576.7], - upperLidTop: [375.9, 563.9], - lowerLidBottom: [380.9, 598.5], - iris: { center: [385, 576], radius: 20 }, - lineScale: 2.5, - }, - eyeRight: { - inner: [566.4, 593.4], - outer: [659.2, 572.9], - upperLidTop: [621.4, 561.7], - lowerLidBottom: [616.8, 596.6], - iris: { center: [611.5, 574.5], radius: 18.5 }, - lineScale: 2.5, - }, - browLeft: { inner: [449.4, 524.6], peak: [353.2, 508.4], outer: [303.6, 507.6], halfWidth: [6.8, 6.5, 2.5], sprite: { href: "/avatar/v3/p4/brow-left.webp", x: 299, y: 499, w: 156, h: 36 } }, - browRight: { inner: [540.9, 523.7], peak: [642.6, 502.3], outer: [695.1, 500.2], halfWidth: [2.4, 6.7, 1.5], sprite: { href: "/avatar/v3/p4/brow-right.webp", x: 536, y: 491, w: 164, h: 42 } }, - noseTip: [498.6, 728.2], - mouthCornerLeft: [426.2, 819.4], - mouthCornerRight: [571.5, 818.6], - upperLipTop: [497.9, 791.6], - lowerLipBottom: [498.7, 852.3], - mouthCenter: [498.4, 815.7], - chinTip: [502.7, 940.6], - }, - palette: { - ink: "#22211F", - sclera: "#D8CEBD", - iris: "#4F3B2C", - irisRing: "#1E1F1F", - lipUpper: "#B48460", - lipLower: "#C08E69", - mouthLine: "#332B26", - mouthInner: "#3B201B", - teeth: "#E9E0CF", - blush: "#C0624A", - tear: "#EEE5D3", - pallor: "#9AA3A6", - paper: "#EEE5D3", - motifPetal: "#D0A362", - motifLeaf: "#53626C", - }, - backdrop: { - cognitive: "#ECE3D1", - positive: "#F1DEC2", - negative: "#DCE0E2", - defensive: "#E6DAD3", - energy: "#E2E0D0", - }, -}; diff --git a/apps/web/src/components/avatar/v3/rigs/p5Rig.ts b/apps/web/src/components/avatar/v3/rigs/p5Rig.ts deleted file mode 100644 index b5f169e..0000000 --- a/apps/web/src/components/avatar/v3/rigs/p5Rig.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* 생성 파일 — docs/avatar-art/linocut-pipeline/scripts/export_rig.py 가 만든다. 손으로 고치지 않는다. */ -import type { LinocutRig } from "../linocutRig"; - -export const P5_LINOCUT_RIG: LinocutRig = { - schemaVersion: "vignette.avatar.v3.rig.v1", - persona: "P5", - canvas: { w: 1005, h: 1566 }, - layers: { - body: { href: "/avatar/v3/p5/body.webp", x: 0, y: 696, w: 1005, h: 870 }, - head: { href: "/avatar/v3/p5/head.webp", x: 144, y: 118, w: 688, h: 846 }, - hairFront: { href: "/avatar/v3/p5/hair-front.webp", x: 231, y: 401, w: 513, h: 436 }, - faceDetail: { href: "/avatar/v3/p5/face-detail.webp", x: 262, y: 410, w: 455, h: 523 }, - }, - grain: { href: "/avatar/v3/p5/paper-grain.webp", size: 256 }, - lipTexture: { - upper: { href: "/avatar/v3/p5/lip-upper.webp", x: 423, y: 749, w: 124, h: 54 }, - lower: { href: "/avatar/v3/p5/lip-lower.webp", x: 423, y: 791, w: 136, h: 46 }, - shadow: { href: "/avatar/v3/p5/lip-shadow.webp", x: 433, y: 797, w: 116, h: 48 }, - }, - jaw: { - head: { href: "/avatar/v3/p5/jaw-head.webp", x: 236, y: 633, w: 512, h: 337 }, - detail: { href: "/avatar/v3/p5/jaw-detail.webp", x: 236, y: 633, w: 512, h: 337 }, - }, - pivots: { - neck: [488, 1001], - body: [502, 1566], - face: [490, 659], - }, - crops: { - portrait: [0, 0, 1005, 1566], - bust: [0, 40, 1005, 1005], - face: [191.6, 370.8, 601.3, 601.3], - }, - faceOval: [[496.1, 403], [564, 405], [621.1, 414.5], [672.8, 434.4], [705.1, 464.2], [723.6, 501.7], [732.1, 541], [732.9, 589.2], [728.9, 634.4], [723.7, 681], [714.5, 730.1], [698.1, 782.2], [675.7, 823.6], [650.1, 853], [619, 879.9], [592.9, 900], [565.2, 919.2], [531.9, 934.8], [488.2, 939.9], [444.8, 934.7], [411.7, 918.9], [384.1, 899.3], [358.4, 879], [328.1, 851.8], [303.5, 822.2], [281.3, 781.3], [265.8, 729], [257.4, 680], [253.9, 634], [251.6, 589.2], [255.3, 541.7], [265.7, 502], [286.1, 464.7], [319.6, 435], [371.3, 414.7], [428.2, 405.3]], - landmarks: { - eyeLeft: { - inner: [431.6, 589], - outer: [343.8, 578.5], - upperLidTop: [381.9, 566.8], - lowerLidBottom: [383, 594], - iris: { center: [387.5, 574.5], radius: 18.5 }, - lineScale: 2.5, - }, - eyeRight: { - inner: [553.9, 588.2], - outer: [641.8, 576.3], - upperLidTop: [605.4, 564.5], - lowerLidBottom: [602.9, 593], - iris: { center: [594, 573], radius: 18 }, - lineScale: 2.5, - }, - browLeft: { inner: [454.2, 526.5], peak: [355, 512.3], outer: [303.8, 521.4], halfWidth: [7.1, 13, 3.4], sprite: { href: "/avatar/v3/p5/brow-left.webp", x: 299, y: 495, w: 161, h: 48 } }, - browRight: { inner: [524.9, 526.2], peak: [625.3, 510.7], outer: [677.1, 518.6], halfWidth: [2.1, 13, 1.9], sprite: { href: "/avatar/v3/p5/brow-right.webp", x: 520, y: 495, w: 162, h: 44 } }, - noseTip: [493.1, 693.9], - mouthCornerLeft: [418.9, 801.4], - mouthCornerRight: [557.3, 801.8], - upperLipTop: [490.9, 759.4], - lowerLipBottom: [489.5, 828.3], - mouthCenter: [490.7, 791], - chinTip: [488.3, 929.7], - }, - palette: { - ink: "#222221", - sclera: "#D8CEBD", - iris: "#4F3B2C", - irisRing: "#1E1F1F", - lipUpper: "#B1845F", - lipLower: "#CC926C", - mouthLine: "#A5694F", - mouthInner: "#3B201B", - teeth: "#E9E0CF", - blush: "#C0624A", - tear: "#EEE5D3", - pallor: "#9AA3A6", - paper: "#EEE5D3", - motifPetal: "#D0A362", - motifLeaf: "#53626C", - }, - backdrop: { - cognitive: "#ECE3D1", - positive: "#F1DEC2", - negative: "#DCE0E2", - defensive: "#E6DAD3", - energy: "#E2E0D0", - }, -}; diff --git a/apps/web/src/components/avatar/v3/rigs/p6Rig.ts b/apps/web/src/components/avatar/v3/rigs/p6Rig.ts deleted file mode 100644 index d347217..0000000 --- a/apps/web/src/components/avatar/v3/rigs/p6Rig.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* 생성 파일 — docs/avatar-art/linocut-pipeline/scripts/export_rig.py 가 만든다. 손으로 고치지 않는다. */ -import type { LinocutRig } from "../linocutRig"; - -export const P6_LINOCUT_RIG: LinocutRig = { - schemaVersion: "vignette.avatar.v3.rig.v1", - persona: "P6", - canvas: { w: 1005, h: 1566 }, - layers: { - body: { href: "/avatar/v3/p6/body.webp", x: 0, y: 844, w: 1005, h: 722 }, - head: { href: "/avatar/v3/p6/head.webp", x: 37, y: 83, w: 887, h: 1063 }, - hairFront: { href: "/avatar/v3/p6/hair-front.webp", x: 257, y: 399, w: 463, h: 529 }, - faceDetail: { href: "/avatar/v3/p6/face-detail.webp", x: 272, y: 404, w: 439, h: 529 }, - }, - grain: { href: "/avatar/v3/p6/paper-grain.webp", size: 256 }, - lipTexture: { - upper: { href: "/avatar/v3/p6/lip-upper.webp", x: 420, y: 765, w: 158, h: 49 }, - lower: { href: "/avatar/v3/p6/lip-lower.webp", x: 419, y: 804, w: 160, h: 48 }, - shadow: { href: "/avatar/v3/p6/lip-shadow.webp", x: 429, y: 807, w: 140, h: 53 }, - }, - jaw: { - head: { href: "/avatar/v3/p6/jaw-head.webp", x: 251, y: 656, w: 482, h: 315 }, - detail: { href: "/avatar/v3/p6/jaw-detail.webp", x: 251, y: 656, w: 482, h: 315 }, - }, - pivots: { - neck: [506, 1001], - body: [502, 1566], - face: [488, 661], - }, - crops: { - portrait: [0, 0, 1005, 1566], - bust: [0, 40, 1005, 1005], - face: [184.6, 365, 608.6, 608.6], - }, - faceOval: [[480.8, 398.4], [546.8, 397.6], [602, 404.4], [651.9, 421], [683.6, 447.7], [702.8, 483.5], [713.5, 522.5], [717.9, 570.3], [717, 615.8], [714.2, 663.3], [706.6, 713.5], [692.8, 766.8], [673.8, 811.4], [651.7, 845], [623.5, 875.2], [600.1, 897], [575.1, 917.5], [545.4, 934.4], [506.6, 941], [467.6, 937.9], [437.3, 924], [410.8, 905.8], [385.5, 886.6], [354, 859], [328.2, 826.9], [304.3, 783.8], [285.2, 730.7], [272.8, 680.6], [265.7, 633.6], [260, 588.5], [260, 540.9], [267.4, 500.8], [283.6, 463.8], [312.3, 434.4], [359.6, 413.5], [414.2, 402.7]], - landmarks: { - eyeLeft: { - inner: [432.4, 593.7], - outer: [341.4, 583.9], - upperLidTop: [378.1, 566], - lowerLidBottom: [383.4, 600.1], - iris: { center: [386.5, 577.5], radius: 20.5 }, - lineScale: 1, - }, - eyeRight: { - inner: [548.9, 589.6], - outer: [639.8, 570.8], - upperLidTop: [601.4, 555.9], - lowerLidBottom: [598.4, 591.8], - iris: { center: [595, 568], radius: 20 }, - lineScale: 1, - }, - browLeft: { inner: [451.9, 530.3], peak: [367.6, 517.4], outer: [324.1, 521.5], halfWidth: [15.7, 11, 7.9], sprite: { href: "/avatar/v3/p6/brow-left.webp", x: 323, y: 504, w: 134, h: 49 } }, - browRight: { inner: [518.3, 525.4], peak: [607, 510.3], outer: [652.7, 515.6], halfWidth: [5.7, 25.5, 4.9], sprite: { href: "/avatar/v3/p6/brow-right.webp", x: 514, y: 492, w: 137, h: 57 } }, - noseTip: [495.6, 716.5], - mouthCornerLeft: [423.8, 811], - mouthCornerRight: [575.1, 805.1], - upperLipTop: [498.5, 778], - lowerLipBottom: [501.5, 841.2], - mouthCenter: [499.8, 803.5], - chinTip: [506.1, 930.6], - }, - palette: { - ink: "#242322", - sclera: "#D8CEBD", - iris: "#4F3B2C", - irisRing: "#1E1F1F", - lipUpper: "#A27659", - lipLower: "#A47459", - mouthLine: "#2C2625", - mouthInner: "#3B201B", - teeth: "#E9E0CF", - blush: "#C0624A", - tear: "#EEE5D3", - pallor: "#9AA3A6", - paper: "#EEE5D3", - motifPetal: "#D0A362", - motifLeaf: "#53626C", - }, - backdrop: { - cognitive: "#ECE3D1", - positive: "#F1DEC2", - negative: "#DCE0E2", - defensive: "#E6DAD3", - energy: "#E2E0D0", - }, -}; diff --git a/apps/web/src/components/avatar/v3/rigs/p7Rig.ts b/apps/web/src/components/avatar/v3/rigs/p7Rig.ts deleted file mode 100644 index 21d5a0b..0000000 --- a/apps/web/src/components/avatar/v3/rigs/p7Rig.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* 생성 파일 — docs/avatar-art/linocut-pipeline/scripts/export_rig.py 가 만든다. 손으로 고치지 않는다. */ -import type { LinocutRig } from "../linocutRig"; - -export const P7_LINOCUT_RIG: LinocutRig = { - schemaVersion: "vignette.avatar.v3.rig.v1", - persona: "P7", - canvas: { w: 1005, h: 1566 }, - layers: { - body: { href: "/avatar/v3/p7/body.webp", x: 0, y: 725, w: 1005, h: 841 }, - head: { href: "/avatar/v3/p7/head.webp", x: 174, y: 151, w: 648, h: 892 }, - hairFront: { href: "/avatar/v3/p7/hair-front.webp", x: 220, y: 413, w: 535, h: 423 }, - faceDetail: { href: "/avatar/v3/p7/face-detail.webp", x: 276, y: 423, w: 425, h: 513 }, - }, - grain: { href: "/avatar/v3/p7/paper-grain.webp", size: 256 }, - lipTexture: { - upper: { href: "/avatar/v3/p7/lip-upper.webp", x: 406, y: 763, w: 170, h: 50 }, - lower: { href: "/avatar/v3/p7/lip-lower.webp", x: 408, y: 801, w: 164, h: 49 }, - shadow: { href: "/avatar/v3/p7/lip-shadow.webp", x: 428, y: 807, w: 126, h: 51 }, - }, - jaw: { - head: { href: "/avatar/v3/p7/jaw-head.webp", x: 251, y: 652, w: 478, h: 321 }, - detail: { href: "/avatar/v3/p7/jaw-detail.webp", x: 251, y: 652, w: 478, h: 321 }, - }, - pivots: { - neck: [492, 1002], - body: [502, 1566], - face: [489, 660], - }, - crops: { - portrait: [0, 0, 1005, 1566], - bust: [0, 40, 1005, 1005], - face: [193.3, 384.2, 590, 590], - }, - faceOval: [[490.5, 415.8], [555.3, 417.3], [609.5, 425.4], [658.6, 442.5], [688.8, 469], [706, 502.9], [714.3, 539.4], [716.2, 584.5], [712.6, 628.1], [706.8, 673.7], [696.8, 722.2], [681.3, 774.6], [662, 818.1], [641.5, 850.8], [615.8, 880.4], [592.8, 902], [566.9, 921.9], [534.8, 937.4], [491.9, 942.6], [449, 938.5], [416.4, 923.9], [389.5, 904.8], [365.4, 883.9], [338.9, 854.2], [317.7, 821], [298, 777], [282.2, 723.6], [271.5, 674.4], [265.1, 628.9], [260.4, 585.3], [262.1, 540.3], [270.9, 503.5], [289.1, 469.4], [320.3, 442.7], [369.9, 425.3], [425, 417.4]], - landmarks: { - eyeLeft: { - inner: [433.4, 588.7], - outer: [345.6, 578.8], - upperLidTop: [381.3, 566.3], - lowerLidBottom: [387.8, 592.8], - iris: { center: [387.5, 574.5], radius: 16.5 }, - lineScale: 2.5, - }, - eyeRight: { - inner: [550.7, 589.9], - outer: [637.7, 578.5], - upperLidTop: [601.5, 566.1], - lowerLidBottom: [596.3, 593.2], - iris: { center: [593.5, 572.5], radius: 16.5 }, - lineScale: 2.5, - }, - browLeft: { inner: [455.8, 527], peak: [369, 521.2], outer: [324.2, 527.5], halfWidth: [5.4, 8, 3.4], sprite: { href: "/avatar/v3/p7/brow-left.webp", x: 324, y: 509, w: 137, h: 29 } }, - browRight: { inner: [526.5, 526.8], peak: [619.5, 519.8], outer: [667.5, 527], halfWidth: [6.3, 8, 2.9], sprite: { href: "/avatar/v3/p7/brow-right.webp", x: 521, y: 509, w: 150, h: 28 } }, - noseTip: [493.5, 712.5], - mouthCornerLeft: [422.6, 806.5], - mouthCornerRight: [558.9, 808.4], - upperLipTop: [493.8, 774], - lowerLipBottom: [492.3, 835], - mouthCenter: [493.2, 800.3], - chinTip: [491.8, 932.4], - }, - palette: { - ink: "#202122", - sclera: "#D8CEBD", - iris: "#4F3B2C", - irisRing: "#1E1F1F", - lipUpper: "#BA9069", - lipLower: "#C4936D", - mouthLine: "#322F2B", - mouthInner: "#3B201B", - teeth: "#E9E0CF", - blush: "#C0624A", - tear: "#EEE5D3", - pallor: "#9AA3A6", - paper: "#EEE5D3", - motifPetal: "#D0A362", - motifLeaf: "#53626C", - }, - backdrop: { - cognitive: "#ECE3D1", - positive: "#F1DEC2", - negative: "#DCE0E2", - defensive: "#E6DAD3", - energy: "#E2E0D0", - }, -}; diff --git a/apps/web/src/pages/AvatarLab.tsx b/apps/web/src/pages/AvatarLab.tsx index e056302..1cac93c 100644 --- a/apps/web/src/pages/AvatarLab.tsx +++ b/apps/web/src/pages/AvatarLab.tsx @@ -8,7 +8,7 @@ import { useEffect, useMemo, useRef, useState, type ChangeEvent } from "react"; import DebugFace from "../components/avatar/v3/DebugFace"; import LinocutAvatar from "../components/avatar/v3/LinocutAvatar"; import LinocutMotif from "../components/avatar/v3/LinocutMotif"; -import { labLinocutRigs } from "../components/avatar/v3/rigs"; +import { P1_LINOCUT_RIG } from "../components/avatar/v3/rigs/p1Rig"; import type { LinocutRig } from "../components/avatar/v3/linocutRig"; import { CHANNEL_IDS } from "../components/avatar/engine/channels"; import { createAvatarEngine, type AvatarEngine, type DebugSnapshot } from "../components/avatar/engine/engine"; @@ -30,10 +30,8 @@ const PERSONA_OPTIONS: Array<{ code: string; label: string }> = [ { code: "P7", label: "P7 도현" }, ]; -/** 리노컷 리그가 있는 페르소나만. 없는 페르소나는 debug 렌더러를 강제한다. - 공통 파이프라인이 만든 pNRig.ts를 전부 모은 값이라, 새 페르소나가 빌드되는 - 즉시(재빌드 없이) 이 Lab에서 보인다 — v3/rigs/index.ts labLinocutRigs() 참고. */ -const LINOCUT_RIGS: Partial> = labLinocutRigs(); +/** 리노컷 리그가 있는 페르소나만. 없는 페르소나는 debug 렌더러를 강제한다. */ +const LINOCUT_RIGS: Partial> = { P1: P1_LINOCUT_RIG }; type RendererMode = "linocut" | "debug"; type FeedbackMode = "immersive" | "ambient" | "coached"; @@ -82,19 +80,11 @@ function seedFromQuery(): number { return Number.isFinite(parsed) ? parsed : 1; } -/** ?persona=P3 같은 쿼리로 처음 고를 페르소나를 정한다(캡처 도구용). 모르는 코드거나 - 없으면 기본 P1. */ -function personaFromQuery(): string { - const raw = new URLSearchParams(window.location.search).get("persona"); - const upper = raw?.toUpperCase() ?? ""; - return PERSONA_OPTIONS.some((p) => p.code === upper) ? upper : "P1"; -} - export default function AvatarLab() { const seed = useMemo(seedFromQuery, []); const clock = useRef(createClock()).current; - const [personaCode, setPersonaCode] = useState(personaFromQuery); + const [personaCode, setPersonaCode] = useState("P1"); const [avatarState, setAvatarState] = useState("idle"); const [openness, setOpenness] = useState(0.4); const [reducedMotion, setReducedMotion] = useState(false); diff --git a/docs/TODO.md b/docs/TODO.md index 58a6385..e1c856a 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -27,7 +27,7 @@ | ADMIN-OPENROUTER-001 | 새 사용자 OAuth finish 성공을 관측한다. | API/web 복구·실제 PostgreSQL rollback·배포/보존 census·공개 관리자 UI를 수용했다. OpenRouter UI 상태는 미연결이고 health의 Jev `live_verified=false`는 live 검증이 아니며, 새 OAuth finish 성공 관측 전까지 진행 중이다. [운영 영수증](./ops/evidence/admin-openrouter-recovery-2026-09-22.json)을 따른다. | | JEV-001 | 충분한 한국어 독립 평가와 전체 지연 반복 비교를 마친다. | 운영 API, 합성·인증 브라우저 3턴 runtime과 DB 감정 영속화는 수용했지만 품질 승격은 보류한다. API legacy baseline 1191 passed·1 skipped·0 failed, gateway 82 passed는 구현 회귀 증거이며 품질·성능 우위 증거가 아니다. 위 품질 게이트를 닫은 뒤 전체 사용자 흐름 완료 여부를 판단하며, 최신 probe·수용/반려 근거는 [Jev 결정문](./decisions/jev-client-affect.md)을 따른다. | | JEV-002 | v2 평가·표현 프로토콜을 운영에 올리고 실제 대사 영향을 관측한다. | 로컬 구현·실 Jev 16/16·실 DB RLS 15항목·실 스택 레이아웃을 수용했다. 운영은 migration 24를 먼저 적용한 뒤 API·web을 배포한다(스키마 없이 배포하면 readiness가 기동을 막는다). 로컬 `claude_cli` 생성 엔진 시간 초과로 v2 지시가 실제 대사에 미치는 영향은 미관측이다. 보정 관찰(`a_understood` 문구, `a_sore_spot` 과다 선택)은 전문가 검토 대상이다. [v2 프로토콜](./decisions/jev-client-affect-v2.md) | -| AVATAR-V3 | 가상 내담자 아바타 v3(표현 엔진·반응 모션·리노컷 리그)를 P1부터 세션에 연결한다. | 1단계 엔진·`/dev/avatar-lab` 수용(check:avatar-presets 통과·E2E 6 passed), 2단계-A P1 리노컷 레이어 수용. 2단계-B-1b 렌더러 기능 계약·B-1c 모티프 수용. 진행: 2단계-B-1d 표정 판독성 개정(입술 결 래스터, 눈썹 중심선 랜드마크, 이마 주름, 작은 크기 선 보정)과 발화층 립싱크(한글 비짐 + 오디오 선분석, 결정문 §5.4 — 구현·검증됨), 발화 동반층 §5.5·하관 띠 변형(턱·코·볼)·원화 픽셀 입술 구현, 2단계-B-1d 수용(소유자 시각 확인 대기). 2단계-B-2 세션 연결 수용·운영 반영(master `613bcb60`, 공개 웹 Pages `dd96c2d5`, 2026-10-01). P2~P7 리노컷 캐스트 리그 수용·세션 활성화(2026-10-01, 결정문 §8.4.1, 공개 웹 미반영). 남음: P2~P7 공개 웹 반영, 운영 화면 소유자 시각 확인, 흰자 색 원화 실측·`data-render-mode` v3 반영·공황 창백 오버레이 사각 경계(후속 다듬기), 3단계 API `avatar_cue`·지문 어휘 가이드, 4단계 7명 외형 SSOT. [결정문](./decisions/avatar-expression-engine-v3.md) | +| AVATAR-V3 | 가상 내담자 아바타 v3(표현 엔진·반응 모션·리노컷 리그)를 P1부터 세션에 연결한다. | 1단계 엔진·`/dev/avatar-lab` 수용(check:avatar-presets 통과·E2E 6 passed), 2단계-A P1 리노컷 레이어 수용. 2단계-B-1b 렌더러 기능 계약·B-1c 모티프 수용. 진행: 2단계-B-1d 표정 판독성 개정(입술 결 래스터, 눈썹 중심선 랜드마크, 이마 주름, 작은 크기 선 보정)과 발화층 립싱크(한글 비짐 + 오디오 선분석, 결정문 §5.4 — 구현·검증됨), 발화 동반층 §5.5·하관 띠 변형(턱·코·볼)·원화 픽셀 입술 구현, 2단계-B-1d 수용(소유자 시각 확인 대기). 2단계-B-2 세션 연결 수용·운영 반영(master `613bcb60`, 공개 웹 Pages `dd96c2d5`, 2026-10-01). 남음: 운영 화면 소유자 시각 확인, 3단계 API `avatar_cue`·지문 어휘 가이드, 4단계 7명 외형 SSOT. [결정문](./decisions/avatar-expression-engine-v3.md) | | USER-LATENCY (JEV-001) | 실제 경로에서 text display·voice onset·완료 준비를 분리 측정한다. | 안전 검사를 유지한 최적화 뒤 반복 비교로 사용자 체감 지연을 평가한다. 현재 CUA 관측상한과 소수 audit은 속도 개선 증거가 아니다. | | G6-PRODUCER-CONFLICT | 기존 submission id와 content hash를 읽기 전용으로 대조한다. | `SupervisionResearchConflictError`의 동일 id 재실행 idempotency 충돌을 해소하고 해당 테스트와 실제 cycle 성공을 확인한다. Jev·세션 경로와 분리된 기존 운영 충돌이며, 이번 배포 회귀로 단정하지 않는다. | | VNET-001 | `vnet.18ka.net` 공개 전환의 외부 설정을 마친다. | DNS, Cloudflare zone 권한, Google redirect URI가 모두 준비된 뒤 live 검증. | diff --git a/docs/avatar-art/linocut-pipeline/README.md b/docs/avatar-art/linocut-pipeline/README.md index 688735c..531606a 100644 --- a/docs/avatar-art/linocut-pipeline/README.md +++ b/docs/avatar-art/linocut-pipeline/README.md @@ -16,15 +16,11 @@ 3. `segmentation.py` — `layers/{body,head,hairFront}.png`(v1) + manifest 기준 섹션 4. `layers_v2.py` — `layers/v2/{body,head,hairFront}.png`(턱 밑 띠·잔머리 halo 보정) 5. `face_detail.py` — `layers/v2/face-detail.png` -6. `brow_sprite.py` — `layers/v2/brow-{left,right}.png`(원화 눈썹 스프라이트) + - `manifest.eyeLineScale`(눈꺼풀 선 굵기 배율) + `manifest.iris`(홍채 실측, 플래그와 - 무관하게 항상 기록). 아래 "`browSpriteFromArt`/`eyeLineScaleFromArt`/ - `irisFromArt`" 절 참고. -7. `paper_grain.py` — `layers/v2/paper-grain.png`(다른 단계와 독립, 순서 유연) -8. `lip_texture.py` — `layers/v2/lip-{upper,lower,shadow}.png` -9. `jaw_pieces.py` — `layers/v2/jaw-{head,detail}.png` -10. `export_rig.py` — WebP 게시(`apps/web/public/avatar/v3//`) + `rigs/` 생성 -11. `final_previews.py` — 게시된 WebP로 모션·눈/입 확대 미리보기 +6. `paper_grain.py` — `layers/v2/paper-grain.png`(다른 단계와 독립, 순서 유연) +7. `lip_texture.py` — `layers/v2/lip-{upper,lower,shadow}.png` +8. `jaw_pieces.py` — `layers/v2/jaw-{head,detail}.png` +9. `export_rig.py` — WebP 게시(`apps/web/public/avatar/v3//`) + `rigs/` 생성 +10. `final_previews.py` — 게시된 WebP로 모션·눈/입 확대 미리보기 이 순서는 전달받은 작업 설명의 번호(주제별 묶음)와 다르다 — 특히 `final_previews`는 `export_rig`가 쓴 `export-rig-report.json`을 읽으므로 반드시 `export_rig` *다음*이어야 @@ -83,17 +79,7 @@ "faceDetail": { // 점(기미) 등 랜드마크로 안 나오는 얼굴 반점. 없으면 빈 배열(점 없는 캐릭터도 된다). - "moles": [{ "center": [661.3, 627.9], "radius": 20.0 }], - // "features"(기본)면 눈·눈썹·입 주변 타원만 faceDetail(원화 픽셀) 범위다. "face"면 - // 얼굴 오벌 전체(moles 포함)로 넓어진다 — 캐스트 확장(새 페르소나)은 "face"를 쓴다. - // 자세한 설계는 아래 "faceDetail.region" 절 참고. - "region": "face", - // region="face"일 때만 쓴다. 얼굴 오벌 경계에서 안쪽으로 줄이는 폭(px, 기본 8) — - // 원화 윤곽 잉크선이 faceDetail 범위 밖에 남도록 원화를 보고 정한다. - "faceInsetPx": 8, - // true면 제외 영역(눈·눈썹·입) 안을 채우는 faceless(F) 픽셀의 색을 주변 원화 - // 피부에 맞춰 평행이동한다(기본 false). 아래 "faceDetail.matchFacelessColor" 절 참고. - "matchFacelessColor": true + "moles": [{ "center": [661.3, 627.9], "radius": 20.0 }] }, // 렌더러 회전/스케일 중심점(결정문 §8.4). 원화를 보고 목·몸통·얼굴 중심을 정한다. @@ -134,150 +120,6 @@ P1의 기존 `face-detail.png`·`jaw-detail.png`·해당 webp·`p1Rig.ts`는 눈 그 고정본과 바이트 단위로 같아지게 한다. **새 페르소나는 이 필드를 넣지 않는다** — 처음부터 중심선 보정 좌표로 빌드되므로 과거 좌표를 따로 고정할 이유가 없다. -### `browHalfWidthFromArt` — 눈썹 굵기를 원화에서 잴지 여부(기본 true, 캐스트 확장) - -```jsonc -{ - "browHalfWidthFromArt": false -} -``` - -렌더러(`v3/linocutGeometry.ts` buildBrow)의 눈썹 반두께는 기본으로 P1 설계값 -(`BROW_STOPS` = [4.5, 3.2, 1.0])이 아니라 **원화에서 실측한 값**을 쓴다 -(`brow_centerline.py`가 눈썹 잉크 띠의 중심선 법선 방향 반두께를 안쪽·꼭짓점·바깥 -세 점에서 재 `manifest.browCenterline.halfWidth{Left,Right}`에 남기고, -`export_rig.py`가 `browHalfWidthFromArt`가 `false`가 아니면(필드 생략 시 기본 true) -그 값을 `rig.landmarks.browLeft/Right.halfWidth`로 내보낸다). 굵은 눈썹 캐릭터(성인· -남성 등)가 P1 고정 굵기로 그려져 원화와 달라지는 문제를 캐스트 확장에서 없앤다. - -P1은 `false`로 둔다 — 기존 `p1Rig.ts`가 2단계-B에서 소유자 검수를 거쳐 고정됐으므로 -(§8.4 눈썹 항목) 재실행해도 바뀌면 안 된다. 측정값 자체는 `browHalfWidthFromArt` -설정과 무관하게 항상 manifest에 남는다(참고용). **새 페르소나는 이 필드를 생략한다** -(기본 true로 원화 실측값을 쓴다). - -### `browSpriteFromArt`/`eyeLineScaleFromArt`/`irisFromArt` — 눈썹 스프라이트·눈꺼풀 굵기·홍채(모두 기본 true) - -```jsonc -{ - "browSpriteFromArt": false, - "eyeLineScaleFromArt": false, - "irisFromArt": false -} -``` - -굵은 판화 눈썹 캐릭터는 벡터 눈썹(P1 설계 굵기에 맞춘 리본·털 획)이 "갈색빛 회색 -막대"로 그려진다(결정문 §8.4.1). 입술과 같은 "중립 렌더 = 원화" 원칙을 적용한다: - -- `browSpriteFromArt`(기본 true): `brow_sprite.py`가 눈썹 중심선(`manifest.landmarks. - eyebrowLeft/Right` + `browCenterline.halfWidth{Left,Right}`)을 따라 띠를 만들고, - 그 안의 원화 잉크(Otsu+diff 연결성분, `brow_centerline.py`와 같은 기준)에서 머리카락 - (`layers/v2/hairFront.png` 알파 — 분할 카테고리 자체가 아니다. 이 화풍에서는 - mediapipe 머리카락 분할이 두꺼운 눈썹 잉크 자체를 머리카락으로 오탐해, 분할 결과를 - 그대로 빼면 눈썹 잉크 태반이 사라진다. 실제로 화면에서 눈썹 위에 겹쳐 그려질 - hairFront 알파로 기준을 좁혀야 "앞머리 가닥이 눈썹과 같이 움직이면 안 된다"는 - 의도를 과하게 지우지 않고 구현할 수 있다)를 뺀 자리만 `brow-{left,right}.png`로 - 뗀다. `export_rig.py`가 입술 결과 같은 무손실(exact) WebP로 게시하고 - `rig.landmarks.browLeft/Right.sprite`를 채운다 — 렌더러(`linocutGeometry.ts` - `computeBrowSpriteStripTransforms`)가 벡터 몸통·털 획 대신 이 스프라이트를 세로 - 16띠로 나눠 옮긴다(안쪽 올림 그늘은 그대로 벡터로 그린다). -- `eyeLineScaleFromArt`(기본 true): `brow_sprite.py`가 눈 폭 가운데 60% 열마다 - `upperLidTop.y-12`~홍채 중심 y 사이 첫 연속 어두운(lum<80) 구간 길이의 중앙값을 - 재, P1 기준값(2.0px, 상수로 고정 — 근거는 `brow_sprite.py` 주석 참고)으로 나눈 - 비를 `[1.0, 2.5]`로 clamp한 값을 `manifest.eyeLineScale`에 남긴다. `export_rig.py`가 - `rig.landmarks.eyeLeft/Right.lineScale`로 내보내고, 렌더러가 눈꺼풀·쌍꺼풀· - 아랫눈꺼풀 굵기에 곱한다(홍채·흰자는 그대로). - -- `irisFromArt`(기본 true): mediapipe iris 랜드마크(468~477)는 실제 사람 눈 비례로 - 학습돼, 판화 잉크로 그려진 홍채보다 반지름을 크게(또는 중심을 다르게) 잡을 수 있다 - (2차 보정 T3, P7 실측: 가로 반지름 기준 리그 대비 약 11~15% 작다). `brow_sprite.py`가 - (browSpriteFromArt/eyeLineScaleFromArt와 무관하게 항상) 원화에서 홍채 가로 반지름· - 중심을 재 `manifest.iris`에 남기고, `export_rig.py`가 `irisFromArt`가 `false`가 - 아니면 `rig.landmarks.eyeLeft/Right.iris`를 이 실측값으로 채운다(세로 경계는 윗눈꺼풀 - 잉크와 홍채가 같은 톤으로 이어져 가를 수 없어, 가로 반지름 + 아래쪽 경계로만 - 중심y를 역산한다 — 자세한 방법은 `brow_sprite.py` `measure_iris` 참고). - -P1은 셋 다 `false`다 — 이미 고정 벡터 눈썹·굵기·홍채 위치로 빌드·검수됐고, -`eyeLineScaleFromArt`의 경우 P1 자체가 lineScale의 기준값이라 바뀌면 안 된다. -**새 페르소나는 이 필드들을 생략한다**(기본 true로 원화 스프라이트·실측 굵기·실측 -홍채를 쓴다). - -### `faceDetail.region` — faceDetail이 그려질 범위(기본 "features", 캐스트 확장 "face") - -`face_detail.py`가 눈·눈썹·입 "제외 영역"(이목구비 잉크선) 밖에서 원화(base-front) 픽셀을 -쓰는 범위를 정한다. 레이어 픽셀은 항상 base-faceless(F)에서 가져오므로(README 상단 -단계 설명 참고), 이 범위 밖은 F뿐이라 — 다크서클·볼 해칭·팔자 주름·턱 그늘 같은 -원화 고유의 판화 해칭이 전혀 남지 않는다. - -- `"features"`(기본, P1 그대로): 눈·입 주변 타원(눈두덩~다크서클, 입가 음영)에서만 - 원화 픽셀을 쓴다. 나머지 얼굴 피부는 F(매끈한 기본형)다. -- `"face"`(캐스트 확장): 얼굴 오벌(export_rig.compute_face_oval, 다른 단계와 같은 - 36점 루프·1.04 배율) 전체에서 원화 픽셀을 쓴다. moles 타원은 그대로 합쳐진다. - - 오벌을 그대로 쓰면 원화 얼굴 윤곽 잉크선(jaw_pieces.py 기준 오벌 경계 ±18px - 안팎)이 영역 안에 들어와, F 기준으로 이미 그려진 head 레이어의 윤곽선과 겹쳐 - 두 겹 턱선(머리 이동 시차)이 생긴다 — `faceInsetPx`(기본 8)만큼 오벌을 안쪽으로 - 줄여(거리변환 기준, 방사형 스케일이 아니다) 이 잉크선을 영역 밖에 둔다. 페더는 - 다른 영역과 같은 `REGION_FEATHER_PX`(12px, sigma 6)를 쓴다. 새 페르소나마다 - 원화 윤곽선 위치가 다르므로, 원화를 보고 `faceInsetPx`를 정해 보고해야 한다. - - 머리카락 알파 0 규칙(머리카락 위에는 faceDetail을 그리지 않는다)은 그대로이되, - `"face"`에서는 머리카락 마스크를 **원화(front)와 faceless(F) 분할 결과의 - 합집합**으로 만든다. 둘의 앞머리 가닥 위치가 달라(codex 편집본 시차) 한쪽 - 분할만 쓰면 다른 쪽 잔머리가 마스크 밖에 남아 유령 가닥이 생기기 때문이다. - `"features"`는 기존처럼 F 분할만 쓴다(P1 회귀 보존). - -manifest.layersV2.faceDetail.region/faceInsetPx에 남는다. - -### `faceDetail.matchFacelessColor` — 제외 영역 F 색을 원화 피부에 맞추기(기본 false) - -`true`면 제외 영역(눈·눈썹·입) 안을 채우는 F 픽셀의 색을 주변 원화 피부에 맞춘다. -영역(eyeLeft/eyeRight/browLeft/browRight/mouth)마다 그 영역 바깥 `RING_PX`(12px) -고리에서 어두운 잉크(원화 명도<110) 픽셀을 뺀 나머지로 원화·F 평균 RGB 차(ΔRGB)를 -구하고, 그 영역 전체의 F 픽셀을 ΔRGB만큼 그대로 평행이동(가산)한다 — 보간·블러· -패치 합성은 하지 않는다(과거 P1에서 그 방식이 회색 얼룩으로 반려된 적이 있다). -영역별 ΔRGB는 콘솔과 `manifest.layersV2.faceDetail.matchFacelessColorDeltaRgb`에 -남는다. P1은 `false`(기존 결과 그대로)다. - -## 캐스트 확장 0단계 — 원화 3장을 준비하는 공통 도구 - -새 페르소나의 후보 원화는 크기·구도가 P1과 다르다(결정문 §8.2 생성 규칙은 그대로 -따르되, 캔버스에 맞추는 과정이 필요하다). 아래 두 도구가 "사람이 고른 후보 원화 + -codex 편집본 2장"을 파이프라인이 기대하는 `base/base-front.png`· -`base/base-faceless.png`·`raw/body.png`(모두 캔버스 1005x1566)로 만든다. 순서대로 -쓴다: - -1. `register_edit.py` — codex가 만든 얼굴 없는 기본형·body 편집본을 ECC 아핀 - 정렬(`cv2.findTransformECC`, `MOTION_AFFINE`)로 후보 원화(ref) 좌표계에 맞춘다. - edited가 ref와 크기가 달라도 된다(먼저 ref 크기로 리사이즈한다). - - ```bash - /python.exe register_edit.py \ - --mode faceless --report - /python.exe register_edit.py \ - --mode body --report - ``` - - 콘솔·리포트 JSON에 정렬 품질 지표(변환 배율·회전·이동, 마스크 안 Canny 에지 - 챔퍼 거리 중앙값·p90, faceless는 얼굴 윤곽 띠 챔퍼와 눈·눈썹·입 상자 잉크 비율)를 - 낸다 — **판정은 하지 않는다**(수용 기준은 출력만, 사람/오케스트레이터가 본다). - -2. `normalize_base.py` — 위 세 장(원래 ref, 정렬된 faceless, 정렬된 body)에 **같은** - 아핀 변환 하나(등방 배율 + 평행이동, 회전 없음)를 적용해 캔버스로 옮긴다. 배율· - 이동은 P1 `base-front.png`의 눈 중심·눈 사이 거리·눈-턱 거리 비율에 맞춘다 - (P1과 머리 크기 비율이 같아지도록). - - ```bash - /python.exe normalize_base.py \ - --front --faceless --body - ``` - - 출력: `/base/base-front.png`·`base/base-faceless.png`(1005x1566 - RGB, 위아래 빈 곳은 원본 가장자리 띠 반사 패딩)·`raw/body.png`(1005x1566, - 빈 곳은 `#00ff00`)·`base/normalize.json`(배율·오프셋·덮인 범위·입력 파일 - sha256). - -이 둘은 `persona.json`이 아직 없어도 돈다(두 스크립트 다 페르소나 폴더 경로만 -받고 `load_persona_config`에 의존하지 않는다) — persona.json은 이 산출물을 보고 -그다음에 쓴다(아래 "새 페르소나를 추가할 때" 3번). - ## 모델 파일(저장소에 없음) `scripts/_models/`에 MediaPipe 모델을 받아 둔다(모든 페르소나가 공유, `.gitignore`의 @@ -295,12 +137,8 @@ python 환경은 numpy·Pillow·scipy·opencv·mediapipe가 필요하다. 1. 원화 2장: `/base/base-front.png`(정면 기본형)과 `base-faceless.png`(같은 그림에서 눈·눈썹·입만 지운 것). 결정문 §8.2 생성 규칙을 따른다. - 후보 원화 크기·구도가 P1과 다르면(캐스트 확장이 보통 그렇다) 바로 두지 말고 - 위 "캐스트 확장 0단계" 도구(`register_edit.py` → `normalize_base.py`)로 캔버스에 - 맞춰 만든다. 2. `/raw/body.png`: body 레이어가 head_mask로 가려지는 목 상단 영역을 - 메울 재생성 참조 편집본(크로마키 초록 배경, base-front와 같은 정렬). 역시 캐스트 - 확장이면 위 도구가 만든다. + 메울 재생성 참조 편집본(크로마키 초록 배경, base-front와 같은 정렬). 3. `/persona.json`: 위 스키마대로 작성한다. 특히 `styleFrame`· `paletteSamples`·`pivots`는 원화를 눈으로 보고 정해야 한다(자동 유도 불가). 4. (선택) `/motif/`: 모티프 스프라이트는 이 라운드 범위 밖이다 — 오케스트레이터가 @@ -309,45 +147,3 @@ python 환경은 numpy·Pillow·scipy·opencv·mediapipe가 필요하다. 그 다음 `run_pipeline.py `를 실행하고, 출력된 검사 수치(halo%, 평균절대차 등 — 각 단계 스크립트가 콘솔에 찍고 manifest.json에도 남긴다)를 기준치와 비교해 판정한다. - -## QA 캡처(`qa/capture_cast_sheet.mjs`) - -리그가 게시된(`run_pipeline.py`가 `export_rig.py`까지 끝낸) 페르소나를 Lab -(`/dev/avatar-lab`)에서 조작해 캐스트 시트 5장을 만든다. - -```bash -node qa/capture_cast_sheet.mjs [--base http://localhost:5173] -``` - -- ``: `P1`처럼 대문자 페르소나 코드. `apps/web/src/components/avatar/v3/rigs/Rig.ts`의 - `_LINOCUT_RIG` export와 `docs/avatar-art/-linocut/base/base-front.png`를 읽는다. -- Lab에 `?persona=` 쿼리(`v3/rigs/index.ts` `labLinocutRigs()` + `AvatarLab.tsx` - `?persona=` 지원, 세션 화면과 무관)로 들어가 그 페르소나를 바로 고른다. -- `--base`가 가리키는 dev server가 이미 떠 있어야 한다(이 스크립트는 서버를 - 띄우거나 끄지 않는다). 기본값 `http://localhost:5173`. - -산출(``에 JPG, 모두 긴 변 ≤1000px — 개별 캡처 PNG는 `/frames/`): - -- `sheet-expr-bust.jpg` / `sheet-expr-face.jpg`: 16표정(강도 1) × bust/face 크롭, - 4×4 격자. -- `sheet-viseme.jpg`: 비짐 9종(X·M·A·EO·O·U·EU·I·E), 입 주변 확대, 3×3 격자. Lab에 - 채널을 직접 주입하는 고정 기능이 없어(엔진 값을 바꾸지 않는 이 패킷 범위에서는 - 추가하지 않는다) 비짐 9종이 모두 나오는 문장을 재생하며 50ms 간격으로 - (시각·비짐·스크린샷)을 한 패스에 모아 두고, 각 비짐이 가장 오래 유지된 구간의 - 중앙 표본을 그 비짐의 대표 프레임으로 쓴다(스캔과 재생을 두 패스로 나눠 같은 - 시각을 다시 재생하면 클릭~엔진 시계 리셋 사이 수십ms급 비결정성 때문에 스캔 때와 - 다른 비짐이 잡힐 수 있음을 실측으로 확인했다 — 항상 한 패스로 찍는다). - `/viseme-timeline.json`에 전체 표본을 남긴다. -- `neutral-vs-art.jpg`: neutral 렌더(portrait 크롭)와 `base/base-front.png`를 - 나란히 둔 것 — 리그 피벗·배율이 원화와 맞는지 한눈에 비교한다. -- `face-art-vs-render.jpg`: 고해상도 얼굴 대조(2차 보정 T4b). `rig.crops.face` - 영역을 원화는 500px, 렌더(neutral·grief·joy·angry)는 760px로 키워 2줄 3칸으로 - 나란히 두고 최종 긴 변 ≤1000px로 다시 줄인다 — 눈·눈썹처럼 작은 차이가 bust - 전체 비교에서는 묻히는 부위를 본다. - -**깜빡임/유휴 시선 흔들림 회피**(`screenshotEyeStable`, 2차 보정 T4a): `neutral-vs-art.jpg`· -`face-art-vs-render.jpg`의 정적 비교 스크린샷은 Reduced motion을 다시 켠 뒤(viseme -구간에서 꺼 둔 채로 남는다 — 유휴층 breath·blink·시선 흔들림이 채널에 섞여 든다) -700ms 간격 3연사 중 eye-left outline(흰자 바탕) bbox 높이가 가장 큰 장을 쓴다 — -neutral-vs-art가 깜빡임 중에 찍힌 사례(P2)와, 원화 대비 렌더 홍채 아래 흰 띠가 -유휴 시선 흔들림(gazeY) 중에만 나타나는 것을 실측으로 확인했다(2026-10-01). diff --git a/docs/avatar-art/linocut-pipeline/qa/capture_cast_sheet.mjs b/docs/avatar-art/linocut-pipeline/qa/capture_cast_sheet.mjs deleted file mode 100644 index 6e7243d..0000000 --- a/docs/avatar-art/linocut-pipeline/qa/capture_cast_sheet.mjs +++ /dev/null @@ -1,394 +0,0 @@ -// 공통 리노컷 리그 — 캐스트 QA 캡처 도구(T7). scratch/linocut-review/capture_out4.mjs의 -// 방식(apps/web의 playwright, auth/me 모킹, Lab 조작, 일시정지 뒤 스크린샷)을 그대로 -// 따른다. 페르소나 코드 하나를 받아 AvatarLab(/dev/avatar-lab?persona=)을 조작해 -// 5장을 만든다: sheet-expr-bust.jpg, sheet-expr-face.jpg, sheet-viseme.jpg, -// neutral-vs-art.jpg, face-art-vs-render.jpg. -// -// 비짐 9종은 Lab에 고정 기능이 없어(엔진 채널을 직접 주입할 UI가 없다) 발화 재생 중 -// 프레임을 쓴다: 비짐 글자를 담는 텍스트를 재생하면서 매 프레임을 처음부터 다시 -// 재생해 정확한 오프셋만큼 기다리는 방식(capture_speech_strip*.mjs 패턴)으로 (t, -// 비짐) 타임라인을 훑고, 각 목표 비짐이 가장 오래 유지된 구간의 중앙 지점을 그 -// 비짐의 대표 프레임으로 쓴다. -// -// 사용: node capture_cast_sheet.mjs [--base http://localhost:5173] -import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { createRequire } from "node:module"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const REPO = path.join(__dirname, "../../../../"); -const require = createRequire(path.join(REPO, "apps/web/package.json")); -const { chromium } = require("playwright"); -const esbuild = require("esbuild"); -const Module = require("node:module"); - -const argv = process.argv.slice(2); -const CODE = argv[0]; -const outDirArg = argv[1]; -if (!CODE || !outDirArg) { - console.error("사용: node capture_cast_sheet.mjs [--base http://localhost:5173]"); - process.exit(1); -} -const baseIdx = argv.indexOf("--base"); -const BASE_URL = baseIdx !== -1 ? argv[baseIdx + 1] : "http://localhost:5173"; -const OUT = path.resolve(outDirArg); -const FRAMES = path.join(OUT, "frames"); -mkdirSync(OUT, { recursive: true }); -mkdirSync(FRAMES, { recursive: true }); - -const SLUG = CODE.toLowerCase(); -const CODE_UPPER = CODE.toUpperCase(); - -/* ── 16표정(사용자 지정) ── */ -const EXPR16 = [ - "neutral", "calm", "warm", "joy", "sad", "grief", "lonely", "anxious", - "panic", "startled", "ashamed", "angry", "skeptical", "confused", "tired", "bored", -]; - -/* ── 비짐 9종(결정문 §5.4) — 목표 open/wide/round로 가장 또렷이 드러나는 한글 음절 1개씩. - 밥/맘은 초성·종성이 모두 양순음(M)이라 M 비짐 구간이 더 길게 유지된다. ── */ -const VISEME_WORDS = ["밥", "너", "소", "두", "그", "미", "네"]; -const VISEME_TEXT = VISEME_WORDS.join(" "); -const VISEME_TARGETS = ["M", "A", "EO", "O", "U", "EU", "I", "E"]; // X는 재생 전 초기 상태에서 따로 캡처 -const VISEME_SCAN_END_MS = 2800; -const VISEME_SCAN_STEP_MS = 50; -const VISEME_CELL_TARGET_W = 300; // 최종 비짐 크롭 1장의 캔버스 단위 목표 너비(px) - -/* ── 깜빡임/유휴 시선 흔들림 회피(T4a) — neutral-vs-art가 깜빡임 중에 찍힌 사례가 - 있었다(P2). reducedMotion=true면 유휴층(breath·blink·시선 흔들림)이 꺼져 채널이 - 표정 목표값에 그대로 스냅되지만(엔진 §idle layer), 안전망으로 700ms 간격 3연사 중 - eye-left outline bbox 높이가 가장 큰 장(=눈이 가장 크게 뜬 상태)을 고른다. ── */ -const EYE_STABILITY_SHOTS = 3; -const EYE_STABILITY_INTERVAL_MS = 700; -const FACE_COMPARE_RENDER_PX = 760; // T4b: 렌더 얼굴 크롭 캡처 해상도 -const FACE_COMPARE_ART_PX = 500; // T4b: 원화 얼굴 크롭 캡처 해상도 -const FACE_COMPARE_MAX_LONG_SIDE = 1000; - -function toDataUri(filePath) { - return `data:image/png;base64,${readFileSync(filePath).toString("base64")}`; -} -function loadTs(file) { - const code = esbuild.transformSync(readFileSync(file, "utf8"), { loader: "ts", format: "cjs" }).code; - const m = new Module(file); - m.filename = file; - m.paths = Module._nodeModulePaths(path.dirname(file)); - m._compile(code, file); - return m.exports; -} -async function mockAuth(page) { - await page.route("**/api/auth/me", (route) => - route.fulfill({ - status: 200, contentType: "application/json", - body: JSON.stringify({ - user_id: "cast-sheet", email: "cast-sheet@hs.ac.kr", display_name: "CastSheet", - role: "learner", cohort_ids: [], onboarding_completed_at: Math.floor(Date.now() / 1000), - }), - }), - ); -} -async function waitReady(page) { - await page.waitForFunction(() => { - const svg = document.querySelector('.al__linocut-main svg[data-render-mode="linocut-v3"]'); - return svg && svg.getAttribute("data-load-state") === "ready"; - }, { timeout: 10000 }); -} -async function setReducedMotion(page, on) { - const el = page.locator('[data-testid="reduced-motion-toggle"]'); - if ((await el.isChecked()) !== on) await el.click(); -} -async function setExpression(page, id) { - await page.locator(`[data-testid="expression-${id}"]`).click(); -} -async function setIntensity(page, value) { - await page.locator(".al__intensity input[type=range]").evaluate((el, v) => { - const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; - setter.call(el, String(v)); - el.dispatchEvent(new Event("input", { bubbles: true })); - }, value); -} -async function resizeAndScreenshot(locator, wPx, hPx, filePath) { - const prev = await locator.evaluate((el) => ({ w: el.style.width, h: el.style.height })); - await locator.evaluate((el, size) => { el.style.width = `${size.w}px`; el.style.height = `${size.h}px`; }, { w: wPx, h: hPx }); - await locator.waitFor(); - await locator.screenshot({ path: filePath }); - await locator.evaluate((el, p) => { el.style.width = p.w; el.style.height = p.h; }, prev); -} -function labelRow(cells, cellPx, cols, labelH = 22) { - return `
${cells - .map((c) => `
${c.label}
`) - .join("")}
`; -} -async function buildMontage(browser, filePath, htmlBody) { - // fullPage 스크린샷은 실제 콘텐츠 크기로 찍힌다(뷰포트보다 작아도 줄지 않고, 커도 - // 안 잘린다 — 실측 확인됨). 뷰포트를 넉넉하게 어림잡으면 그 어림값만큼 빈 여백이 - // 그대로 찍혀 긴 변 1000px 기준을 넘길 수 있어, 작게 고정해 항상 실제 콘텐츠 - // 크기로 나오게 한다. - const page = await browser.newPage({ viewport: { width: 60, height: 60 } }); - const html = `${htmlBody}`; - await page.setContent(html); - await page.waitForTimeout(300); - await page.screenshot({ path: filePath, type: "jpeg", quality: 88, fullPage: true }); - await page.close(); -} -async function cropScale(page, srcPath, rect, scale, destPath) { - const uri = toDataUri(srcPath); - const outUri = await page.evaluate(async ({ src, rect, scale }) => { - const img = await new Promise((resolve, reject) => { - const im = new Image(); - im.onload = () => resolve(im); - im.onerror = reject; - im.src = src; - }); - const c = document.createElement("canvas"); - c.width = Math.max(1, Math.round(rect.w * scale)); - c.height = Math.max(1, Math.round(rect.h * scale)); - const ctx = c.getContext("2d"); - ctx.imageSmoothingEnabled = false; - ctx.drawImage(img, rect.x, rect.y, rect.w, rect.h, 0, 0, c.width, c.height); - return c.toDataURL("image/png"); - }, { src: uri, rect, scale }); - writeFileSync(destPath, Buffer.from(outUri.split(",")[1], "base64")); -} -async function scaleDownToMaxLongSide(browser, filePath, maxSide) { - const page = await browser.newPage({ viewport: { width: 60, height: 60 } }); - const uri = toDataUri(filePath); - const outUri = await page.evaluate(async ({ src, maxSide }) => { - const img = await new Promise((resolve, reject) => { - const im = new Image(); - im.onload = () => resolve(im); - im.onerror = reject; - im.src = src; - }); - const scale = Math.min(1, maxSide / Math.max(img.width, img.height)); - const c = document.createElement("canvas"); - c.width = Math.max(1, Math.round(img.width * scale)); - c.height = Math.max(1, Math.round(img.height * scale)); - const ctx = c.getContext("2d"); - ctx.imageSmoothingEnabled = true; - ctx.imageSmoothingQuality = "high"; - ctx.drawImage(img, 0, 0, c.width, c.height); - return c.toDataURL("image/jpeg", 0.9); - }, { src: uri, maxSide }); - writeFileSync(filePath, Buffer.from(outUri.split(",")[1], "base64")); - await page.close(); -} -/** 깜빡임(또는 유휴 시선 흔들림) 중 캡처되는 것을 피한다(T4a). 같은 locator를 - EYE_STABILITY_INTERVAL_MS 간격으로 EYE_STABILITY_SHOTS번 찍어, eye-left outline - (흰자 바탕) bbox 높이가 가장 큰 장을 destPath에 남긴다. */ -async function screenshotEyeStable(page, locator, wPx, hPx, destPath) { - const candidates = []; - for (let i = 0; i < EYE_STABILITY_SHOTS; i++) { - if (i > 0) await page.waitForTimeout(EYE_STABILITY_INTERVAL_MS); - const tmpPath = path.join(FRAMES, `${path.basename(destPath, path.extname(destPath))}-stab${i}.png`); - await resizeAndScreenshot(locator, wPx, hPx, tmpPath); - const h = await page.evaluate(() => { - const outline = document.querySelector('[data-part="eye-left"]')?.children?.[0]; - return outline ? outline.getBBox().height : 0; - }); - candidates.push({ h, tmpPath }); - } - const best = candidates.reduce((a, b) => (b.h > a.h ? b : a)); - copyFileSync(best.tmpPath, destPath); - return destPath; -} - -/** 같은 비짐이 유지된 가장 긴 구간에서 중앙 표본을 고른다(전환 중 프레임을 피한다). - 표본 자체(스크린샷 포함)를 반환한다 — 나중에 같은 t로 재생을 다시 틀어 캡처하면 - 클릭~엔진 시계 리셋 사이의 수십ms급 비결정성 때문에 스캔 때와 다른 비짐이 잡힐 - 수 있다(실측 확인됨). 그래서 스캔 패스에서 이미 찍어 둔 스크린샷을 그대로 쓴다. */ -function pickRepresentativeSample(samples, viseme) { - let bestLen = 0; - let bestMid = null; - let i = 0; - while (i < samples.length) { - if (samples[i].viseme !== viseme) { i++; continue; } - let j = i; - while (j < samples.length && samples[j].viseme === viseme) j++; - const runLen = j - i; - if (runLen > bestLen) { - bestLen = runLen; - bestMid = samples[Math.floor((i + j - 1) / 2)]; - } - i = j; - } - return bestMid; -} - -async function main() { - const rigPath = path.join(REPO, `apps/web/src/components/avatar/v3/rigs/${SLUG}Rig.ts`); - const rigExportName = `${CODE_UPPER}_LINOCUT_RIG`; - const rigModule = loadTs(rigPath); - const rig = rigModule[rigExportName]; - if (!rig) throw new Error(`${rigPath}에 ${rigExportName} export가 없다.`); - const baseFrontPath = path.join(REPO, `docs/avatar-art/${SLUG}-linocut/base/base-front.png`); - - const browser = await chromium.launch(); - const page = await browser.newPage({ viewport: { width: 1400, height: 1100 } }); - await mockAuth(page); - await page.goto(`${BASE_URL}/dev/avatar-lab?seed=1&persona=${CODE_UPPER}`); - await page.waitForSelector('[data-avatar-lab="true"]'); - await waitReady(page); - - const bustCircle = page.locator(".al__preview-cell--bust-circle"); - const face96 = page.locator(".al__preview-cell--face-96"); - const portrait = page.locator(".al__preview-cell--portrait"); - const visemeCell = page.locator('[data-testid="speech-shape-viseme"]'); - - /* ── 1,2) sheet-expr-bust.jpg / sheet-expr-face.jpg: 16표정, 강도1, 4x4 격자 ── */ - await setReducedMotion(page, true); - const CELL_PX = 215; - const exprShots = []; - for (const id of EXPR16) { - await setExpression(page, id); - await setIntensity(page, 1); - await page.waitForTimeout(500); - const bustPath = path.join(FRAMES, `expr-bust-${id}.png`); - const facePath = path.join(FRAMES, `expr-face-${id}.png`); - await resizeAndScreenshot(bustCircle, CELL_PX, CELL_PX, bustPath); - await resizeAndScreenshot(face96, CELL_PX, CELL_PX, facePath); - exprShots.push({ id, bustPath, facePath }); - console.log(`[expr] ${id}@1 캡처 완료`); - } - { - const cells = exprShots.map((s) => ({ src: s.bustPath, label: s.id })); - const rows = []; - for (let i = 0; i < cells.length; i += 4) rows.push(labelRow(cells.slice(i, i + 4), CELL_PX, 4)); - await buildMontage(browser, path.join(OUT, "sheet-expr-bust.jpg"), rows.join("")); - console.log("sheet-expr-bust.jpg 완료"); - } - { - const cells = exprShots.map((s) => ({ src: s.facePath, label: s.id })); - const rows = []; - for (let i = 0; i < cells.length; i += 4) rows.push(labelRow(cells.slice(i, i + 4), CELL_PX, 4)); - await buildMontage(browser, path.join(OUT, "sheet-expr-face.jpg"), rows.join("")); - console.log("sheet-expr-face.jpg 완료"); - } - - /* ── 3) sheet-viseme.jpg: 비짐 9종, 입 주변 확대, Lab에 고정 기능이 없어 발화 재생 중 - 프레임을 쓴다(패킷 비목표 — 엔진 값 변경 금지라 임시 harness도 만들지 않는다). ── */ - const [fcx, fcy, fcw] = rig.crops.face; - const RENDER_PX = 1203; - const faceScale = RENDER_PX / fcw; - const L = rig.landmarks; - const mxs = [L.mouthCornerLeft[0], L.mouthCornerRight[0]]; - const mys = [L.upperLipTop[1], L.lowerLipBottom[1]]; - const mw = mxs[1] - mxs[0]; - const mh = mys[1] - mys[0]; - const mouthRect = { x: mxs[0] - mw * 0.25, y: mys[0] - mh * 0.6, w: mw * 1.5, h: mh * 2.2 }; - const mouthRenderRect = { x: (mouthRect.x - fcx) * faceScale, y: (mouthRect.y - fcy) * faceScale, w: mouthRect.w * faceScale, h: mouthRect.h * faceScale }; - const cropStageScale = (VISEME_CELL_TARGET_W / mouthRect.w) / faceScale; - - async function captureMouthZoom(destPath) { - const bigPath = path.join(FRAMES, "viseme-big-tmp.png"); - await resizeAndScreenshot(face96, RENDER_PX, RENDER_PX, bigPath); - await cropScale(page, bigPath, mouthRenderRect, cropStageScale, destPath); - } - - await setReducedMotion(page, false); - await setExpression(page, "neutral"); - await setIntensity(page, 0.3); - await page.waitForTimeout(300); - - const visemeShots = []; - // X(쉼) — 아직 재생을 누르기 전 초기 휴지 상태. - { - const shotPath = path.join(FRAMES, "viseme-X.png"); - await captureMouthZoom(shotPath); - visemeShots.push({ id: "X", src: shotPath }); - console.log("[viseme] X(재생 전 초기 상태) 캡처 완료"); - } - - await page.locator('[data-testid="speech-text"]').fill(VISEME_TEXT); - // 한 패스로 (t, 비짐, 스크린샷)을 같이 모은다 — 읻고 나중에 같은 t로 재생을 다시 - // 틀어 캡처하면(2패스) 클릭~엔진 시계 리셋 사이 비결정성 때문에 스캔 때와 다른 - // 비짐이 잡히는 것을 실측으로 확인했다(capture_speech_strip*.mjs와 같은 1패스 방식). - const samples = []; - for (let t = 0; t <= VISEME_SCAN_END_MS; t += VISEME_SCAN_STEP_MS) { - await page.locator('[data-testid="play-speech"]').click(); - await page.waitForTimeout(t); - const viseme = await visemeCell.innerText().catch(() => "?"); - const shotPath = path.join(FRAMES, `viseme-scan-t${String(t).padStart(5, "0")}.png`); - await captureMouthZoom(shotPath); - samples.push({ t, viseme, shotPath }); - } - writeFileSync( - path.join(OUT, "viseme-timeline.json"), - JSON.stringify(samples.map(({ t, viseme, shotPath }) => ({ t, viseme, shotPath })), null, 1), - ); - console.log(`[viseme] 타임라인 ${samples.length}개 표본 수집(스크린샷 포함) 완료`); - - for (const target of VISEME_TARGETS) { - const best = pickRepresentativeSample(samples, target); - if (best === null) { - console.log(`[viseme] ${target} — 타임라인에서 찾지 못함(건너뜀)`); - continue; - } - visemeShots.push({ id: target, src: best.shotPath, t: best.t }); - console.log(`[viseme] ${target} t=${best.t}ms 표본 채택`); - } - - { - const cells = visemeShots.map((s) => ({ src: s.src, label: s.id })); - const rows = []; - const cellPx = VISEME_CELL_TARGET_W; - for (let i = 0; i < cells.length; i += 3) rows.push(labelRow(cells.slice(i, i + 3), cellPx, 3)); - await buildMontage(browser, path.join(OUT, "sheet-viseme.jpg"), rows.join("")); - console.log("sheet-viseme.jpg 완료"); - } - - /* ── 4) neutral-vs-art.jpg: neutral 렌더(portrait)와 base-front.png를 나란히(T4a: - reducedMotion을 다시 켜 유휴층을 끄고, 700ms 3연사로 깜빡임/시선 흔들림을 피한다 — - viseme 구간에서 reducedMotion이 꺼진 채로 남아 neutral-vs-art가 유휴 애니메이션 - 중에 찍힌 사례가 있었다, P2). ── */ - await setReducedMotion(page, true); - await setExpression(page, "neutral"); - await setIntensity(page, 1); - await page.waitForTimeout(400); - const PANEL_W = 420; - const PANEL_H = Math.round((PANEL_W * rig.canvas.h) / rig.canvas.w); - const neutralPortraitPath = path.join(FRAMES, "neutral-portrait.png"); - await screenshotEyeStable(page, portrait, PANEL_W, PANEL_H, neutralPortraitPath); - { - const html = `
-
neutral 렌더(portrait)
-
base-front.png(원화)
-
`; - await buildMontage(browser, path.join(OUT, "neutral-vs-art.jpg"), html); - console.log("neutral-vs-art.jpg 완료"); - } - - /* ── 5) face-art-vs-render.jpg: 고해상도 얼굴 대조(T4b) — bust 얼굴 크롭(rig.crops.face)을 - 렌더 760px·원화 500px로 키워 neutral·grief·joy·angry와 나란히 둔다(2줄 3칸, - 최종 긴 변 <=1000px로 다시 줄인다). ── */ - { - const fch = rig.crops.face[3]; - const artFaceCropPath = path.join(FRAMES, "face-art-crop.png"); - await cropScale(page, baseFrontPath, { x: fcx, y: fcy, w: fcw, h: fch }, FACE_COMPARE_ART_PX / fcw, artFaceCropPath); - - const FACE_COMPARE_EXPR = ["neutral", "grief", "joy", "angry"]; - const faceCompareCells = [{ src: artFaceCropPath, label: "원화(base-front)" }]; - for (const id of FACE_COMPARE_EXPR) { - await setExpression(page, id); - await setIntensity(page, 1); - await page.waitForTimeout(400); - const shotPath = path.join(FRAMES, `face-art-vs-render-${id}.png`); - await screenshotEyeStable(page, face96, FACE_COMPARE_RENDER_PX, FACE_COMPARE_RENDER_PX, shotPath); - faceCompareCells.push({ src: shotPath, label: `렌더: ${id}` }); - } - - const rows = []; - for (let i = 0; i < faceCompareCells.length; i += 3) { - rows.push(labelRow(faceCompareCells.slice(i, i + 3), FACE_COMPARE_RENDER_PX, 3)); - } - const outPath = path.join(OUT, "face-art-vs-render.jpg"); - await buildMontage(browser, outPath, rows.join("")); - await scaleDownToMaxLongSide(browser, outPath, FACE_COMPARE_MAX_LONG_SIDE); - console.log("face-art-vs-render.jpg 완료"); - } - - await browser.close(); - console.log(`\n모두 완료. 산출물: ${OUT}`); -} - -main().catch((e) => { console.error(e); process.exit(1); }); diff --git a/docs/avatar-art/linocut-pipeline/scripts/brow_centerline.py b/docs/avatar-art/linocut-pipeline/scripts/brow_centerline.py index 7b427bf..6ed2c32 100644 --- a/docs/avatar-art/linocut-pipeline/scripts/brow_centerline.py +++ b/docs/avatar-art/linocut-pipeline/scripts/brow_centerline.py @@ -40,7 +40,6 @@ from scipy import ndimage SCRIPTS_DIR = Path(__file__).resolve().parent sys.path.insert(0, str(SCRIPTS_DIR)) from persona_config import load_persona_config # noqa: E402 -from segmentation import build_padded_faceless # noqa: E402 — 새 페르소나 첫 실행에서도 segmentation 단계 전에 패딩본을 만든다 # bbox: old(mediapipe) 세 점 bbox에 이 여백을 더한 영역 안에서 잉크를 찾는다. BBOX_X_PAD = 25.0 @@ -53,8 +52,6 @@ MIN_COMPONENT_AREA = 30.0 # 닫기로 묶은 최대 성분 안에서, 이보 OUTLIER_STD_MULT = 2.0 # 잔차가 표준편차의 이 배수를 넘으면 이상치 열로 제거 EDGE_INSET_FRAC = 0.03 # inner·outer = 마스크 열 범위 양끝에서 이 비율만큼 안쪽 -HALFWIDTH_WINDOW_PX = 6.0 # 반두께를 잴 때 각 점 주변 이 폭(열, px) 안의 열을 모아 중앙값을 낸다 - PEAK_WINDOW = (0.25, 0.60) # 바깥 끝 기준 이 구간 안에 꼭짓점이 있으면 그 점을 쓴다 PEAK_FALLBACK_FRAC = 0.35 PEAK_FLAT_HEIGHT_PX = 3.0 # 이 구간 안 높이 차가 이보다 작으면 "거의 평평"으로 본다 @@ -129,23 +126,6 @@ def column_weighted_centroid(band: np.ndarray, diff_region: np.ndarray, x0: int, } -def measure_half_width(cw: dict, fit: np.poly1d, x: float, window_px: float = HALFWIDTH_WINDOW_PX) -> float: - """x 주변 window_px 안 열들의 band 세로 폭(col_yhi-col_ylo) 중앙값을, 그 지점의 - 중심선 접선 기울기로 중심선 법선 방향 반두께로 환산한다(세로로 자른 폭은 띠가 - 기울어진 만큼 실제보다 넓게 잡힌다 — cos(기울기각)을 곱해 보정).""" - xs_abs = cw["xs_local"] + cw["x0"] - in_window = (xs_abs >= x - window_px) & (xs_abs <= x + window_px) - ylo = cw["col_ylo"][in_window] - yhi = cw["col_yhi"][in_window] - valid = ~np.isnan(ylo) & ~np.isnan(yhi) - if not valid.any(): - return float("nan") - vertical_extent = float(np.median(yhi[valid] - ylo[valid])) - slope = float(fit.deriv()(x)) - theta = np.arctan(slope) - return 0.5 * vertical_extent * float(np.cos(theta)) - - def fit_centerline(xs_abs: np.ndarray, ys: np.ndarray) -> tuple[np.poly1d, int]: coef = np.polyfit(xs_abs, ys, 2) fit = np.poly1d(coef) @@ -240,10 +220,6 @@ def process_brow(name: str, front_lum: np.ndarray, diff: np.ndarray, old_pts: di n_ok += 1 frac_ok = n_ok / n_total if n_total else 0.0 - half_width_inner = measure_half_width(cw, fit, float(inner_x)) - half_width_peak = measure_half_width(cw, fit, float(peak_pt_raw[0])) - half_width_outer = measure_half_width(cw, fit, float(outer_x)) - return { "name": name, "bbox": [x0, y0, x1, y1], "otsuThresh": otsu_thresh, "colRange": [col_min, col_max], "width": width, @@ -254,9 +230,6 @@ def process_brow(name: str, front_lum: np.ndarray, diff: np.ndarray, old_pts: di "peakFail": bool(peak_fail), "checkA_fracColumnsFitWithinMask": round(frac_ok, 4), "checkA_pass": frac_ok >= CHECK_A_MIN_FRAC, - "halfWidthInner": round(half_width_inner, 3), - "halfWidthPeak": round(half_width_peak, 3), - "halfWidthOuter": round(half_width_outer, 3), "band": band, "bandX0": x0, "bandY0": y0, "fit": fit, "colMin": col_min, "colMax": col_max, } @@ -311,7 +284,7 @@ def main(persona_dir: Path) -> int: lm = manifest["landmarks"] front = np.array(Image.open(base_dir / "base-front.png").convert("RGB")).astype(np.float64) - faceless = np.array(build_padded_faceless(base_dir).convert("RGB")).astype(np.float64) + faceless = np.array(Image.open(base_dir / "base-faceless-padded.png").convert("RGB")).astype(np.float64) front_lum = front.mean(axis=2) faceless_lum = faceless.mean(axis=2) diff = np.clip(faceless_lum - front_lum, 0.0, None) @@ -338,7 +311,6 @@ def main(persona_dir: Path) -> int: print(f" new inner={result['inner']} peak={result['peak']}({result['peakMethod']}) outer={result['outer']}") print(f" 검사(a) 열 포함 비율 = {result['checkA_fracColumnsFitWithinMask']} (기준>=0.80) {'OK' if result['checkA_pass'] else '[실패]'}") print(f" peak 위치비율(바깥끝기준) = {result['peakFracFromOuterEdge']} {'[실패:양끝20% 안]' if result['peakFail'] else 'OK'}") - print(f" 반두께(inner/peak/outer) = {result['halfWidthInner']}/{result['halfWidthPeak']}/{result['halfWidthOuter']}") dropL = resultL["inner"][1] - resultL["peak"][1] dropR = resultR["inner"][1] - resultR["peak"][1] @@ -389,10 +361,6 @@ def main(persona_dir: Path) -> int: "peakFailEdgeFrac": PEAK_FAIL_EDGE_FRAC, "oldPoints": {"browLeft": browL_old, "browRight": browR_old}, "newPoints": {"browLeft": strip_for_json(resultL), "browRight": strip_for_json(resultR)}, - "halfWidthLeft": [resultL["halfWidthInner"], resultL["halfWidthPeak"], resultL["halfWidthOuter"]], - "halfWidthRight": [resultR["halfWidthInner"], resultR["halfWidthPeak"], resultR["halfWidthOuter"]], - "halfWidthNote": "원화 눈썹 잉크 띠 반두께[inner,peak,outer](px, 중심선 법선 방향). export_rig.py가 " - "persona.json browHalfWidthFromArt!=false(기본)면 RigBrow.halfWidth로 내보낸다.", "checkB_innerMinusPeakY": {"browLeft": round(dropL, 2), "browRight": round(dropR, 2)}, "checkD_symmetry": {"innerYDiff": round(inner_y_symmetry, 2), "peakYDiff": round(peak_y_symmetry, 2)}, "evidenceImage": "preview/v2/brow-centerline.jpg", diff --git a/docs/avatar-art/linocut-pipeline/scripts/brow_sprite.py b/docs/avatar-art/linocut-pipeline/scripts/brow_sprite.py deleted file mode 100644 index 43ded66..0000000 --- a/docs/avatar-art/linocut-pipeline/scripts/brow_sprite.py +++ /dev/null @@ -1,635 +0,0 @@ -"""공통 리노컷 리그 — 원화 눈썹 스프라이트 + 눈꺼풀 선 굵기 배율 생성(결정문 §8.4.1, -작업 패킷 "원화 눈썹 스프라이트와 눈꺼풀 선 굵기"). - -배경(오케스트레이터 판정): P7 시범 렌더에서 벡터 눈썹(P1의 가늘고 옅은 눈썹에 맞춘 -굵기)이 P7의 굵은 판화 눈썹을 "갈색빛 회색 막대"로 그렸다. 입술에서 받아들여진 -"중립 렌더 = 원화" 원칙을 눈썹에도 적용한다 — 원화 눈썹 잉크를 그대로 스프라이트로 -떼어 렌더러가 세로 띠로 나눠 휘게 한다. 눈은 감고 뜨기 때문에 벡터를 유지하되, -원화에서 잰 윗눈꺼풀 잉크 두께 배율(lineScale)을 선 굵기에 곱한다. - -persona.json browSpriteFromArt/eyeLineScaleFromArt(둘 다 기본 true)가 false인 -페르소나는 해당 산출을 건너뛴다(P1: 이미 고정 벡터 눈썹·굵기로 빌드·검수됐고, -lineScale의 기준값 자체이기도 하다 — 바뀌면 안 된다). - -눈썹 영역: -- manifest의 눈썹 중심선(brow_centerline.py 결과: landmarks.eyebrowLeft/Right - inner·peak·outer, browCenterline.halfWidthLeft/Right)을 따라 띠를 만든다. - 반높이는 max(halfWidth)+8, inner·outer 바깥으로 6px 연장한다. -- 띠 안에서 원화(base-front) 잉크와 연결된 성분만 남긴다(Otsu(front 휘도) AND - diff(F-front)>=25, 형태학적 닫기로 해칭 조각을 묶어 최대 성분만 취한다 — - brow_centerline.build_ink_band와 같은 기준). -- 머리카락은 뺀다. 기준은 layers/v2/hairFront.png(이미 게시되는 그 레이어)의 - 알파>128(1px 팽창, 오케스트레이터 판정값)다 — 분할 카테고리(category=1) 자체를 - 쓰면 이 화풍(두꺼운 판화 눈썹이 앞머리와 맞닿아 있다)에서 세그멘터가 눈썹 - 잉크 자체를 "머리카락"으로 광범위 오탐한다(P7 1차 실측: 분할 합집합을 그대로 - 빼면 눈썹 잉크의 54~82%가 사라짐). hairFront는 렌더 z-순서에서 눈썹 스프라이트 - "위"에 그려지므로, 그 알파 아래 픽셀을 스프라이트에서 빼도 중립 렌더는 원화와 - 같고 이중 가닥(겹쳐 보임)도 생기지 않는다 — "앞머리 가닥이 눈썹과 같이 - 움직이면 안 된다"는 의도를 분할 카테고리보다 정확히 구현한다(오케스트레이터 - 실측: 눈썹 띠 안 잉크 중 hairFront 알파>128 아래에 있는 비율은 좌 47% - (1392/2953), 우 19%(442/2329) — 분할 카테고리 기준 손실 54~82%보다 훨씬 적다). - -알파: a = clamp((L_skin-L)/(L_skin-L_ink), 0, 1) × (region 경계 1.5px 페더, RAMP_PX). -L_skin은 띠 안 faceless 휘도 중앙값, L_ink는 띠 안에서 "잉크로 분류된 픽셀"(머리카락 -제외 후 region, band_ink_mask 결과) 휘도의 중앙값이다 — 그보다 어두운 픽셀은 a=1이 -된다(2차 보정, 작업 패킷 "눈썹 스프라이트·눈꺼풀 굵기 2차 보정": 띠 전체 휘도의 -5백분위를 쓰면 극단적으로 어두운 꼬리값이 L_ink가 돼, 잉크 픽셀 대부분이 a<1로 -옅게 그려졌다). 페더도 4px→1.5px로 줄였다 — 눈썹 해칭 획 자체가 가늘어(경계거리 -평균 2.28px) 4px 페더로는 L_ink를 낮춰도 대부분 페더 중간값에 걸려 옅었다(RAMP_PX -주석 참고, 블러·형태학 닫기는 쓰지 않아 결은 그대로다). - -색: RGB = clip((front - (1-a)*skin)/a), skin은 띠 안 faceless 평균색. a<0.05면 -잉크색(L_ink population의 front RGB 중앙값)으로 대체한다(0 나눗셈 폭주 방지). - -눈꺼풀 굵기: 눈 폭 가운데 60% 열마다 upperLidTop.y-12 ~ 홍채 중심 y 사이의 첫 -연속 어두운 구간(lum<80) 길이를 재 중앙값을 낸다. P1 기준값(아래 상수)으로 나눈 -비를 [1.0, 2.5]로 clamp한 값이 lineScale이다. - -전제: landmarks.py·brow_centerline.py·layers_v2.py·face_detail.py가 먼저 돌아서 -manifest.landmarks(+browCenterline)와 layers/v2/{body,head,hairFront,face-detail}.png를 -만들어 두어야 한다. export_rig.py보다 앞, face_detail.py보다 뒤에 둔다. - -실행: /python.exe brow_sprite.py -""" -from __future__ import annotations - -import json -import math -import sys -from pathlib import Path - -import cv2 -import numpy as np -from PIL import Image, ImageDraw -from scipy import ndimage - -SCRIPTS_DIR = Path(__file__).resolve().parent -sys.path.insert(0, str(SCRIPTS_DIR)) -from persona_config import load_persona_config # noqa: E402 -from segmentation import build_padded_faceless, to_u8, composite_over, CREAM_BG # noqa: E402 -from lip_texture import crop_to_alpha_bbox # noqa: E402 — 알파 bbox로 잘라 RGBA PNG를 내는 공용 유틸(입술 결과 동일 패턴) - -# --- 눈썹 영역(띠) 상수 ------------------------------------------------------- -BAND_EXTRA_HALF_HEIGHT_PX = 8.0 # 반높이 = max(halfWidth) + 이 값 -BAND_END_EXTEND_PX = 6.0 # inner·outer 바깥 연장 -BAND_BBOX_PAD_PX = 10 # 닫기 연산이 띠 경계에서 잘리지 않게 주는 여유 - -# brow_centerline.py의 잉크 판정 기준과 같다(같은 원화 눈썹 잉크를 다시 찾는 -# 것이므로 같은 임계값을 쓴다) — DIFF_THRESH·CLOSING_KERNEL·MIN_COMPONENT_AREA. -DIFF_THRESH = 25.0 -CLOSING_KERNEL = (7, 11) -MIN_COMPONENT_AREA = 30.0 - -HAIR_DILATE_ITER = 1 # 머리카락(hairFront 알파) 마스크 팽창(오케스트레이터 판정값) -HAIR_ALPHA_THRESH = 128.0 # layers/v2/hairFront.png 알파가 이보다 크면 "머리카락이 덮는다"로 본다(오케스트레이터 판정값) - -RAMP_PX = 1.5 # region(잉크, 머리카락 제외) 경계 알파 페더(px) -# 2차 보정: 눈썹 해칭은 획 자체가 가늘다(P7 실측: region 안 경계거리변환(distance_transform_edt) -# 평균 2.28px, dist>=4px인 픽셀 14~17%뿐) — 기존 4px 페더로는 L_ink를 아무리 낮춰도(=어둡게 -# 잡아도) 거의 모든 획이 페더 중간값에 걸려 옅게 그려졌다(T1 목표 미달의 실제 원인). 1.5px로 -# 줄이면 블러·형태학 닫기 없이(결 보존) 경계 anti-alias만 남기고 목표(알파>0 영역 평균절대차 -# <=10)를 만족한다(P7 실측: 좌 7.83 우 4.99, RAMP_PX=4일 때는 좌 42.75 우 41.77이었다). -MIN_ALPHA_FOR_UNMIX = 0.05 # a<0.05면 피부색 역산(unmix) 대신 잉크색을 쓴다(0 나눗셈 폭주 방지) - -# --- 눈꺼풀 선 굵기(lineScale) 상수 ------------------------------------------ -UPPER_LID_WINDOW_ABOVE_PX = 12.0 # 측정 창 위쪽 = upperLidTop.y - 이 값 -UPPER_LID_DARK_LUM_THRESH = 80.0 -UPPER_LID_COLUMN_MIDDLE_FRAC = 0.6 # 눈 폭 가운데 60%(양옆 20%씩 제외) -LINE_SCALE_MIN = 1.0 -LINE_SCALE_MAX = 2.5 - -# P1 기준값(윗눈꺼풀 잉크 두께, px) — measure_upper_lid_thickness를 P1 -# manifest.landmarks.eyeLeft/eyeRight + base/base-front.png에 적용한 값이다 -# (둘 다 median=2.000px, 표본 열 57/58개 — 실측 근거, 2026-10-01). P1은 -# "다시 빌드하지 않는다"는 결정문 지시로 고정돼 있어 매번 다시 재는 대신 -# 상수로 고정한다(패킷 지시: "P1 값은... 상수로 고정하되 근거를 주석으로 단다"). -P1_UPPER_LID_THICKNESS_PX = 2.0 - -# --- 홍채 실측(2차 보정 T3) 상수 --------------------------------------------- -# mediapipe iris 랜드마크(468~477)는 실제 사람 눈 비례로 학습돼, 판화 잉크로 -# 그려진 홍채(원화 실측)보다 반지름을 크게 잡을 수 있다(P7 실측: 가로 반지름 -# 기준 리그 대비 약 11~15% 작다, 2026-10-01). lineScale·browSprite와 무관하게 -# 항상 측정해 manifest에 남긴다(browHalfWidthFromArt와 같은 "측정값은 항상 기록" -# 원칙) — export_rig.py가 persona.json irisFromArt(기본 true)일 때만 이 값을 쓴다. -IRIS_DARK_LUM_THRESH = 100.0 # 홍채 잉크 실측 휘도(28~40대) vs 흰자(150 이상) 중간, 여유를 둔 임계값 -IRIS_SCAN_BAND_PX = 4 # 중심 둘레 ±이 값(px)의 행/열을 스캔한다 - - -# --- 눈썹 띠(band) 기하 ------------------------------------------------------- - - -def _quad_point(p0: tuple[float, float], c: tuple[float, float], p2: tuple[float, float], t: float) -> tuple[float, float]: - mt = 1.0 - t - return (mt * mt * p0[0] + 2 * mt * t * c[0] + t * t * p2[0], mt * mt * p0[1] + 2 * mt * t * c[1] + t * t * p2[1]) - - -def _quad_control(p0: tuple[float, float], mid: tuple[float, float], p2: tuple[float, float]) -> tuple[float, float]: - return (2 * mid[0] - 0.5 * (p0[0] + p2[0]), 2 * mid[1] - 0.5 * (p0[1] + p2[1])) - - -def _normalize(dx: float, dy: float) -> tuple[float, float]: - length = math.hypot(dx, dy) or 1.0 - return dx / length, dy / length - - -def _seg_normal(a: tuple[float, float], b: tuple[float, float]) -> tuple[float, float]: - dx, dy = b[0] - a[0], b[1] - a[1] - length = math.hypot(dx, dy) or 1.0 - return -dy / length, dx / length - - -def brow_band_polygon( - inner: tuple[float, float], peak: tuple[float, float], outer: tuple[float, float], - half_height: float, extend_px: float, samples: int = 40, -) -> list[tuple[float, float]]: - """눈썹 중심선(inner→peak→outer 2차 베지어, linocutGeometry.ts buildBrow의 - 곡선과 같은 방식)을 따라 반높이 half_height인 리본 폴리곤을 만든다(곡선 표본 + - 법선 오프셋, linocutGeometry.ts polylineRibbon과 같은 기법). 양끝은 접선 - 방향으로 extend_px만큼 늘린다(inner·outer 바깥 연장).""" - control = _quad_control(inner, peak, outer) - pts = [_quad_point(inner, control, outer, i / (samples - 1)) for i in range(samples)] - t0 = _normalize(pts[0][0] - pts[1][0], pts[0][1] - pts[1][1]) - t1 = _normalize(pts[-1][0] - pts[-2][0], pts[-1][1] - pts[-2][1]) - ext_inner = (pts[0][0] + t0[0] * extend_px, pts[0][1] + t0[1] * extend_px) - ext_outer = (pts[-1][0] + t1[0] * extend_px, pts[-1][1] + t1[1] * extend_px) - pts = [ext_inner] + pts + [ext_outer] - - n = len(pts) - normals = [] - for i in range(n): - a = _seg_normal(pts[i - 1], pts[i]) if i > 0 else _seg_normal(pts[i], pts[i + 1]) - b = _seg_normal(pts[i], pts[i + 1]) if i < n - 1 else _seg_normal(pts[i - 1], pts[i]) - normals.append(((a[0] + b[0]) / 2.0, (a[1] + b[1]) / 2.0)) - - top = [(p[0] + nx * half_height, p[1] + ny * half_height) for p, (nx, ny) in zip(pts, normals)] - bot = [(p[0] - nx * half_height, p[1] - ny * half_height) for p, (nx, ny) in zip(pts, normals)] - return top + list(reversed(bot)) - - -def polygon_bool_mask(points: list[tuple[float, float]], w: int, h: int) -> np.ndarray: - img = Image.new("L", (w, h), 0) - ImageDraw.Draw(img).polygon(points, fill=255) - return np.array(img) > 127 - - -def mask_bbox(mask: np.ndarray, pad: int, w: int, h: int) -> tuple[int, int, int, int]: - ys, xs = np.where(mask) - if len(xs) == 0: - raise SystemExit("[중단] 눈썹 띠 마스크가 비어 있다.") - x0 = max(0, int(xs.min()) - pad) - y0 = max(0, int(ys.min()) - pad) - x1 = min(w, int(xs.max()) + 1 + pad) - y1 = min(h, int(ys.max()) + 1 + pad) - return x0, y0, x1, y1 - - -# --- 띠 안 잉크 연결성분 ------------------------------------------------------ - - -def band_ink_mask(front_lum: np.ndarray, diff: np.ndarray, band_mask: np.ndarray, bbox: tuple[int, int, int, int]) -> tuple[np.ndarray, float]: - """brow_centerline.build_ink_band와 같은 기준(Otsu(front 휘도) AND diff>=25, - 형태학적 닫기로 해칭 조각을 묶어 최대 성분만 취함)을, 축 정렬 bbox 대신 눈썹 - 띠 폴리곤(band_mask) 안으로 제한해 적용한다.""" - x0, y0, x1, y1 = bbox - region_lum = front_lum[y0:y1, x0:x1] - u8 = np.clip(np.round(region_lum), 0, 255).astype(np.uint8) - otsu_thresh, _ = cv2.threshold(u8, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) - ink_mask = region_lum <= otsu_thresh - diff_mask = diff[y0:y1, x0:x1] >= DIFF_THRESH - band_region = band_mask[y0:y1, x0:x1] - combined = ink_mask & diff_mask & band_region - - closed = ndimage.binary_closing(combined, structure=np.ones(CLOSING_KERNEL)) - labeled, n = ndimage.label(closed) - if n == 0: - raise SystemExit("[중단] 눈썹 띠 안에 잉크 마스크가 전혀 없다.") - sizes = ndimage.sum(closed, labeled, range(1, n + 1)) - top_label = int(np.argmax(sizes)) + 1 - band_ink_local = combined & (labeled == top_label) - - lbl2, n2 = ndimage.label(band_ink_local) - if n2 > 0: - sizes2 = ndimage.sum(band_ink_local, lbl2, range(1, n2 + 1)) - keep = [i + 1 for i, s in enumerate(sizes2) if s >= MIN_COMPONENT_AREA] - band_ink_local = band_ink_local & np.isin(lbl2, keep) - - full = np.zeros(front_lum.shape, dtype=bool) - full[y0:y1, x0:x1] = band_ink_local - return full, float(otsu_thresh) - - -# --- 눈썹 스프라이트 한 쪽 ---------------------------------------------------- - - -def build_one_brow_sprite( - inner: tuple[float, float], peak: tuple[float, float], outer: tuple[float, float], - half_width: tuple[float, float, float], - front_rgb: np.ndarray, faceless_rgb: np.ndarray, front_lum: np.ndarray, faceless_lum: np.ndarray, - diff: np.ndarray, hair_mask: np.ndarray, canvas_w: int, canvas_h: int, side_label: str, -) -> dict: - half_height = max(half_width) + BAND_EXTRA_HALF_HEIGHT_PX - band_points = brow_band_polygon(inner, peak, outer, half_height, BAND_END_EXTEND_PX) - band_mask = polygon_bool_mask(band_points, canvas_w, canvas_h) - bbox = mask_bbox(band_mask, BAND_BBOX_PAD_PX, canvas_w, canvas_h) - - ink_mask_raw, otsu_thresh = band_ink_mask(front_lum, diff, band_mask, bbox) - hair_overlap_px = int((ink_mask_raw & hair_mask).sum()) - region = ink_mask_raw & ~hair_mask - if not region.any(): - raise SystemExit(f"[중단] {side_label} 눈썹 영역이 비었다(머리카락 제거 후 잉크 픽셀 0).") - - band_lum_faceless = faceless_lum[band_mask] - l_skin = float(np.median(band_lum_faceless)) - # 2차 보정: 띠 전체 휘도의 5백분위(극단적으로 어두운 꼬리값) 대신, 실제로 잉크로 - # 분류된 픽셀(region, 머리카락 제거 후)의 휘도 중앙값을 쓴다 — 그보다 어두운 - # 잉크 픽셀은 a=1이 된다(목표: 눈썹 알파>0 영역 평균절대차 <=10). - l_ink = float(np.median(front_lum[region])) - denom = (l_skin - l_ink) if abs(l_skin - l_ink) > 1e-6 else 1e-6 - - dist_in = ndimage.distance_transform_edt(region) - feather = np.clip(dist_in / RAMP_PX, 0.0, 1.0) - density = np.clip((l_skin - front_lum) / denom, 0.0, 1.0) - alpha_frac = np.where(region, density * feather, 0.0) - alpha = np.rint(alpha_frac * 255.0).astype(np.uint8) - - skin_rgb = faceless_rgb[band_mask].mean(axis=0) - ink_sample_mask = band_mask & (front_lum <= l_ink) - ink_fallback_rgb = ( - np.median(front_rgb[ink_sample_mask], axis=0) if ink_sample_mask.any() else np.array([0.0, 0.0, 0.0]) - ) - - a3 = alpha_frac[..., None] - unmixed = (front_rgb - (1.0 - a3) * skin_rgb.reshape(1, 1, 3)) / np.maximum(a3, 1e-6) - unmixed = np.clip(unmixed, 0.0, 255.0) - rgb = np.where(a3 >= MIN_ALPHA_FOR_UNMIX, unmixed, ink_fallback_rgb.reshape(1, 1, 3)) - - print( - f" [{side_label}] 띠 반높이={half_height:.2f}px otsu={otsu_thresh:.1f} " - f"L_skin={l_skin:.2f} L_ink={l_ink:.2f} 영역픽셀={int(region.sum())} " - f"앞머리겹침픽셀={hair_overlap_px}" - ) - - return { - "alpha": alpha, "rgb": rgb, "region": region, "bandMask": band_mask, "bbox": bbox, - "halfHeight": half_height, "otsuThresh": otsu_thresh, "lSkin": l_skin, "lInk": l_ink, - "hairOverlapPx": hair_overlap_px, "regionPixelCount": int(region.sum()), - } - - -# --- 중립 합성 검사(기존 레이어 위에 스프라이트를 얹어 원화와 비교) ------------ - - -def _load_rgba(path: Path) -> tuple[np.ndarray, np.ndarray]: - arr = np.array(Image.open(path).convert("RGBA")).astype(np.float64) - return arr[..., :3], arr[..., 3] - - -def composite_with_sprites(layers_v2_dir: Path, canvas_w: int, canvas_h: int, sprites: dict[str, dict]) -> np.ndarray: - """body→head→face-detail(이미 눈썹 자리를 faceless로 메워 둔 상태) 위에 - 눈썹 스프라이트를 얹고, 그 위에 hairFront를 덮는다 — LinocutAvatar.tsx의 - 그리는 순서(브로우는 face 그룹 안, hairFront보다 먼저)와 같다.""" - canvas = np.zeros((canvas_h, canvas_w, 4), dtype=np.float64) - canvas[..., 0] = CREAM_BG[0] - canvas[..., 1] = CREAM_BG[1] - canvas[..., 2] = CREAM_BG[2] - canvas[..., 3] = 255.0 - for stem in ("body", "head", "face-detail"): - p = layers_v2_dir / f"{stem}.png" - if p.exists(): - rgb, a = _load_rgba(p) - canvas = composite_over(canvas, to_u8(rgb), to_u8(a)) - for data in sprites.values(): - canvas = composite_over(canvas, to_u8(data["rgb"]), data["alpha"]) - hf_path = layers_v2_dir / "hairFront.png" - if hf_path.exists(): - hf_rgb, hf_a = _load_rgba(hf_path) - canvas = composite_over(canvas, to_u8(hf_rgb), to_u8(hf_a)) - return canvas[..., :3] - - -def _save_evidence(front_rgb: np.ndarray, sprites: dict[str, dict], composite_rgb: np.ndarray, preview_v2_dir: Path) -> None: - """옆 쪽마다 원화 | 중립 합성(스프라이트 포함) | 스프라이트(체커 배경) 3열, - 3배 확대(lip_texture.py _save_evidence와 같은 방식).""" - rows = [] - for side_label, data in sprites.items(): - x0, y0, x1, y1 = data["bbox"] - pad = 10 - bx0, by0 = max(0, x0 - pad), max(0, y0 - pad) - bx1, by1 = x1 + pad, y1 + pad - - def zoom(arr: np.ndarray) -> Image.Image: - crop = arr[by0:by1, bx0:bx1] - im = Image.fromarray(np.clip(crop, 0, 255).astype(np.uint8)) - return im.resize((im.width * 3, im.height * 3), Image.LANCZOS) - - panel_orig = zoom(front_rgb).convert("RGB") - panel_composite = zoom(composite_rgb).convert("RGB") - - alpha = data["alpha"] - rgb = data["rgb"] - crop_rgb = rgb[by0:by1, bx0:bx1] - crop_a = (alpha[by0:by1, bx0:bx1].astype(np.float64) / 255.0)[..., None] - yy, xx = np.indices(crop_a.shape[:2]) - checker = ((yy // 6 + xx // 6) % 2) * 60 + 180 - checker3 = np.stack([checker] * 3, axis=-1).astype(np.float64) - sprite_view = crop_rgb * crop_a + checker3 * (1 - crop_a) - panel_sprite = Image.fromarray(np.clip(sprite_view, 0, 255).astype(np.uint8)).resize( - (panel_orig.width, panel_orig.height), Image.NEAREST - ) - - gap = 12 - row = Image.new("RGB", (panel_orig.width * 3 + gap * 2, panel_orig.height), (255, 255, 255)) - row.paste(panel_orig, (0, 0)) - row.paste(panel_composite, (panel_orig.width + gap, 0)) - row.paste(panel_sprite, (panel_orig.width * 2 + gap * 2, 0)) - rows.append(row) - - gap_y = 12 - max_w = max(r.width for r in rows) - total_h = sum(r.height for r in rows) + gap_y * (len(rows) - 1) - combined = Image.new("RGB", (max_w, total_h), (255, 255, 255)) - y = 0 - for row in rows: - combined.paste(row, (0, y)) - y += row.height + gap_y - scale = min(1.0, 1400 / max(combined.size)) - if scale < 1.0: - combined = combined.resize((round(combined.width * scale), round(combined.height * scale)), Image.LANCZOS) - preview_v2_dir.mkdir(parents=True, exist_ok=True) - out_path = preview_v2_dir / "brow-sprite.jpg" - combined.save(out_path, "JPEG", quality=92) - print(f"저장: {out_path}") - - -# --- 눈꺼풀 선 굵기(lineScale) ------------------------------------------------- - - -def measure_upper_lid_thickness(eye: dict, front_lum: np.ndarray) -> tuple[float, int]: - """눈 폭(innerCorner~outerCorner) 가운데 60% 열마다, upperLidTop.y-12에서 - 홍채 중심 y 사이의 첫 연속 어두운 구간(lum<80) 길이를 재 중앙값을 낸다.""" - inner_x = eye["innerCorner"][0] - outer_x = eye["outerCorner"][0] - x_lo, x_hi = sorted([inner_x, outer_x]) - width = x_hi - x_lo - margin = (1.0 - UPPER_LID_COLUMN_MIDDLE_FRAC) / 2.0 * width - col_lo = x_lo + margin - col_hi = x_hi - margin - y_top = eye["upperLidTop"][1] - UPPER_LID_WINDOW_ABOVE_PX - y_bottom = eye["iris"]["center"][1] - - lengths: list[int] = [] - for cx in range(int(round(col_lo)), int(round(col_hi)) + 1): - col = front_lum[int(round(y_top)):int(round(y_bottom)), cx] - dark = col < UPPER_LID_DARK_LUM_THRESH - idx = np.where(dark)[0] - if len(idx) == 0: - continue - start = int(idx[0]) - length = 1 - i = start - while i + 1 < len(dark) and dark[i + 1]: - length += 1 - i += 1 - lengths.append(length) - if not lengths: - return float("nan"), 0 - return float(np.median(lengths)), len(lengths) - - -# --- 홍채 실측(T3) ------------------------------------------------------------- - - -def _contiguous_dark_run(values: np.ndarray, center_idx: int, thresh: float) -> tuple[int, int] | None: - """1차원 휘도 배열에서 center_idx를 포함하는 연속 어두운(값= n or values[center_idx] >= thresh: - return None - s = center_idx - while s - 1 >= 0 and values[s - 1] < thresh: - s -= 1 - e = center_idx - while e + 1 < n and values[e + 1] < thresh: - e += 1 - return s, e - - -def measure_iris(eye: dict, front_lum: np.ndarray, side_label: str) -> dict: - """홍채(잉크 원판)의 실측 중심·반지름. 세로축은 윗눈꺼풀 잉크와 홍채가 같은 - 톤으로 이어져(판화 특성상 경계가 없다) 못 가르므로, 가로축(중심 y 부근 행마다 - 중심 x를 포함하는 연속 어두운 구간)만으로 반지름·중심 x를 재고, 중심 x 부근 - 열마다 중심 y를 포함하는 연속 어두운 구간의 아래쪽 끝(중앙값)에서 그 가로 - 반지름을 빼 중심 y를 역산한다(원이라고 가정).""" - cx, cy = eye["iris"]["center"] - cxi, cyi = int(round(cx)), int(round(cy)) - - lefts: list[int] = [] - rights: list[int] = [] - for dy in range(-IRIS_SCAN_BAND_PX, IRIS_SCAN_BAND_PX + 1): - row = front_lum[cyi + dy, :] - seg = _contiguous_dark_run(row, cxi, IRIS_DARK_LUM_THRESH) - if seg is not None: - lefts.append(seg[0]) - rights.append(seg[1]) - if not lefts: - raise SystemExit(f"[중단] {side_label} 홍채 가로 실측 — 중심 부근 행에서 어두운 구간을 찾지 못했다.") - left = float(np.median(lefts)) - right = float(np.median(rights)) - radius = (right - left) / 2.0 - measured_cx = (right + left) / 2.0 - - bottoms: list[int] = [] - for dx in range(-IRIS_SCAN_BAND_PX, IRIS_SCAN_BAND_PX + 1): - col = front_lum[:, cxi + dx] - seg = _contiguous_dark_run(col, cyi, IRIS_DARK_LUM_THRESH) - if seg is not None: - bottoms.append(seg[1]) - if not bottoms: - raise SystemExit(f"[중단] {side_label} 홍채 세로 실측 — 중심 부근 열에서 어두운 구간을 찾지 못했다.") - bottom = float(np.median(bottoms)) - measured_cy = bottom - radius - - rig_radius = float(eye["iris"]["radius"]) - print( - f" [{side_label}] 홍채 실측: center=({measured_cx:.1f},{measured_cy:.1f}) radius={radius:.2f} " - f"(리그 center=({cx:.1f},{cy:.1f}) radius={rig_radius:.2f}, 차이 radius={radius - rig_radius:+.2f}, " - f"표본 가로 {len(lefts)}행/세로 {len(bottoms)}열)" - ) - return { - "measuredCenter": [round(measured_cx, 2), round(measured_cy, 2)], - "measuredRadius": round(radius, 2), - "rigCenter": [round(cx, 2), round(cy, 2)], - "rigRadius": round(rig_radius, 2), - "nRowsHorizontal": len(lefts), - "nColsVertical": len(bottoms), - } - - -def main(persona_dir: Path) -> int: - cfg = load_persona_config(persona_dir) - base_dir = cfg.base_dir - layers_v2_dir = cfg.layers_v2_dir - preview_v2_dir = cfg.preview_v2_dir - manifest_path = cfg.manifest_path - - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - lm = manifest["landmarks"] - canvas_w = manifest["canvas"]["w"] - canvas_h = manifest["canvas"]["h"] - - front_rgb = np.array(Image.open(base_dir / "base-front.png").convert("RGB")).astype(np.float64) - faceless_rgb = np.array(build_padded_faceless(base_dir)).astype(np.float64) - front_lum = front_rgb.mean(axis=2) - faceless_lum = faceless_rgb.mean(axis=2) - diff = np.clip(faceless_lum - front_lum, 0.0, None) - - browsprite_report: dict | None = None - if cfg.brow_sprite_from_art: - print("=== 눈썹 스프라이트 ===") - bc = manifest.get("browCenterline") - if bc is None: - raise SystemExit("[중단] manifest.browCenterline이 없다 — brow_centerline.py를 먼저 실행하라.") - half_width_by_side = {"left": bc["halfWidthLeft"], "right": bc["halfWidthRight"]} - eyebrow_by_side = {"left": lm["eyebrowLeft"], "right": lm["eyebrowRight"]} - - hairfront_path = layers_v2_dir / "hairFront.png" - if not hairfront_path.exists(): - raise SystemExit(f"[중단] {hairfront_path}가 없다 — layers_v2.py를 먼저 실행하라.") - hairfront_alpha = np.array(Image.open(hairfront_path).convert("RGBA"))[..., 3].astype(np.float64) - if hairfront_alpha.shape != (canvas_h, canvas_w): - raise SystemExit( - f"[중단] layers/v2/hairFront.png 크기({hairfront_alpha.shape[1]}x{hairfront_alpha.shape[0]})가 " - f"캔버스({canvas_w}x{canvas_h})와 다르다." - ) - hair_mask = ndimage.binary_dilation(hairfront_alpha > HAIR_ALPHA_THRESH, iterations=HAIR_DILATE_ITER) - print(f"머리카락 마스크(hairFront 알파>{HAIR_ALPHA_THRESH}, {HAIR_DILATE_ITER}px 팽창) 픽셀 = {int(hair_mask.sum())}") - - sprites: dict[str, dict] = {} - for side in ("left", "right"): - eb = eyebrow_by_side[side] - sprites[side] = build_one_brow_sprite( - tuple(eb["inner"]), tuple(eb["peak"]), tuple(eb["outer"]), - tuple(half_width_by_side[side]), - front_rgb, faceless_rgb, front_lum, faceless_lum, diff, hair_mask, - canvas_w, canvas_h, side, - ) - - composite_rgb = composite_with_sprites(layers_v2_dir, canvas_w, canvas_h, sprites) - diffs: dict[str, float] = {} - for side, data in sprites.items(): - d = np.abs(composite_rgb - front_rgb).mean(axis=2) - # 2차 보정: 띠 전체가 아니라 스프라이트 알파>0 영역(region)만 잰다 — 알파=0인 - # 띠 가장자리 피부 픽셀(원래도 거의 차이가 없다)이 섞여 들어가 잉크 알파가 - # 옅은 문제를 가리던 것을 바로잡는다(목표 <=10). - diffs[side] = float(d[data["region"]].mean()) - print(f" 검사 [{side}] 중립 합성(스프라이트 포함) vs 원화, 눈썹 알파>0 영역 평균절대차 = {diffs[side]:.3f}") - - _save_evidence(front_rgb, sprites, composite_rgb, preview_v2_dir) - - layers_v2_dir.mkdir(parents=True, exist_ok=True) - side_reports: dict[str, dict] = {} - canvas_origin: dict[str, list[float]] = {} - for side, data in sprites.items(): - png_arr, bbox = crop_to_alpha_bbox(data["alpha"], to_u8(data["rgb"])) - out_path = layers_v2_dir / f"brow-{side}.png" - Image.fromarray(png_arr, "RGBA").save(out_path) - file_size = out_path.stat().st_size - canvas_origin[side] = [float(bbox[0]), float(bbox[1])] - print(f"저장: {out_path} bbox(캔버스 절대좌표)={bbox} size={png_arr.shape[1]}x{png_arr.shape[0]} ({file_size} bytes)") - side_reports[side] = { - "bboxCanvas": list(bbox), - "halfHeightPx": round(data["halfHeight"], 3), - "otsuThresh": data["otsuThresh"], - "lSkin": round(data["lSkin"], 3), - "lInk": round(data["lInk"], 3), - "regionPixelCount": data["regionPixelCount"], - "hairOverlapPixelCount": data["hairOverlapPx"], - "neutralCompositeMeanAbsDiffAlphaRegion": round(diffs[side], 3), - "pngFileSizeBytes": file_size, - } - - browsprite_report = { - "enabled": True, - "bandExtraHalfHeightPx": BAND_EXTRA_HALF_HEIGHT_PX, - "bandEndExtendPx": BAND_END_EXTEND_PX, - "diffThresh": DIFF_THRESH, - "closingKernel": list(CLOSING_KERNEL), - "minComponentAreaPx": MIN_COMPONENT_AREA, - "hairSource": "layers/v2/hairFront.png alpha(게시되는 레이어 그 자체, 분할 카테고리 아님)", - "hairAlphaThresh": HAIR_ALPHA_THRESH, - "hairDilateIterations": HAIR_DILATE_ITER, - "rampPx": RAMP_PX, - "inkLumMethod": "median(ink-classified pixels in band, region after hair removal)", - "minAlphaForUnmix": MIN_ALPHA_FOR_UNMIX, - "hairMaskPixelCount": int(hair_mask.sum()), - "left": side_reports["left"], - "right": side_reports["right"], - "canvasOrigin": canvas_origin, - "evidenceImage": "preview/v2/brow-sprite.jpg", - } - else: - print("browSpriteFromArt=false — 눈썹 스프라이트를 건너뛴다.") - browsprite_report = {"enabled": False} - - eye_line_scale_report: dict | None = None - if cfg.eye_line_scale_from_art: - print("=== 눈꺼풀 선 굵기(lineScale) ===") - side_reports = {} - for side, key in (("left", "eyeLeft"), ("right", "eyeRight")): - eye = lm[key] - measured_px, n_cols = measure_upper_lid_thickness(eye, front_lum) - if n_cols == 0: - raise SystemExit(f"[중단] {key} 윗눈꺼풀 두께 측정 열이 하나도 없다(모든 열에서 lum<80 구간을 못 찾음).") - ratio = measured_px / P1_UPPER_LID_THICKNESS_PX - line_scale = float(np.clip(ratio, LINE_SCALE_MIN, LINE_SCALE_MAX)) - print( - f" [{key}] 측정={measured_px:.3f}px(열 {n_cols}개) / P1기준={P1_UPPER_LID_THICKNESS_PX}px " - f"= 비율 {ratio:.3f} -> lineScale={line_scale:.3f}" - + (" [clamp 적용됨]" if not (LINE_SCALE_MIN <= ratio <= LINE_SCALE_MAX) else "") - ) - side_reports[side] = { - "measuredPx": round(measured_px, 3), "nColumns": n_cols, - "ratio": round(ratio, 3), "lineScale": round(line_scale, 3), - } - eye_line_scale_report = { - "enabled": True, - "method": "눈 폭 가운데 60% 열마다 upperLidTop.y-12 ~ 홍채중심y 사이 첫 연속 어두운(lum<80) 구간 길이의 중앙값", - "windowAbovePx": UPPER_LID_WINDOW_ABOVE_PX, - "darkLumThresh": UPPER_LID_DARK_LUM_THRESH, - "columnMiddleFrac": UPPER_LID_COLUMN_MIDDLE_FRAC, - "p1BaselinePx": P1_UPPER_LID_THICKNESS_PX, - "lineScaleClampRange": [LINE_SCALE_MIN, LINE_SCALE_MAX], - "left": side_reports["left"], - "right": side_reports["right"], - } - else: - print("eyeLineScaleFromArt=false — 눈꺼풀 lineScale을 건너뛴다.") - eye_line_scale_report = {"enabled": False} - - # 홍채 실측(T3)은 browSpriteFromArt/eyeLineScaleFromArt와 무관하게 항상 재 - # manifest에 남긴다(browHalfWidthFromArt와 같은 "측정값은 설정과 무관하게 항상 - # 기록" 원칙). export_rig.py가 persona.json irisFromArt(기본 true)일 때만 쓴다. - print("=== 홍채 실측(T3) ===") - iris_report = { - "method": ( - "가로: 중심 y 부근 상하 각 4행에서 중심 x를 포함하는 연속 어두운(lum<100) 구간의 " - "중앙값으로 반지름·중심x. 세로: 중심 x 부근 좌우 각 4열에서 중심 y를 포함하는 연속 " - "어두운 구간의 아래쪽 끝 중앙값에서 가로 반지름을 빼 중심y(세로축은 윗눈꺼풀 잉크와 " - "홍채가 같은 톤으로 이어져 위쪽 경계를 가를 수 없다)." - ), - "darkLumThresh": IRIS_DARK_LUM_THRESH, - "scanBandPx": IRIS_SCAN_BAND_PX, - "left": measure_iris(lm["eyeLeft"], front_lum, "eyeLeft"), - "right": measure_iris(lm["eyeRight"], front_lum, "eyeRight"), - } - - manifest["browSprite"] = browsprite_report - manifest["eyeLineScale"] = eye_line_scale_report - manifest["iris"] = iris_report - manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8") - print(f"manifest.json 갱신(browSprite, eyeLineScale, iris): {manifest_path}") - return 0 - - -if __name__ == "__main__": - sys.exit(main(Path(sys.argv[1]))) diff --git a/docs/avatar-art/linocut-pipeline/scripts/export_rig.py b/docs/avatar-art/linocut-pipeline/scripts/export_rig.py index 5f9647e..be168cd 100644 --- a/docs/avatar-art/linocut-pipeline/scripts/export_rig.py +++ b/docs/avatar-art/linocut-pipeline/scripts/export_rig.py @@ -59,15 +59,6 @@ LIP_SPECS = [ ] LIP_SIZE_BUDGET_BYTES = 80 * 1024 # 그늘 조각 분리로 3장 -# --- 눈썹 스프라이트(결정문 §8.4.1, 작업 패킷 "원화 눈썹 스프라이트와 눈꺼풀 선 굵기") -- - -BROW_SPECS = [ - # (rig key, 파일 stem, public href stem) - ("left", "brow-left", "brow-left"), - ("right", "brow-right", "brow-right"), -] -BROW_SIZE_BUDGET_BYTES = 60 * 1024 - # --- 턱 조각(결정문 §8.4 하관 띠 변형) --------------------------------------- JAW_SPECS = [ @@ -316,123 +307,6 @@ def publish_lip_texture(cfg: PersonaConfig, manifest: dict, layers_v2_dir: Path) return {"upper": lip_field["upper"], "lower": lip_field["lower"], "shadow": lip_field["shadow"]}, publish_report -def publish_brow_sprite(cfg: PersonaConfig, manifest: dict, layers_v2_dir: Path) -> tuple[dict | None, dict | None]: - """layers/v2/brow-{left,right}.png(brow_sprite.py 산출)를 알파 bbox로 잘라 - WebP로 게시한다(결정문 §8.4.1). 소스가 없으면 (None, None) — - browSpriteFromArt=false인 리그(P1)는 RigBrow.sprite 없이 벡터 눈썹을 그대로 - 쓴다(렌더러 대체 경로). - - PNG는 이미 알파 bbox로 꽉 차게 잘려 있어(brow_sprite.py crop_to_alpha_bbox) - crop_to_bbox는 사실상 no-op이고, 캔버스 위치는 manifest["browSprite"] - ["canvasOrigin"]에서 읽는다(publish_lip_texture와 같은 패턴). 인코딩은 입술 - 스프라이트와 같은 무손실(exact) 우선 — 알파=0 영역도 원화 RGB를 그대로 - 담고 있으므로(brow_sprite.py의 피부색 역산·잉크색 대체) exact=True로 - 버려지지 않게 한다.""" - canvas_origin = manifest.get("browSprite", {}).get("canvasOrigin") - if canvas_origin is None: - return None, None - - sources: dict[str, tuple[Path, str]] = {} - for key, stem, href_stem in BROW_SPECS: - p = layers_v2_dir / f"{stem}.png" - if not p.exists(): - return None, None - sources[key] = (p, href_stem) - - cropped: dict[str, tuple[Image.Image, tuple[int, int, int, int], str]] = {} - for key, (path, href_stem) in sources.items(): - im = Image.open(path) - cropped_im, local_bbox = crop_to_bbox(im) - lx0, ly0 = canvas_origin[key] - abs_bbox = (lx0 + local_bbox[0], ly0 + local_bbox[1], local_bbox[2], local_bbox[3]) - cropped[key] = (cropped_im, abs_bbox, href_stem) - - BROW_DIFF_TARGET = 2.0 - - def encode_all(quality: int) -> dict[str, bytes]: - out: dict[str, bytes] = {} - for key, (im, _bbox, href_stem) in cropped.items(): - out[href_stem] = encode_webp(im.convert("RGBA"), quality) - return out - - def encode_all_lossless() -> dict[str, bytes]: - out: dict[str, bytes] = {} - for key, (im, _bbox, href_stem) in cropped.items(): - buf = io.BytesIO() - im.convert("RGBA").save(buf, "WEBP", lossless=True, quality=100, method=6, exact=True) - out[href_stem] = buf.getvalue() - return out - - def webp_vs_png_diff(encoded: dict[str, bytes]) -> dict[str, float]: - diffs: dict[str, float] = {} - for key, (im, _bbox, href_stem) in cropped.items(): - src_arr = np.array(im.convert("RGBA")).astype(np.float64) - dec_arr = np.array(Image.open(io.BytesIO(encoded[href_stem])).convert("RGBA")).astype(np.float64) - diffs[key] = round(float(np.abs(src_arr - dec_arr).mean()), 3) - return diffs - - lossless_encoded = encode_all_lossless() - lossless_total = sum(len(b) for b in lossless_encoded.values()) - if lossless_total <= BROW_SIZE_BUDGET_BYTES: - chosen, chosen_quality = lossless_encoded, "lossless" - else: - candidates: list[tuple[int, dict[str, bytes], int, dict[str, float]]] = [] - chosen, chosen_quality = None, None - for q in (100, 99, 98, 97, 96, 95): - encoded = encode_all(q) - total = sum(len(b) for b in encoded.values()) - diffs = webp_vs_png_diff(encoded) - candidates.append((q, encoded, total, diffs)) - if total <= BROW_SIZE_BUDGET_BYTES and max(diffs.values()) <= BROW_DIFF_TARGET: - chosen, chosen_quality = encoded, q - break - if chosen is None: - within_budget = [c for c in candidates if c[2] <= BROW_SIZE_BUDGET_BYTES] - pool = within_budget if within_budget else candidates - best = min(pool, key=lambda c: max(c[3].values())) - chosen_quality, chosen, _total, _diffs = best[0], best[1], best[2], best[3] - print( - f" [경고] quality 95~100 중 예산({BROW_SIZE_BUDGET_BYTES}B) AND 평균차<={BROW_DIFF_TARGET} " - f"동시 만족 없음 — quality={chosen_quality}(평균차 최소) 채택" - ) - assert chosen is not None and chosen_quality is not None - - public_dir = cfg.public_dir - public_dir.mkdir(parents=True, exist_ok=True) - file_sizes: dict[str, int] = {} - for href_stem, data in chosen.items(): - out_path = public_dir / f"{href_stem}.webp" - out_path.write_bytes(data) - file_sizes[href_stem] = len(data) - total_bytes = sum(file_sizes.values()) - print(f"눈썹 스프라이트 인코딩: quality={chosen_quality} (무손실 시도 시 합계 {lossless_total} bytes)") - for href_stem, size in file_sizes.items(): - print(f" {href_stem}.webp = {size} bytes") - print( - f"눈썹 스프라이트 합계 = {total_bytes} bytes (예산 {BROW_SIZE_BUDGET_BYTES} bytes) " - f"{'OK' if total_bytes <= BROW_SIZE_BUDGET_BYTES else '[초과]'}" - ) - - webp_vs_png = webp_vs_png_diff(chosen) - for href_stem in file_sizes: - key = next(k for k, (_im, _bbox, hs) in cropped.items() if hs == href_stem) - print(f" 검사 [{href_stem}] WebP 대 PNG 평균차={webp_vs_png[key]}") - - brow_field = {} - for key, (_im, bbox, href_stem) in cropped.items(): - x0, y0, w, h = bbox - brow_field[key] = { - "href": f"{cfg.public_href_prefix}/{href_stem}.webp", - "x": round(x0, 1), "y": round(y0, 1), "w": round(w, 1), "h": round(h, 1), - } - publish_report = { - "quality": chosen_quality, "fileSizes": file_sizes, "totalBytes": total_bytes, - "budgetBytes": BROW_SIZE_BUDGET_BYTES, - "webpVsPngMeanAbsDiff": webp_vs_png, - } - return brow_field, publish_report - - # --- 턱 조각 게시(결정문 §8.4 하관 띠 변형) ---------------------------------- @@ -890,49 +764,29 @@ def compute_mouth_center_from_manifest(manifest: dict) -> tuple[float, float]: # --- landmarks 매핑(manifest 값을 이름만 바꿔 그대로 사용) ----------------- -def build_landmarks( - cfg: PersonaConfig, manifest: dict, mouth_center: tuple[float, float], brow_sprite_field: dict | None -) -> dict: +def build_landmarks(manifest: dict, mouth_center: tuple[float, float]) -> dict: lm = manifest["landmarks"] - half_widths = manifest.get("browCenterline", {}) if cfg.brow_half_width_from_art else {} - eye_line_scale = manifest.get("eyeLineScale", {}) if cfg.eye_line_scale_from_art else {} - iris_measured = manifest.get("iris", {}) if cfg.iris_from_art else {} def pt(p) -> list[float]: return [round(p[0], 1), round(p[1], 1)] - def eye(e, line_scale_key: str) -> dict: - iris_m = iris_measured.get(line_scale_key) - if iris_m is not None: - iris_field = {"center": pt(iris_m["measuredCenter"]), "radius": round(float(iris_m["measuredRadius"]), 1)} - else: - iris_field = {"center": pt(e["iris"]["center"]), "radius": round(e["iris"]["radius"], 1)} - d = { + def eye(e) -> dict: + return { "inner": pt(e["innerCorner"]), "outer": pt(e["outerCorner"]), "upperLidTop": pt(e["upperLidTop"]), "lowerLidBottom": pt(e["lowerLidBottom"]), - "iris": iris_field, + "iris": {"center": pt(e["iris"]["center"]), "radius": round(e["iris"]["radius"], 1)}, } - line_scale = eye_line_scale.get(line_scale_key, {}).get("lineScale") - if line_scale is not None: - d["lineScale"] = round(float(line_scale), 3) - return d - def brow(b, half_width_key: str, sprite_key: str) -> dict: - d = {"inner": pt(b["inner"]), "peak": pt(b["peak"]), "outer": pt(b["outer"])} - hw = half_widths.get(half_width_key) - if hw is not None: - d["halfWidth"] = [round(float(v), 2) for v in hw] - if brow_sprite_field is not None and brow_sprite_field.get(sprite_key) is not None: - d["sprite"] = brow_sprite_field[sprite_key] - return d + def brow(b) -> dict: + return {"inner": pt(b["inner"]), "peak": pt(b["peak"]), "outer": pt(b["outer"])} return { - "eyeLeft": eye(lm["eyeLeft"], "left"), - "eyeRight": eye(lm["eyeRight"], "right"), - "browLeft": brow(lm["eyebrowLeft"], "halfWidthLeft", "left"), - "browRight": brow(lm["eyebrowRight"], "halfWidthRight", "right"), + "eyeLeft": eye(lm["eyeLeft"]), + "eyeRight": eye(lm["eyeRight"]), + "browLeft": brow(lm["eyebrowLeft"]), + "browRight": brow(lm["eyebrowRight"]), "noseTip": pt(lm["noseTip"]), "mouthCornerLeft": pt(lm["mouthCornerLeft"]), "mouthCornerRight": pt(lm["mouthCornerRight"]), @@ -1083,22 +937,11 @@ def render_rig_ts(cfg: PersonaConfig, rig: dict) -> str: lines.append(f' upperLidTop: {ts_point(e["upperLidTop"])},') lines.append(f' lowerLidBottom: {ts_point(e["lowerLidBottom"])},') lines.append(f' iris: {{ center: {ts_point(e["iris"]["center"])}, radius: {ts_num(e["iris"]["radius"])} }},') - if e.get("lineScale") is not None: - lines.append(f' lineScale: {ts_num(e["lineScale"])},') lines.append(" },") def emit_brow(name, b): - hw = b.get("halfWidth") - hw_str = f", halfWidth: [{ts_num(hw[0])}, {ts_num(hw[1])}, {ts_num(hw[2])}]" if hw else "" - sprite = b.get("sprite") - sprite_str = "" - if sprite: - sprite_str = ( - f', sprite: {{ href: "{sprite["href"]}", x: {ts_num(sprite["x"])}, y: {ts_num(sprite["y"])}, ' - f'w: {ts_num(sprite["w"])}, h: {ts_num(sprite["h"])} }}' - ) lines.append( - f' {name}: {{ inner: {ts_point(b["inner"])}, peak: {ts_point(b["peak"])}, outer: {ts_point(b["outer"])}{hw_str}{sprite_str} }},' + f' {name}: {{ inner: {ts_point(b["inner"])}, peak: {ts_point(b["peak"])}, outer: {ts_point(b["outer"])} }},' ) emit_eye("eyeLeft", lmk["eyeLeft"]) @@ -1158,12 +1001,7 @@ def main(persona_dir: Path) -> int: for k, v in palette.items(): print(f" {k} = {v}") - print("=== 눈썹 스프라이트 게시 ===") - brow_sprite_field, brow_sprite_publish_report = publish_brow_sprite(cfg, manifest, layers_v2_dir) - if brow_sprite_field is None: - print(" brow-left.png/brow-right.png 없음(생략) — 벡터 눈썹을 그대로 쓰는 리그") - - landmarks = build_landmarks(cfg, manifest, mouth_center, brow_sprite_field) + landmarks = build_landmarks(manifest, mouth_center) face_crop = compute_face_crop(face_oval) print(f"crops.face = {[round(v,1) for v in face_crop]}") @@ -1245,7 +1083,6 @@ def main(persona_dir: Path) -> int: "lipTexturePublish": lip_publish_report, "jawPublish": jaw_publish_report, "motifPublish": motif_publish_report, - "browSpritePublish": brow_sprite_publish_report, "rig": rig, } report_path = cfg.preview_v2_dir / "export-rig-report.json" @@ -1260,14 +1097,8 @@ def main(persona_dir: Path) -> int: if jaw_publish_report is not None and "vsPublishedLayerAbsDiff" in jaw_publish_report: manifest.setdefault("jaw", {})["check1VsPublishedLayerAbsDiff"] = jaw_publish_report["vsPublishedLayerAbsDiff"] manifest["motifPublish"] = motif_publish_report - manifest["browSpritePublish"] = brow_sprite_publish_report - if brow_sprite_publish_report is not None and "webpVsPngMeanAbsDiff" in brow_sprite_publish_report: - manifest.setdefault("browSprite", {})["checkWebpVsPngMeanAbsDiff"] = brow_sprite_publish_report["webpVsPngMeanAbsDiff"] manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8") - print( - f"manifest.json 갱신(lipTexturePublish, jawPublish, motifPublish, browSpritePublish): " - f"{manifest_path.relative_to(cfg.repo_root)}" - ) + print(f"manifest.json 갱신(lipTexturePublish, jawPublish, motifPublish): {manifest_path.relative_to(cfg.repo_root)}") return 0 diff --git a/docs/avatar-art/linocut-pipeline/scripts/face_detail.py b/docs/avatar-art/linocut-pipeline/scripts/face_detail.py index 8c8d17c..cc83fbe 100644 --- a/docs/avatar-art/linocut-pipeline/scripts/face_detail.py +++ b/docs/avatar-art/linocut-pipeline/scripts/face_detail.py @@ -39,7 +39,6 @@ SCRIPTS_DIR = Path(__file__).resolve().parent sys.path.insert(0, str(SCRIPTS_DIR)) from segmentation import build_padded_faceless, to_u8, alpha_bbox, run_segmentation, composite_over, CREAM_BG # noqa: E402 from persona_config import load_persona_config # noqa: E402 -from export_rig import detect_face_landmarks, compute_face_oval # noqa: E402 — region="face"용 얼굴 오벌(캐스트 확장) # --- 영역(region) 상수: faceDetail이 그려질 전체 범위(눈두덩·다크서클·입가 음영 포함) -- REGION_FEATHER_PX = 12.0 @@ -100,9 +99,6 @@ CHECKE_THRESH = 1.5 GENERIC_PATCH_SIZE = 48 GENERIC_PATCH_MARGIN = 40 -# --- matchFacelessColor(faceDetail.matchFacelessColor) 상수 ----------------- -MATCH_RING_LUM_THRESH = 110.0 # 고리 표본에서 이보다 어두운(잉크) front 픽셀은 뺀다 - def ellipse_alpha(cx: float, cy: float, rx: float, ry: float, w: int, h: int, feather: float) -> np.ndarray: yy, xx = np.mgrid[0:h, 0:w].astype(np.float64) @@ -112,26 +108,6 @@ def ellipse_alpha(cx: float, cy: float, rx: float, ry: float, w: int, h: int, fe return np.clip(alpha, 0, 255) -def face_oval_region_alpha( - base_front_path: Path, inset_px: float, w: int, h: int, feather: float = REGION_FEATHER_PX -) -> tuple[np.ndarray, np.ndarray, int, int]: - """base-front.png 랜드마크로 얼굴 오벌(export_rig.compute_face_oval, 다른 단계와 같은 - 36점 루프·1.04 배율)을 구해 inset_px만큼 안쪽으로 줄인 뒤 feather px로 페더링한다. - 원화 윤곽 잉크선은 오벌 경계 근방(jaw_pieces.py OVAL_OUTSET_PX=18px 안팎)에 있으므로, - inset_px를 충분히 크게 잡아야 이 잉크선이 반환 영역(알파>0) 밖에 남는다 — 호출자가 - persona.json faceDetail.faceInsetPx로 값을 정하고 근거를 보고해야 한다. - 반환: (알파, 원본 오벌 불리언 마스크, 오벌 픽셀 수, inset 후 픽셀 수).""" - points = detect_face_landmarks(base_front_path) - oval_pts = compute_face_oval(points) - oval_img = Image.new("L", (w, h), 0) - ImageDraw.Draw(oval_img).polygon(oval_pts, fill=255) - oval_mask = np.array(oval_img) > 127 - dist_in = distance_transform_edt(oval_mask) - inset_mask = oval_mask & (dist_in >= inset_px) - alpha = np.clip(gaussian_filter(inset_mask.astype(np.float64) * 255.0, sigma=feather / 2.0), 0, 255) - return alpha, oval_mask, int(oval_mask.sum()), int(inset_mask.sum()) - - def eyebrow_mask(inner, peak, outer, w: int, h: int, width: int) -> np.ndarray: img = Image.new("L", (w, h), 0) d = ImageDraw.Draw(img) @@ -477,18 +453,7 @@ def main(persona_dir: Path) -> int: mcx, mcy = mole["center"] r = float(mole["radius"]) a_mole = np.maximum(a_mole, ellipse_alpha(mcx, mcy, r, r, w, h, REGION_FEATHER_PX)) - - if cfg.face_detail_region == "face": - a_face, oval_mask, oval_px, inset_px_count = face_oval_region_alpha( - base_dir / "base-front.png", cfg.face_inset_px, w, h - ) - region_alpha = np.maximum(a_face, a_mole) - print( - f"faceDetail.region=face: faceInsetPx={cfg.face_inset_px} " - f"오벌픽셀={oval_px} inset후픽셀={inset_px_count}" - ) - else: - region_alpha = np.maximum(np.maximum(a_eyeL, a_eyeR), np.maximum(a_mouth, a_mole)) + region_alpha = np.maximum(np.maximum(a_eyeL, a_eyeR), np.maximum(a_mouth, a_mole)) # ------------------------------------------------------------------ # 제외 영역(구멍): 이목구비 잉크선 전체 + 지정 방향 여유 + 최종 팽창 @@ -556,20 +521,7 @@ def main(persona_dir: Path) -> int: # 머리카락 마스크(분할, 2px 팽창) — faceDetail 알파를 0으로 만든다 # ------------------------------------------------------------------ category_mask = run_segmentation(base_dir / "base-faceless-padded.png") - hair_mask_raw = category_mask == 1 - if cfg.face_detail_region == "face": - # region="face"는 얼굴 피부 전체에 원화(front) 픽셀을 노출하므로, front와 - # faceless의 앞머리 가닥 위치가 달라(codex 편집본 시차) faceless 분할 하나만 - # 쓰면 front 쪽 잔머리가 머리카락 마스크 밖으로 나와 유령 가닥이 생긴다 — - # 두 그림의 분할 합집합으로 덮는다. - front_category_mask = run_segmentation(base_dir / "base-front.png") - hair_mask_front_raw = front_category_mask == 1 - hair_mask_raw = hair_mask_raw | hair_mask_front_raw - print( - f"머리카락 분할(합집합): faceless={int((category_mask == 1).sum())}px " - f"front={int(hair_mask_front_raw.sum())}px union={int(hair_mask_raw.sum())}px" - ) - hair_mask = binary_dilation(hair_mask_raw, iterations=HAIR_DILATE_PX) + hair_mask = binary_dilation(category_mask == 1, iterations=HAIR_DILATE_PX) print(f"머리카락 마스크(2px 팽창) 픽셀: {int(hair_mask.sum())}") # ------------------------------------------------------------------ @@ -583,31 +535,6 @@ def main(persona_dir: Path) -> int: for r in stamp_report: print(f" 복제 도장: comp={r['componentId']} px={r['pixels']} offset={r['offset']} method={r['method']}") - # ------------------------------------------------------------------ - # matchFacelessColor: 제외 영역(눈·눈썹·입) 안의 F 색을 주변 원화 피부에 맞춘다. - # 영역마다 RING_PX 바깥 고리(어두운 잉크 제외)에서 front·F 평균 RGB 차를 구해 - # 그 영역 전체의 F를 그 차이만큼 평행이동한다 — 보간·블러·패치 합성은 하지 않는다. - # ------------------------------------------------------------------ - match_color_report: dict[str, list[float]] = {} - if cfg.match_faceless_color: - print("=== matchFacelessColor: 영역별 ΔRGB(front - F, 안쪽 고리 표본) ===") - deltas: dict[str, np.ndarray] = {} - for name, region_mask in named_excl.items(): - ring = binary_dilation(region_mask, iterations=RING_PX) & ~exclusion_mask & ~hair_mask - sample_mask = ring & (lum_front >= MATCH_RING_LUM_THRESH) - if not sample_mask.any(): - deltas[name] = np.zeros(3) - print(f" {name}: 고리 표본 없음 — 보정 생략") - continue - front_mean = front[sample_mask].mean(axis=0) - f_mean = f_clean[sample_mask].mean(axis=0) - deltas[name] = front_mean - f_mean - d = deltas[name] - print(f" {name}: ΔRGB=({d[0]:+.2f},{d[1]:+.2f},{d[2]:+.2f}) 표본={int(sample_mask.sum())}px") - for name, region_mask in named_excl.items(): - f_clean[region_mask] = np.clip(f_clean[region_mask] + deltas[name], 0, 255) - match_color_report = {name: [round(float(c), 2) for c in d] for name, d in deltas.items()} - # ------------------------------------------------------------------ # 합성: 제외 영역 밖은 front 그대로, 안은 F(f_clean) 그대로. 비율 보간 # 없음. 경계는 10px 페더로 섞는다 — 단 눈 아래쪽 좁은 띠(eye_lower_bands)는 @@ -787,10 +714,6 @@ def main(persona_dir: Path) -> int: manifest.setdefault("layersV2", {}) manifest["layersV2"]["faceDetail"] = { "browLandmarksSource": brow_source, - "region": cfg.face_detail_region, - "faceInsetPx": cfg.face_inset_px if cfg.face_detail_region == "face" else None, - "matchFacelessColor": cfg.match_faceless_color, - "matchFacelessColorDeltaRgb": match_color_report, "regions": { "eyeLeft": [round(bx0, 1), round(by0, 1), round(bx1, 1), round(by1, 1)], "eyeRight": [round(rx0, 1), round(ry0, 1), round(rx1, 1), round(ry1, 1)], diff --git a/docs/avatar-art/linocut-pipeline/scripts/normalize_base.py b/docs/avatar-art/linocut-pipeline/scripts/normalize_base.py deleted file mode 100644 index 8d03598..0000000 --- a/docs/avatar-art/linocut-pipeline/scripts/normalize_base.py +++ /dev/null @@ -1,249 +0,0 @@ -"""공통 리노컷 리그 — 캐스트 확장 0단계: 원화 3장을 캔버스로 정규화. - -입력 front/faceless/body는 모두 같은 좌표계(후보 원화) 위에 있다고 전제한다 -(T1 register_edit.py가 faceless·body를 원화 좌표계로 이미 정렬해 둔 출력을 -그대로 받는다). 셋 모두에 **같은 아핀 변환 하나**(등방 배율 + 평행이동, -회전 없음)를 적용해 캔버스 1005x1566(결정문 §8.4)으로 옮긴다. - -기준은 P1 base-front.png의 랜드마크다(눈 중심 두 점의 중점, 두 눈 사이 거리, -턱끝) — 머리 크기·위치가 P1과 같은 비율이 되게 새 원화의 배율·이동을 定한다. -mediapipe FaceLandmarker 인덱스: 눈A=(33,133) 중점, 눈B=(263,362) 중점, 턱=152 -(landmarks.py의 EYE_A_IDX/EYE_B_IDX/CHIN_IDX와 같다). - -배율: s_head = (P1 눈-턱 거리/원화 눈-턱 거리 + P1 눈 사이 거리/원화 눈 사이 -거리) / 2. s = max(s_head, 캔버스폭/원화폭) — 원화가 머리 비율상 더 작게 -배율돼도 캔버스 폭은 항상 덮어야 한다. - -이동: 두 눈 중점을 P1 두 눈 중점에 맞춘다. 가로 오프셋은 배율된 원화가 캔버스 -폭을 꼭 덮도록 [캔버스폭 − 원화폭·s, 0] 구간으로 자른다(좌우 빈틈 방지). -세로 오프셋은 자르지 않는다 — 위/아래로 남는 빈 곳은 각 이미지 종류에 맞게 -채운다(front·faceless: 원본 가장자리 띠 반사 패딩, body: #00ff00). - -사용: normalize_base.py --front <원화> --faceless <정렬된 faceless> --body <정렬된 body> -""" -from __future__ import annotations - -import argparse -import hashlib -import json -import subprocess -import sys -import tempfile -from pathlib import Path - -import cv2 -import numpy as np -from PIL import Image - -SCRIPTS_DIR = Path(__file__).resolve().parent - -CANVAS_W = 1005 -CANVAS_H = 1566 -TOP_PAPER_BAND_PX = 8 # 위쪽 채움 색을 뽑을 원화 맨 위 행 수(머리카락은 14행부터 나온다) -P1_REFERENCE_RELATIVE = Path("docs/avatar-art/p1-linocut/base/base-front.png") - -# mediapipe FaceLandmarker 인덱스(landmarks.py EYE_A_IDX/EYE_B_IDX/CHIN_IDX와 같다). -EYE_A_IDX = (33, 133) -EYE_B_IDX = (263, 362) -CHIN_IDX = 152 - - -def detect_face_landmarks(image_path: Path) -> list[tuple[float, float]]: - """FaceLandmarker를 별도 프로세스로 실행한다(세그폴트 회피, segmentation.py와 같은 이유).""" - with tempfile.TemporaryDirectory() as td: - out_json = Path(td) / "landmarks.json" - proc = subprocess.run( - [sys.executable, "-u", str(SCRIPTS_DIR / "_run_face_landmarks.py"), str(image_path), str(out_json)], - capture_output=True, text=True, - ) - print(proc.stdout.strip()) - if proc.returncode != 0 or not out_json.exists(): - raise SystemExit(f"[중단] {image_path.name}: FaceLandmarker 서브프로세스 실패.\n{proc.stderr}") - data = json.loads(out_json.read_text(encoding="utf-8")) - if not data.get("ok"): - raise SystemExit(f"[중단] {image_path.name}: FaceLandmarker가 얼굴을 찾지 못했다.") - return [(p[0], p[1]) for p in data["points"]] - - -def midpoint(a: tuple[float, float], b: tuple[float, float]) -> tuple[float, float]: - return ((a[0] + b[0]) / 2.0, (a[1] + b[1]) / 2.0) - - -def dist(a: tuple[float, float], b: tuple[float, float]) -> float: - return float(np.hypot(a[0] - b[0], a[1] - b[1])) - - -def measure_head(points: list[tuple[float, float]]) -> dict: - eye_a = midpoint(points[EYE_A_IDX[0]], points[EYE_A_IDX[1]]) - eye_b = midpoint(points[EYE_B_IDX[0]], points[EYE_B_IDX[1]]) - eyes_center = midpoint(eye_a, eye_b) - chin = points[CHIN_IDX] - return { - "eyeA": eye_a, "eyeB": eye_b, "eyesCenter": eyes_center, "chin": chin, - "eyeDistance": dist(eye_a, eye_b), - "eyeChinDistance": dist(eyes_center, chin), - } - - -def measure_report(head: dict) -> dict: - return { - "eyesCenter": [round(head["eyesCenter"][0], 2), round(head["eyesCenter"][1], 2)], - "chin": [round(head["chin"][0], 2), round(head["chin"][1], 2)], - "eyeDistance": round(head["eyeDistance"], 3), - "eyeChinDistance": round(head["eyeChinDistance"], 3), - } - - -def clamp(v: float, lo: float, hi: float) -> float: - return max(lo, min(hi, v)) - - -def sha256_of(path: Path) -> str: - return hashlib.sha256(path.read_bytes()).hexdigest() - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description="원화 3장(front/faceless/body)을 캔버스 1005x1566으로 정규화한다.") - parser.add_argument("persona_dir", help="페르소나 폴더(예: docs/avatar-art/p2-linocut)") - parser.add_argument("--front", required=True, help="후보 원화(정면 기본형)") - parser.add_argument("--faceless", required=True, help="원화 좌표계로 정렬된 얼굴 없는 기본형(T1 출력)") - parser.add_argument("--body", required=True, help="원화 좌표계로 정렬된 body 편집본(T1 출력)") - return parser.parse_args(argv) - - -def main(argv: list[str]) -> int: - args = parse_args(argv) - persona_dir = Path(args.persona_dir).resolve() - front_path = Path(args.front).resolve() - faceless_path = Path(args.faceless).resolve() - body_path = Path(args.body).resolve() - - # persona_dir 위치와 무관하게 이 스크립트 파일 기준으로 repo root를 고정한다 - # (persona.json이 아직 없을 수 있는 단계라 persona_config.py의 persona_dir 기준 - # repo_root 계산에 의존하지 않는다): scripts/ -> linocut-pipeline -> avatar-art -> docs -> repo root. - repo_root = SCRIPTS_DIR.parents[3] - ref_path = repo_root / P1_REFERENCE_RELATIVE - if not ref_path.exists(): - raise SystemExit(f"[중단] P1 기준 이미지가 없다: {ref_path}") - - print(f"=== 기준(P1) 랜드마크: {ref_path} ===") - ref_head = measure_head(detect_face_landmarks(ref_path)) - print( - f" eyesCenter={ref_head['eyesCenter']} eyeDistance={ref_head['eyeDistance']:.2f} " - f"eyeChinDistance={ref_head['eyeChinDistance']:.2f}" - ) - - print(f"=== 입력 원화 랜드마크: {front_path} ===") - front_head = measure_head(detect_face_landmarks(front_path)) - print( - f" eyesCenter={front_head['eyesCenter']} eyeDistance={front_head['eyeDistance']:.2f} " - f"eyeChinDistance={front_head['eyeChinDistance']:.2f}" - ) - - front_img = Image.open(front_path).convert("RGB") - front_w, front_h = front_img.size - - s_head = ( - ref_head["eyeChinDistance"] / front_head["eyeChinDistance"] - + ref_head["eyeDistance"] / front_head["eyeDistance"] - ) / 2.0 - s_width = CANVAS_W / front_w - s = max(s_head, s_width) - print(f"s_head={s_head:.5f} s_width(캔버스폭/원화폭)={s_width:.5f} -> s={s:.5f}") - - offset_x_natural = ref_head["eyesCenter"][0] - front_head["eyesCenter"][0] * s - offset_x_lo, offset_x_hi = CANVAS_W - front_w * s, 0.0 - offset_x = clamp(offset_x_natural, offset_x_lo, offset_x_hi) - offset_y = ref_head["eyesCenter"][1] - front_head["eyesCenter"][1] * s - print(f"offset_x natural={offset_x_natural:.3f} clamp=[{offset_x_lo:.3f}, {offset_x_hi:.3f}] -> {offset_x:.3f}") - print(f"offset_y={offset_y:.3f} (클램프 없음)") - - covered_x0 = clamp(offset_x, 0.0, CANVAS_W) - covered_x1 = clamp(offset_x + front_w * s, 0.0, CANVAS_W) - covered_y0 = clamp(offset_y, 0.0, CANVAS_H) - covered_y1 = clamp(offset_y + front_h * s, 0.0, CANVAS_H) - print( - f"덮인 범위(반사/채움 아닌 원화 픽셀): x=[{covered_x0:.1f}, {covered_x1:.1f}] " - f"y=[{covered_y0:.1f}, {covered_y1:.1f}] (캔버스 {CANVAS_W}x{CANVAS_H})" - ) - - matrix = np.array([[s, 0.0, offset_x], [0.0, s, offset_y]], dtype=np.float64) - - def warp(src_path: Path, border_mode: int, border_value: tuple[int, int, int] | None) -> np.ndarray: - arr = np.array(Image.open(src_path).convert("RGB")) - kwargs: dict = {"flags": cv2.INTER_LANCZOS4, "borderMode": border_mode} - if border_value is not None: - kwargs["borderValue"] = border_value - return cv2.warpAffine(arr, matrix, (CANVAS_W, CANVAS_H), **kwargs) - - # front·faceless: 빈 곳은 원본 가장자리 띠 반사(BORDER_REFLECT_101 — 가장자리 픽셀을 - # 복제하지 않고 그 안쪽부터 비추어, 복제판(T3 build_padded_faceless류)보다 이음매가 - # 덜 드러난다; 위쪽은 아래에서 종이색으로 덮는다). body: 빈 곳은 크로마키 배경(#00ff00)으로 채운다. - front_out = warp(front_path, cv2.BORDER_REFLECT_101, None) - faceless_out = warp(faceless_path, cv2.BORDER_REFLECT_101, None) - body_out = warp(body_path, cv2.BORDER_CONSTANT, (0, 255, 0)) - - # 위쪽 빈 곳만은 반사하지 않고 원화 위 가장자리의 종이색으로 채운다. 머리카락이 원화 위 - # 끝 가까이(14~42px)까지 오는 후보는 반사하면 머리 위에 뒤집힌 머리카락이 생긴다 - # (2026-10-01 P3·P5 판정). 배경은 분할에서 레이어 밖으로 빠지므로 평평한 색이어도 화면에 - # 드러나지 않는다. 아래쪽은 옷이 이어지는 편이 나아 반사를 유지한다. - top_rows = int(np.ceil(covered_y0)) - if top_rows > 0: - for out, src_path in ((front_out, front_path), (faceless_out, faceless_path)): - band = np.array(Image.open(src_path).convert("RGB"))[:TOP_PAPER_BAND_PX].reshape(-1, 3) - paper = band[band.mean(axis=1) > 150] - out[:top_rows] = np.median(paper if len(paper) else band, axis=0).astype(np.uint8) - - base_dir = persona_dir / "base" - raw_dir = persona_dir / "raw" - base_dir.mkdir(parents=True, exist_ok=True) - raw_dir.mkdir(parents=True, exist_ok=True) - - front_out_path = base_dir / "base-front.png" - faceless_out_path = base_dir / "base-faceless.png" - body_out_path = raw_dir / "body.png" - Image.fromarray(front_out, "RGB").save(front_out_path) - Image.fromarray(faceless_out, "RGB").save(faceless_out_path) - Image.fromarray(body_out, "RGB").save(body_out_path) - print(f"저장: {front_out_path}") - print(f"저장: {faceless_out_path}") - print(f"저장: {body_out_path}") - - report = { - "canvas": {"w": CANVAS_W, "h": CANVAS_H}, - "reference": {"image": str(ref_path.relative_to(repo_root)), **measure_report(ref_head)}, - "input": { - "front": str(front_path), - "faceless": str(faceless_path), - "body": str(body_path), - "frontSize": [front_w, front_h], - **measure_report(front_head), - }, - "transform": { - "sHead": round(s_head, 6), - "sWidth": round(s_width, 6), - "s": round(s, 6), - "rotationDeg": 0.0, - "offsetXNatural": round(offset_x_natural, 3), - "offsetXClampRange": [round(offset_x_lo, 3), round(offset_x_hi, 3)], - "offsetX": round(offset_x, 3), - "offsetY": round(offset_y, 3), - }, - "coveredRange": { - "x": [round(covered_x0, 1), round(covered_x1, 1)], - "y": [round(covered_y0, 1), round(covered_y1, 1)], - }, - "inputSha256": { - "front": sha256_of(front_path), - "faceless": sha256_of(faceless_path), - "body": sha256_of(body_path), - }, - } - report_path = base_dir / "normalize.json" - report_path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") - print(f"저장: {report_path}") - return 0 - - -if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) diff --git a/docs/avatar-art/linocut-pipeline/scripts/persona_config.py b/docs/avatar-art/linocut-pipeline/scripts/persona_config.py index f8fb563..1af04eb 100644 --- a/docs/avatar-art/linocut-pipeline/scripts/persona_config.py +++ b/docs/avatar-art/linocut-pipeline/scripts/persona_config.py @@ -37,7 +37,6 @@ DEFAULT_BACKDROP = { "energy": "#E2E0D0", } DEFAULT_BUST_Y_OFFSET = 40.0 -DEFAULT_FACE_INSET_PX = 8.0 @dataclass @@ -71,32 +70,6 @@ class PersonaConfig: # 단위 회귀가 성립한다. {"browLeft": {...}, "browRight": {...}} 형태(각 # inner/peak/outer), manifest.landmarks.eyebrowLeft/Right와 같은 스키마. brow_landmarks_override: dict | None - # false면 export_rig.py가 RigBrow.halfWidth를 쓰지 않는다(렌더러가 linocutGeometry.ts - # BROW_STOPS 고정값을 쓴다). 기본 true — 원화에서 잰 눈썹 반두께(manifest.browCenterline. - # halfWidth{Left,Right})를 쓴다. P1은 이미 고정 굵기로 빌드·검수됐으므로 false로 둔다. - brow_half_width_from_art: bool - # false면 brow_sprite.py가 눈썹 스프라이트(layers/v2/brow-{left,right}.png)를 - # 만들지 않는다(결정문 §8.4.1). 기본 true. P1은 이미 고정 벡터 눈썹으로 - # 빌드·검수됐으므로 false로 둔다. - brow_sprite_from_art: bool - # false면 brow_sprite.py가 눈꺼풀 선 굵기 배율(RigEye.lineScale)을 재지 않는다 - # (결정문 §8.4.1). 기본 true. P1은 lineScale의 기준값 자체(P1_UPPER_LID_THICKNESS_PX)라 - # false로 둔다. - eye_line_scale_from_art: bool - # false면 export_rig.py가 RigEye.iris에 manifest.landmarks(mediapipe)의 값을 그대로 - # 쓴다. 기본 true — brow_sprite.py가 원화에서 실측한 홍채(manifest.iris)를 쓴다 - # (2차 보정 T3: mediapipe iris 랜드마크는 실제 눈 비례로 학습돼 판화 잉크 홍채보다 - # 크게 잡는 경향이 있다). P1은 이미 고정 벡터 렌더로 빌드·검수됐으므로 false로 둔다. - iris_from_art: bool - # faceDetail.region: "features"(기본, 눈·입 주변 타원만) | "face"(얼굴 오벌 전체 — - # 캐스트 확장, 얼굴 피부 해칭 보존). face_detail.py만 쓴다. - face_detail_region: str - # region="face"일 때 얼굴 오벌 경계에서 안쪽으로 줄이는 폭(px) — 원화 윤곽 잉크선이 - # 이 영역에 들어오지 않게 하는 여유. region="features"면 쓰이지 않는다. - face_inset_px: float - # true면 face_detail.py가 제외 영역(눈·눈썹·입) 안을 채우는 faceless(F) 픽셀의 - # 색을 주변 원화 피부에 맞춰 평행이동한다(보간/블러 없음). 기본 false(P1 그대로). - match_faceless_color: bool def require_pivots(self) -> dict: if self.pivots is None: @@ -210,18 +183,6 @@ def load_persona_config(persona_dir: Path) -> PersonaConfig: face_detail = data.get("faceDetail", {}) moles = face_detail.get("moles", []) brow_landmarks_override = face_detail.get("browLandmarksOverride") - brow_half_width_from_art = data.get("browHalfWidthFromArt", True) - brow_sprite_from_art = data.get("browSpriteFromArt", True) - eye_line_scale_from_art = data.get("eyeLineScaleFromArt", True) - iris_from_art = data.get("irisFromArt", True) - face_detail_region = face_detail.get("region", "features") - if face_detail_region not in ("features", "face"): - raise SystemExit( - f"[중단] {persona_dir.name}/persona.json의 faceDetail.region={face_detail_region!r}는 " - "지원하지 않는다 — \"features\" 또는 \"face\"만 쓴다." - ) - face_inset_px = float(face_detail.get("faceInsetPx", DEFAULT_FACE_INSET_PX)) - match_faceless_color = bool(face_detail.get("matchFacelessColor", False)) pivots = data.get("pivots") crops = data.get("crops", {}) @@ -250,11 +211,4 @@ def load_persona_config(persona_dir: Path) -> PersonaConfig: palette_fixed=palette_fixed, backdrop=backdrop, brow_landmarks_override=brow_landmarks_override, - brow_half_width_from_art=bool(brow_half_width_from_art), - brow_sprite_from_art=bool(brow_sprite_from_art), - eye_line_scale_from_art=bool(eye_line_scale_from_art), - iris_from_art=bool(iris_from_art), - face_detail_region=face_detail_region, - face_inset_px=face_inset_px, - match_faceless_color=match_faceless_color, ) diff --git a/docs/avatar-art/linocut-pipeline/scripts/register_edit.py b/docs/avatar-art/linocut-pipeline/scripts/register_edit.py deleted file mode 100644 index 8ac0acc..0000000 --- a/docs/avatar-art/linocut-pipeline/scripts/register_edit.py +++ /dev/null @@ -1,327 +0,0 @@ -"""공통 리노컷 리그 — 캐스트 확장 0단계: codex 편집본을 원화 좌표계로 정렬. - -ref는 편집의 참조가 된 원본(후보 원화, normalize_base.py 이전 — 아직 캔버스로 -옮기지 않은 원래 크기)이고, edited는 그 ref를 참조해 codex가 편집한 결과다 -(얼굴 없는 기본형 또는 body 재생성). 크기가 다를 수 있다. - -정렬: edited를 ref 크기로 리사이즈한 뒤, cv2.findTransformECC(MOTION_AFFINE)로 -회색조·가우시안 흐림(gaussFiltSize) 위에서 ref 좌표계에 정렬한다. ECC가 보는 -영역은 모드별 마스크로 제한한다(전체 이미지를 다 보면 얼굴 생김새 차이·크로마키 -배경이 정렬을 왜곡한다): - -- faceless: 머리카락·옷·얼굴 윤곽처럼 두 이미지에서 모양이 같아야 하는 곳만 - 본다 — 랜드마크(ref 기준)로 잡은 눈·눈썹·입 상자를 25px 넓혀 뺀 뒤, 평평한 - 배경(지역 표준편차가 작은 곳)도 뺀다. -- body: 턱끝 +20px 아래 행 중 edited(리사이즈본)의 비초록(크로마키 전경) 픽셀만 - 본다 — 그 위는 얼굴이라 body 재생성과 무관하고, 초록 배경은 정렬 정보가 없다. - -출력은 ref 크기로 맞춘 정렬된 편집본 1장이다. ECC로 못 채우는 테두리는 -faceless면 테두리 복제(BORDER_REPLICATE), body면 크로마키 배경(#00ff00)이다. - -사용: register_edit.py --mode faceless|body [--report ] -""" -from __future__ import annotations - -import argparse -import json -import subprocess -import sys -import tempfile -from pathlib import Path - -import cv2 -import numpy as np -from PIL import Image -from scipy.ndimage import distance_transform_edt - -SCRIPTS_DIR = Path(__file__).resolve().parent -sys.path.insert(0, str(SCRIPTS_DIR)) -from segmentation import chroma_key # noqa: E402 — 크로마키 전경/배경 판정 재사용(같은 임계치로 일관성 유지) - -# --- mediapipe FaceLandmarker 인덱스(landmarks.py/segmentation.py와 같다) --- -RIGHT_EYEBROW_IDX = [46, 53, 52, 65, 55, 70, 63, 105, 66, 107] -LEFT_EYEBROW_IDX = [276, 283, 282, 295, 285, 300, 293, 334, 296, 336] -EYE_A_IDX = {"outer": 33, "inner": 133, "top": 159, "bottom": 145} -EYE_B_IDX = {"inner": 362, "outer": 263, "top": 386, "bottom": 374} -MOUTH_CORNER_A_IDX = 61 -MOUTH_CORNER_B_IDX = 291 -UPPER_LIP_TOP_IDX = 0 -LOWER_LIP_BOTTOM_IDX = 17 -CHIN_IDX = 152 -FACE_OVAL_LOOP = [ - 10, 338, 297, 332, 284, 251, 389, 356, 454, 323, 361, 288, 397, 365, 379, - 378, 400, 377, 152, 148, 176, 149, 150, 136, 172, 58, 132, 93, 234, 127, - 162, 21, 54, 103, 67, 109, -] -FACE_OVAL_SCALE = 1.04 - -# --- ECC --- -ECC_EXCLUDE_PAD_PX = 25.0 # 눈·눈썹·입 상자를 이만큼 넓혀 ECC 마스크에서 뺀다 -ECC_CHIN_MARGIN_PX = 20.0 # body: 턱끝 + 이만큼 아래부터 본다 -FLAT_BG_WINDOW = 9 # 평평한 배경 판정용 지역 표준편차 창(px) -FLAT_BG_STD_THRESH = 6.0 # 이 미만이면 "평평"(정렬 정보 없음)으로 보고 뺀다 -GREEN_ALPHA_THRESH = 127 # chroma_key 알파 임계치(이상이면 비초록 전경) -ECC_MIN_MASK_PX = 400 # 이보다 적으면 ECC가 의미 있게 수렴하지 않는다 -ECC_GAUSS_FILT_SIZE = 7 # findTransformECC 내부 가우시안 흐림 창(홀수) -ECC_MAX_ITER = 5000 -ECC_EPS = 1e-7 - -# --- 지표 --- -CANNY_BLUR_SIGMA = 1.0 -CANNY_LOW, CANNY_HIGH = 40, 120 -FACE_OVAL_BAND_PX = 12.0 # 얼굴 윤곽 띠 폭(±px) -INK_LUM_THRESH = 70.0 # "어두운 잉크" 휘도 임계치 - - -def detect_face_landmarks(image_path: Path) -> list[tuple[float, float]]: - """FaceLandmarker를 별도 프로세스로 실행한다(세그폴트 회피, segmentation.py와 같은 이유).""" - with tempfile.TemporaryDirectory() as td: - out_json = Path(td) / "landmarks.json" - proc = subprocess.run( - [sys.executable, "-u", str(SCRIPTS_DIR / "_run_face_landmarks.py"), str(image_path), str(out_json)], - capture_output=True, text=True, - ) - print(proc.stdout.strip()) - if proc.returncode != 0 or not out_json.exists(): - raise SystemExit(f"[중단] {image_path.name}: FaceLandmarker 서브프로세스 실패.\n{proc.stderr}") - data = json.loads(out_json.read_text(encoding="utf-8")) - if not data.get("ok"): - raise SystemExit(f"[중단] {image_path.name}: FaceLandmarker가 얼굴을 찾지 못했다.") - return [(p[0], p[1]) for p in data["points"]] - - -def bbox_of(points: list[tuple[float, float]], idxs: list[int]) -> tuple[float, float, float, float]: - xs = [points[i][0] for i in idxs] - ys = [points[i][1] for i in idxs] - return (min(xs), min(ys), max(xs), max(ys)) - - -def expand_box(box: tuple[float, float, float, float], pad: float, w: int, h: int) -> tuple[int, int, int, int]: - x0, y0, x1, y1 = box - return ( - max(0, int(np.floor(x0 - pad))), max(0, int(np.floor(y0 - pad))), - min(w, int(np.ceil(x1 + pad))), min(h, int(np.ceil(y1 + pad))), - ) - - -def build_face_boxes(points: list[tuple[float, float]]) -> dict[str, tuple[float, float, float, float]]: - """눈(좌/우)·눈썹(좌/우)·입 상자(원화 좌표, 확장 전).""" - mouth_idx = [MOUTH_CORNER_A_IDX, MOUTH_CORNER_B_IDX, UPPER_LIP_TOP_IDX, LOWER_LIP_BOTTOM_IDX] - return { - "eyeA": bbox_of(points, list(EYE_A_IDX.values())), - "eyeB": bbox_of(points, list(EYE_B_IDX.values())), - "browA": bbox_of(points, RIGHT_EYEBROW_IDX), - "browB": bbox_of(points, LEFT_EYEBROW_IDX), - "mouth": bbox_of(points, mouth_idx), - } - - -def flat_background_mask(gray_u8: np.ndarray) -> np.ndarray: - """지역(FLAT_BG_WINDOW) 표준편차가 FLAT_BG_STD_THRESH 미만인 곳 — 평평한 배경(정렬 - 정보가 없는 영역)으로 보고 ECC 포함 마스크에서 뺀다.""" - g = gray_u8.astype(np.float32) - mean = cv2.boxFilter(g, -1, (FLAT_BG_WINDOW, FLAT_BG_WINDOW)) - sqmean = cv2.boxFilter(g * g, -1, (FLAT_BG_WINDOW, FLAT_BG_WINDOW)) - std = np.sqrt(np.clip(sqmean - mean * mean, 0.0, None)) - return std < FLAT_BG_STD_THRESH - - -def face_oval_band_mask(points: list[tuple[float, float]], w: int, h: int, band_px: float) -> np.ndarray: - """랜드마크 face oval(결정문 §8.2, segmentation.py/export_rig.py와 같은 루프·배율) - 경계선에서 band_px 이내인 픽셀.""" - from PIL import ImageDraw - - loop_pts = [points[i] for i in FACE_OVAL_LOOP] - cx = float(np.mean([p[0] for p in loop_pts])) - cy = float(np.mean([p[1] for p in loop_pts])) - scaled = [((x - cx) * FACE_OVAL_SCALE + cx, (y - cy) * FACE_OVAL_SCALE + cy) for x, y in loop_pts] - outline_img = Image.new("L", (w, h), 0) - ImageDraw.Draw(outline_img).polygon(scaled, outline=255, width=1) - boundary = np.array(outline_img) > 0 - dist_to_boundary = distance_transform_edt(~boundary) - return dist_to_boundary <= band_px - - -def chamfer_stats(ref_edges: np.ndarray, aligned_edges: np.ndarray, region_mask: np.ndarray) -> dict: - """aligned_edges 중 region_mask 안에 있는 에지 픽셀들이 ref_edges의 가장 가까운 - 에지까지 떨어진 거리(챔퍼)의 중앙값·p90. ref_edges 쪽 거리장은 호출자가 한 번 - 계산해 재사용하도록 이미 distance_transform_edt를 적용한 배열을 받는다는 점에 - 주의 — 이 함수는 edge 불boolean 배열 두 개를 받아 내부에서 거리장을 계산한다.""" - dist_to_ref_edge = distance_transform_edt(~ref_edges) - sample_mask = aligned_edges & region_mask - n = int(sample_mask.sum()) - if n == 0: - return {"median": None, "p90": None, "nSamples": 0} - d = dist_to_ref_edge[sample_mask] - return {"median": round(float(np.median(d)), 3), "p90": round(float(np.percentile(d, 90)), 3), "nSamples": n} - - -def ink_ratio(gray_u8: np.ndarray, box: tuple[int, int, int, int]) -> float: - x0, y0, x1, y1 = box - region = gray_u8[y0:y1, x0:x1].astype(np.float64) - if region.size == 0: - return float("nan") - return float((region < INK_LUM_THRESH).mean()) - - -def decompose_affine(warp_matrix: np.ndarray) -> dict: - a, b, tx = warp_matrix[0] - c, d, ty = warp_matrix[1] - det = a * d - b * c - scale = float(np.sqrt(abs(det))) - rotation_deg = float(np.degrees(np.arctan2(c, a))) - return { - "scaleSqrtDet": round(scale, 5), - "rotationDeg": round(rotation_deg, 3), - "translateX": round(float(tx), 3), - "translateY": round(float(ty), 3), - "matrix": [[round(float(v), 6) for v in row] for row in warp_matrix.tolist()], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다.", - } - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description="codex 편집본을 ECC 아핀 정렬로 원화(ref) 좌표계에 맞춘다.") - parser.add_argument("ref", help="편집의 참조가 된 원본(원화)") - parser.add_argument("edited", help="codex 편집본(크기가 ref와 다를 수 있다)") - parser.add_argument("out", help="정렬된 출력(ref 크기)") - parser.add_argument("--mode", required=True, choices=["faceless", "body"]) - parser.add_argument("--report", help="지표 JSON 저장 경로(생략 가능, 콘솔에는 항상 출력)") - return parser.parse_args(argv) - - -def main(argv: list[str]) -> int: - args = parse_args(argv) - ref_path = Path(args.ref).resolve() - edited_path = Path(args.edited).resolve() - out_path = Path(args.out).resolve() - mode = args.mode - - ref_img = Image.open(ref_path).convert("RGB") - w, h = ref_img.size - ref_arr = np.array(ref_img) - ref_gray = cv2.cvtColor(ref_arr, cv2.COLOR_RGB2GRAY) - - edited_img = Image.open(edited_path).convert("RGB") - ew, eh = edited_img.size - print(f"ref 크기={w}x{h} edited 원본 크기={ew}x{eh} (리사이즈 후 ref 크기로 정렬)") - edited_resized = np.array(edited_img.resize((w, h), Image.LANCZOS)) - edited_gray = cv2.cvtColor(edited_resized, cv2.COLOR_RGB2GRAY) - - print("=== ref 랜드마크 검출 ===") - ref_points = detect_face_landmarks(ref_path) - chin_xy = ref_points[CHIN_IDX] - print(f"턱끝(152)={chin_xy}") - - boxes_report: dict[str, list[int]] = {} - if mode == "faceless": - boxes = build_face_boxes(ref_points) - exclude = np.zeros((h, w), dtype=bool) - for name, box in boxes.items(): - x0, y0, x1, y1 = expand_box(box, ECC_EXCLUDE_PAD_PX, w, h) - boxes_report[name] = [x0, y0, x1, y1] - exclude[y0:y1, x0:x1] = True - flat_bg = flat_background_mask(ref_gray) - include = ~exclude & ~flat_bg - print(f"제외 상자(25px 확장): {boxes_report}") - print(f"평평한 배경 제외 픽셀: {int(flat_bg.sum())} ({flat_bg.mean()*100:.1f}%)") - else: - _, edited_alpha = chroma_key(edited_resized) - nongreen = edited_alpha > GREEN_ALPHA_THRESH - chin_line = chin_xy[1] + ECC_CHIN_MARGIN_PX - below_chin = np.zeros((h, w), dtype=bool) - below_chin[int(np.ceil(chin_line)):, :] = True - include = below_chin & nongreen - print(f"턱끝+{ECC_CHIN_MARGIN_PX:.0f}px={chin_line:.1f} 아래 비초록 픽셀: {int(include.sum())}") - - n_include = int(include.sum()) - print(f"ECC 포함 마스크 픽셀 수: {n_include} / {w * h} ({n_include / (w * h) * 100:.2f}%)") - if n_include < ECC_MIN_MASK_PX: - raise SystemExit(f"[중단] ECC 포함 마스크 픽셀이 너무 적다({n_include} < {ECC_MIN_MASK_PX}).") - include_u8 = (include.astype(np.uint8)) * 255 - - warp0 = np.eye(2, 3, dtype=np.float32) - criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, ECC_MAX_ITER, ECC_EPS) - try: - cc, warp_matrix = cv2.findTransformECC( - ref_gray.astype(np.float32), edited_gray.astype(np.float32), - warp0, cv2.MOTION_AFFINE, criteria, include_u8, ECC_GAUSS_FILT_SIZE, - ) - except cv2.error as exc: - raise SystemExit(f"[중단] ECC 정렬이 수렴하지 않았다: {exc}") - print(f"ECC 상관계수={cc:.5f}") - print(f"warp_matrix=\n{warp_matrix}") - - transform_report = decompose_affine(warp_matrix) - print( - f"변환: 배율(sqrt det)={transform_report['scaleSqrtDet']} 회전={transform_report['rotationDeg']}deg " - f"이동=({transform_report['translateX']}, {transform_report['translateY']})" - ) - - border_mode = cv2.BORDER_REPLICATE if mode == "faceless" else cv2.BORDER_CONSTANT - border_value = None if mode == "faceless" else (0, 255, 0) - warp_kwargs: dict = {"flags": cv2.INTER_LANCZOS4 | cv2.WARP_INVERSE_MAP, "borderMode": border_mode} - if border_value is not None: - warp_kwargs["borderValue"] = border_value - aligned = cv2.warpAffine(edited_resized, warp_matrix, (w, h), **warp_kwargs) - - out_path.parent.mkdir(parents=True, exist_ok=True) - Image.fromarray(aligned, "RGB").save(out_path) - print(f"저장: {out_path}") - - aligned_gray = cv2.cvtColor(aligned, cv2.COLOR_RGB2GRAY) - ref_edges = cv2.Canny(cv2.GaussianBlur(ref_gray, (0, 0), CANNY_BLUR_SIGMA), CANNY_LOW, CANNY_HIGH) > 0 - aligned_edges = cv2.Canny(cv2.GaussianBlur(aligned_gray, (0, 0), CANNY_BLUR_SIGMA), CANNY_LOW, CANNY_HIGH) > 0 - - mask_chamfer = chamfer_stats(ref_edges, aligned_edges, include) - print( - f"마스크 안 Canny 챔퍼: 중앙값={mask_chamfer['median']} p90={mask_chamfer['p90']} " - f"(n={mask_chamfer['nSamples']})" - + (f" 기준(중앙값<=1.0,p90<=2.5) {'OK' if mask_chamfer['median'] is not None and mask_chamfer['median'] <= 1.0 and mask_chamfer['p90'] <= 2.5 else '[미달]'}" - if mode == "faceless" else - f" 기준(중앙값<=1.5,p90<=3.0) {'OK' if mask_chamfer['median'] is not None and mask_chamfer['median'] <= 1.5 and mask_chamfer['p90'] <= 3.0 else '[미달]'}") - ) - - report: dict = { - "mode": mode, - "ref": str(ref_path), - "edited": str(edited_path), - "out": str(out_path), - "refSize": [w, h], - "editedOriginalSize": [ew, eh], - "eccCorrelation": round(float(cc), 5), - "transform": transform_report, - "eccIncludeMaskPixels": n_include, - "chamferInMask": mask_chamfer, - } - - if mode == "faceless": - oval_band = face_oval_band_mask(ref_points, w, h, FACE_OVAL_BAND_PX) - oval_chamfer = chamfer_stats(ref_edges, aligned_edges, oval_band) - print( - f"얼굴 윤곽 띠(±{FACE_OVAL_BAND_PX:.0f}px) Canny 챔퍼: 중앙값={oval_chamfer['median']} " - f"p90={oval_chamfer['p90']} (n={oval_chamfer['nSamples']}) 기준(p90<=2.0) " - f"{'OK' if oval_chamfer['p90'] is not None and oval_chamfer['p90'] <= 2.0 else '[미달]'}" - ) - report["faceOvalBandChamfer"] = oval_chamfer - - ink_report: dict[str, dict] = {} - for name, box in boxes_report.items(): - ref_ratio = ink_ratio(ref_gray, tuple(box)) - edited_ratio = ink_ratio(aligned_gray, tuple(box)) - ink_report[name] = {"box": box, "refInkRatio": round(ref_ratio, 4), "editedInkRatio": round(edited_ratio, 4)} - print(f" 잉크 비율(lum<{INK_LUM_THRESH:.0f}) [{name}] ref={ref_ratio:.4f} edited={edited_ratio:.4f}") - report["inkRatioByBox"] = ink_report - report["eccExcludeBoxes"] = boxes_report - - if args.report: - report_path = Path(args.report).resolve() - report_path.parent.mkdir(parents=True, exist_ok=True) - report_path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") - print(f"저장: {report_path}") - - return 0 - - -if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) diff --git a/docs/avatar-art/linocut-pipeline/scripts/run_pipeline.py b/docs/avatar-art/linocut-pipeline/scripts/run_pipeline.py index a3fb687..5fa8cc6 100644 --- a/docs/avatar-art/linocut-pipeline/scripts/run_pipeline.py +++ b/docs/avatar-art/linocut-pipeline/scripts/run_pipeline.py @@ -1,7 +1,7 @@ """공통 리노컷 리그 파이프라인 — 단일 진입점. 페르소나 폴더 하나(예: docs/avatar-art/p1-linocut/, persona.json이 있어야 한다)에 -대해 1~11단계를 정해진 의존 순서대로 실행한다. 각 단계는 독립 프로세스로 돌린다 +대해 1~8단계를 정해진 의존 순서대로 실행한다. 각 단계는 독립 프로세스로 돌린다 (mediapipe FaceLandmarker+ImageSegmenter를 같은 프로세스에서 같이 쓰면 세그폴트가 나는 것과 같은 이유로, 모든 단계를 서브프로세스로 분리해 두면 한 단계의 문제가 다른 단계·전체 파이프라인을 오염시키지 않는다). @@ -15,12 +15,11 @@ export-rig-report.json을 읽으므로 반드시 export_rig *다음*이다): 3. segmentation — layers/{body,head,hairFront}.png(v1) + manifest 기준 섹션 4. layers_v2 — layers/v2/{body,head,hairFront}.png(결함 보정) 5. face_detail — layers/v2/face-detail.png - 6. brow_sprite — layers/v2/brow-{left,right}.png + manifest.eyeLineScale(결정문 §8.4.1) - 7. paper_grain — layers/v2/paper-grain.png(다른 단계와 독립, 순서 유연) - 8. lip_texture — layers/v2/lip-{upper,lower,shadow}.png - 9. jaw_pieces — layers/v2/jaw-{head,detail}.png - 10. export_rig — WebP 게시 + rigs/Rig.ts 생성 - 11. final_previews — 게시된 WebP로 모션·눈/입 확대 미리보기 + 6. paper_grain — layers/v2/paper-grain.png(다른 단계와 독립, 순서 유연) + 7. lip_texture — layers/v2/lip-{upper,lower,shadow}.png + 8. jaw_pieces — layers/v2/jaw-{head,detail}.png + 9. export_rig — WebP 게시 + rigs/Rig.ts 생성 + 10. final_previews — 게시된 WebP로 모션·눈/입 확대 미리보기 주의(재실행 순서): landmarks를 혼자 다시 돌리면 manifest.landmarks를 통째로 새로 쓰므로, brow_centerline이 보정한 eyebrowLeft/Right가 사라진다. landmarks만 @@ -37,7 +36,6 @@ from __future__ import annotations import argparse import json -import os import subprocess import sys import time @@ -54,7 +52,6 @@ STAGES: list[tuple[str, str]] = [ ("segmentation", "segmentation.py"), ("layers_v2", "layers_v2.py"), ("face_detail", "face_detail.py"), - ("brow_sprite", "brow_sprite.py"), ("paper_grain", "paper_grain.py"), ("lip_texture", "lip_texture.py"), ("jaw_pieces", "jaw_pieces.py"), @@ -67,13 +64,9 @@ STAGE_NAMES = [name for name, _ in STAGES] def run_stage(name: str, script: str, persona_dir: Path) -> tuple[bool, str, float]: script_path = SCRIPTS_DIR / script t0 = time.monotonic() - # 단계 스크립트 출력은 UTF-8로 주고받는다. Windows 콘솔 기본(cp949)은 em dash 같은 문자를 - # 못 그려, 모티프 없는 리그 분기(export_rig) 같은 데서 단계가 UnicodeEncodeError로 죽었다 - # (2026-10-01 P7). 부모가 콘솔에 옮겨 쓸 때 못 그리는 문자는 main()에서 대체 문자로 바꾼다. proc = subprocess.run( [sys.executable, "-u", str(script_path), str(persona_dir)], - capture_output=True, text=True, encoding="utf-8", errors="replace", - env={**os.environ, "PYTHONIOENCODING": "utf-8"}, + capture_output=True, text=True, ) elapsed = time.monotonic() - t0 output = proc.stdout + proc.stderr @@ -82,8 +75,6 @@ def run_stage(name: str, script: str, persona_dir: Path) -> tuple[bool, str, flo def main() -> int: - sys.stdout.reconfigure(errors="replace") - sys.stderr.reconfigure(errors="replace") parser = argparse.ArgumentParser(description="공통 리노컷 파이프라인 실행") parser.add_argument("persona_dir", nargs="?", help="페르소나 폴더(예: docs/avatar-art/p1-linocut)") parser.add_argument("--only", choices=STAGE_NAMES, help="이 단계만 실행한다") diff --git a/docs/avatar-art/linocut-pipeline/scripts/segmentation.py b/docs/avatar-art/linocut-pipeline/scripts/segmentation.py index 15046af..0095d88 100644 --- a/docs/avatar-art/linocut-pipeline/scripts/segmentation.py +++ b/docs/avatar-art/linocut-pipeline/scripts/segmentation.py @@ -112,18 +112,8 @@ def chroma_key(rgb_u8: np.ndarray) -> tuple[np.ndarray, np.ndarray]: def build_padded_faceless(base_dir: Path) -> Image.Image: bf = Image.open(base_dir / "base-faceless.png").convert("RGB") - front_h = Image.open(base_dir / "base-front.png").size[1] arr = np.array(bf) - deficit = front_h - arr.shape[0] - if deficit < 0: - raise SystemExit( - f"[중단] base-faceless.png 높이({arr.shape[0]})가 base-front.png 높이({front_h})보다 크다." - ) - if deficit > 0: - pad_rows = np.repeat(arr[-1:, :, :], deficit, axis=0) - padded = np.concatenate([arr, pad_rows], axis=0) - else: - padded = arr + padded = np.concatenate([arr, arr[-1:, :, :]], axis=0) out = Image.fromarray(padded, "RGB") out.save(base_dir / "base-faceless-padded.png") return out @@ -243,12 +233,10 @@ def main(persona_dir: Path) -> int: layers_dir.mkdir(parents=True, exist_ok=True) preview_dir.mkdir(parents=True, exist_ok=True) - orig_faceless_h = Image.open(base_dir / "base-faceless.png").size[1] f_img = build_padded_faceless(base_dir) f_arr = np.array(f_img) h, w, _ = f_arr.shape - pad_rows = h - orig_faceless_h - print(f"F(패딩된 base-faceless) 크기: {w}x{h} (원본 {w}x{orig_faceless_h}, 패딩 {pad_rows}행)") + print(f"F(패딩된 base-faceless) 크기: {w}x{h}") front_pts = detect_face_landmarks(base_dir / "base-front.png") chin_y = front_pts[152][1] @@ -454,8 +442,8 @@ def main(persona_dir: Path) -> int: "front": "base/base-front.png", "faceless": "base/base-faceless.png", "facelessPadded": "base/base-faceless-padded.png", - "facelessSize": [w, orig_faceless_h], - "padNote": f"base-faceless.png 마지막 행을 {pad_rows}번 복제해 {w}x{h}(base-front.png 크기)으로 패딩한 것이 F다.", + "facelessSize": [w, h - 1], + "padNote": f"base-faceless.png 마지막 행을 복제해 {w}x{h}(base-front.png 크기)으로 패딩한 것이 F다.", } manifest["segmenter"] = { "model": "selfie_multiclass_256x256.tflite (mediapipe ImageSegmenter, storage.googleapis.com)", diff --git a/docs/avatar-art/p1-linocut/manifest.json b/docs/avatar-art/p1-linocut/manifest.json index a85ca63..6bcba08 100644 --- a/docs/avatar-art/p1-linocut/manifest.json +++ b/docs/avatar-art/p1-linocut/manifest.json @@ -13,7 +13,7 @@ 1005, 1565 ], - "padNote": "base-faceless.png 마지막 행을 1번 복제해 1005x1566(base-front.png 크기)으로 패딩한 것이 F다." + "padNote": "base-faceless.png 마지막 행을 복제해 1005x1566(base-front.png 크기)으로 패딩한 것이 F다." }, "layers": [ { @@ -424,10 +424,6 @@ ], "faceDetail": { "browLandmarksSource": "persona.json faceDetail.browLandmarksOverride", - "region": "features", - "faceInsetPx": null, - "matchFacelessColor": false, - "matchFacelessColorDeltaRgb": {}, "regions": { "eyeLeft": [ 309.1, @@ -1443,9 +1439,6 @@ "peakFail": false, "checkA_fracColumnsFitWithinMask": 0.9247, "checkA_pass": true, - "halfWidthInner": 2.389, - "halfWidthPeak": 8.999, - "halfWidthOuter": 3.963, "bandX0": 274, "bandY0": 456, "colMin": 309, @@ -1489,26 +1482,12 @@ "peakFail": false, "checkA_fracColumnsFitWithinMask": 0.9202, "checkA_pass": true, - "halfWidthInner": 5.84, - "halfWidthPeak": 9.499, - "halfWidthOuter": 2.984, "bandX0": 506, "bandY0": 454, "colMin": 513, "colMax": 688 } }, - "halfWidthLeft": [ - 2.389, - 8.999, - 3.963 - ], - "halfWidthRight": [ - 5.84, - 9.499, - 2.984 - ], - "halfWidthNote": "원화 눈썹 잉크 띠 반두께[inner,peak,outer](px, 중심선 법선 방향). export_rig.py가 persona.json browHalfWidthFromArt!=false(기본)면 RigBrow.halfWidth로 내보낸다.", "checkB_innerMinusPeakY": { "browLeft": 14.62, "browRight": 13.47 @@ -1742,84 +1721,59 @@ }, "pipelineRun": { "persona": "P1", - "timestamp": "2026-10-01T21:46:24", + "timestamp": "2026-10-01T11:04:58", "completed": true, "stages": [ { - "stage": "face_detail", + "stage": "landmarks", "ok": true, - "elapsedSec": 15.41 + "elapsedSec": 1.24 }, { - "stage": "brow_sprite", + "stage": "brow_centerline", "ok": true, - "elapsedSec": 1.05 + "elapsedSec": 0.64 + }, + { + "stage": "segmentation", + "ok": true, + "elapsedSec": 7.84 + }, + { + "stage": "layers_v2", + "ok": true, + "elapsedSec": 12.39 + }, + { + "stage": "face_detail", + "ok": true, + "elapsedSec": 12.25 }, { "stage": "paper_grain", "ok": true, - "elapsedSec": 2.45 + "elapsedSec": 1.91 }, { "stage": "lip_texture", "ok": true, - "elapsedSec": 2.45 + "elapsedSec": 1.92 }, { "stage": "jaw_pieces", "ok": true, - "elapsedSec": 2.17 + "elapsedSec": 1.89 }, { "stage": "export_rig", "ok": true, - "elapsedSec": 22.95 + "elapsedSec": 21.12 }, { "stage": "final_previews", "ok": true, - "elapsedSec": 19.59 + "elapsedSec": 16.83 } ] - }, - "browSprite": { - "enabled": false - }, - "eyeLineScale": { - "enabled": false - }, - "browSpritePublish": null, - "iris": { - "method": "가로: 중심 y 부근 상하 각 4행에서 중심 x를 포함하는 연속 어두운(lum<100) 구간의 중앙값으로 반지름·중심x. 세로: 중심 x 부근 좌우 각 4열에서 중심 y를 포함하는 연속 어두운 구간의 아래쪽 끝 중앙값에서 가로 반지름을 빼 중심y(세로축은 윗눈꺼풀 잉크와 홍채가 같은 톤으로 이어져 위쪽 경계를 가를 수 없다).", - "darkLumThresh": 100.0, - "scanBandPx": 4, - "left": { - "measuredCenter": [ - 395.0, - 567.0 - ], - "measuredRadius": 30.0, - "rigCenter": [ - 387.33, - 579.15 - ], - "rigRadius": 21.43, - "nRowsHorizontal": 9, - "nColsVertical": 9 - }, - "right": { - "measuredCenter": [ - 600.0, - 573.0 - ], - "measuredRadius": 22.0, - "rigCenter": [ - 601.93, - 576.21 - ], - "rigRadius": 21.28, - "nRowsHorizontal": 9, - "nColsVertical": 9 - } } } \ No newline at end of file diff --git a/docs/avatar-art/p1-linocut/persona.json b/docs/avatar-art/p1-linocut/persona.json index 8ee341d..1d67318 100644 --- a/docs/avatar-art/p1-linocut/persona.json +++ b/docs/avatar-art/p1-linocut/persona.json @@ -35,10 +35,6 @@ "browRight": { "inner": [531.24, 535.37], "peak": [628.82, 496.05], "outer": [687.99, 522.25] } } }, - "browHalfWidthFromArt": false, - "browSpriteFromArt": false, - "eyeLineScaleFromArt": false, - "irisFromArt": false, "pivots": { "neck": [500, 990], "body": [502, 1566], diff --git a/docs/avatar-art/p1-linocut/preview/v2/export-rig-report.json b/docs/avatar-art/p1-linocut/preview/v2/export-rig-report.json index 05d5072..c35ad66 100644 --- a/docs/avatar-art/p1-linocut/preview/v2/export-rig-report.json +++ b/docs/avatar-art/p1-linocut/preview/v2/export-rig-report.json @@ -267,7 +267,6 @@ }, "haloThreshold": 2.0 }, - "browSpritePublish": null, "rig": { "schemaVersion": "vignette.avatar.v3.rig.v1", "persona": "P1", diff --git a/docs/avatar-art/p2-linocut/base/base-faceless.png b/docs/avatar-art/p2-linocut/base/base-faceless.png deleted file mode 100644 index a42bbbf..0000000 Binary files a/docs/avatar-art/p2-linocut/base/base-faceless.png and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/base/base-front.png b/docs/avatar-art/p2-linocut/base/base-front.png deleted file mode 100644 index 92a88e5..0000000 Binary files a/docs/avatar-art/p2-linocut/base/base-front.png and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/base/edits/aligned-body-1.json b/docs/avatar-art/p2-linocut/base/edits/aligned-body-1.json deleted file mode 100644 index 0ad90ba..0000000 --- a/docs/avatar-art/p2-linocut/base/edits/aligned-body-1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\body-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\aligned-body-1.png", - "refSize": [ - 1004, - 1566 - ], - "editedOriginalSize": [ - 1004, - 1567 - ], - "eccCorrelation": 0.86251, - "transform": { - "scaleSqrtDet": 1.00197, - "rotationDeg": -0.001, - "translateX": -1.76, - "translateY": -2.211, - "matrix": [ - [ - 1.002585, - -0.000235, - -1.760458 - ], - [ - -9e-06, - 1.001356, - -2.210697 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 584670, - "chamferInMask": { - "median": 0.0, - "p90": 3.0, - "nSamples": 110891 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p2-linocut/base/edits/aligned-body-2.json b/docs/avatar-art/p2-linocut/base/edits/aligned-body-2.json deleted file mode 100644 index b9ea9f7..0000000 --- a/docs/avatar-art/p2-linocut/base/edits/aligned-body-2.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\body-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\aligned-body-2.png", - "refSize": [ - 1004, - 1566 - ], - "editedOriginalSize": [ - 1004, - 1567 - ], - "eccCorrelation": 0.86381, - "transform": { - "scaleSqrtDet": 1.00257, - "rotationDeg": -0.001, - "translateX": -0.653, - "translateY": -5.297, - "matrix": [ - [ - 1.001779, - -0.000341, - -0.652621 - ], - [ - -2.6e-05, - 1.00337, - -5.296521 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 584730, - "chamferInMask": { - "median": 1.0, - "p90": 2.828, - "nSamples": 108723 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p2-linocut/base/edits/aligned-faceless-1.json b/docs/avatar-art/p2-linocut/base/edits/aligned-faceless-1.json deleted file mode 100644 index 763589c..0000000 --- a/docs/avatar-art/p2-linocut/base/edits/aligned-faceless-1.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\faceless-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\aligned-faceless-1.png", - "refSize": [ - 1004, - 1566 - ], - "editedOriginalSize": [ - 1004, - 1567 - ], - "eccCorrelation": 0.99442, - "transform": { - "scaleSqrtDet": 1.00043, - "rotationDeg": 0.004, - "translateX": -0.943, - "translateY": -0.245, - "matrix": [ - [ - 1.001139, - -8.8e-05, - -0.942587 - ], - [ - 6.6e-05, - 0.999724, - -0.24468 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 881537, - "chamferInMask": { - "median": 0.0, - "p90": 1.414, - "nSamples": 147663 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 6008 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 318, - 527, - 461, - 611 - ], - "refInkRatio": 0.2271, - "editedInkRatio": 0.0011 - }, - "eyeB": { - "box": [ - 526, - 526, - 669, - 610 - ], - "refInkRatio": 0.2123, - "editedInkRatio": 0.0 - }, - "browA": { - "box": [ - 276, - 463, - 475, - 554 - ], - "refInkRatio": 0.367, - "editedInkRatio": 0.1944 - }, - "browB": { - "box": [ - 510, - 460, - 712, - 550 - ], - "refInkRatio": 0.2291, - "editedInkRatio": 0.0745 - }, - "mouth": { - "box": [ - 393, - 750, - 599, - 862 - ], - "refInkRatio": 0.1096, - "editedInkRatio": 0.0 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 318, - 527, - 461, - 611 - ], - "eyeB": [ - 526, - 526, - 669, - 610 - ], - "browA": [ - 276, - 463, - 475, - 554 - ], - "browB": [ - 510, - 460, - 712, - 550 - ], - "mouth": [ - 393, - 750, - 599, - 862 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p2-linocut/base/edits/aligned-faceless-2.json b/docs/avatar-art/p2-linocut/base/edits/aligned-faceless-2.json deleted file mode 100644 index 0f91297..0000000 --- a/docs/avatar-art/p2-linocut/base/edits/aligned-faceless-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\faceless-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\aligned-faceless-2.png", - "refSize": [ - 1004, - 1566 - ], - "editedOriginalSize": [ - 1004, - 1566 - ], - "eccCorrelation": 0.99464, - "transform": { - "scaleSqrtDet": 1.00043, - "rotationDeg": 0.002, - "translateX": -0.804, - "translateY": -0.406, - "matrix": [ - [ - 1.000778, - -2.9e-05, - -0.803723 - ], - [ - 3.8e-05, - 1.000088, - -0.405893 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 881537, - "chamferInMask": { - "median": 0.0, - "p90": 1.414, - "nSamples": 146528 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 5917 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 318, - 527, - 461, - 611 - ], - "refInkRatio": 0.2271, - "editedInkRatio": 0.0001 - }, - "eyeB": { - "box": [ - 526, - 526, - 669, - 610 - ], - "refInkRatio": 0.2123, - "editedInkRatio": 0.0 - }, - "browA": { - "box": [ - 276, - 463, - 475, - 554 - ], - "refInkRatio": 0.367, - "editedInkRatio": 0.1755 - }, - "browB": { - "box": [ - 510, - 460, - 712, - 550 - ], - "refInkRatio": 0.2291, - "editedInkRatio": 0.0665 - }, - "mouth": { - "box": [ - 393, - 750, - 599, - 862 - ], - "refInkRatio": 0.1096, - "editedInkRatio": 0.0001 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 318, - 527, - 461, - 611 - ], - "eyeB": [ - 526, - 526, - 669, - 610 - ], - "browA": [ - 276, - 463, - 475, - 554 - ], - "browB": [ - 510, - 460, - 712, - 550 - ], - "mouth": [ - 393, - 750, - 599, - 862 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p2-linocut/base/edits/sheet.jpg b/docs/avatar-art/p2-linocut/base/edits/sheet.jpg deleted file mode 100644 index 761bf74..0000000 Binary files a/docs/avatar-art/p2-linocut/base/edits/sheet.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/base/normalize.json b/docs/avatar-art/p2-linocut/base/normalize.json deleted file mode 100644 index 78d35a2..0000000 --- a/docs/avatar-art/p2-linocut/base/normalize.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "canvas": { - "w": 1005, - "h": 1566 - }, - "reference": { - "image": "docs\\avatar-art\\p1-linocut\\base\\base-front.png", - "eyesCenter": [ - 492.69, - 584.33 - ], - "chin": [ - 500.61, - 919.55 - ], - "eyeDistance": 215.427, - "eyeChinDistance": 335.308 - }, - "input": { - "front": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\source-front.png", - "faceless": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\aligned-faceless-2.png", - "body": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\base\\edits\\aligned-body-2.png", - "frontSize": [ - 1004, - 1566 - ], - "eyesCenter": [ - 493.46, - 572.88 - ], - "chin": [ - 498.98, - 945.65 - ], - "eyeDistance": 208.065, - "eyeChinDistance": 372.813 - }, - "transform": { - "sHead": 0.967393, - "sWidth": 1.000996, - "s": 1.000996, - "rotationDeg": 0.0, - "offsetXNatural": -1.268, - "offsetXClampRange": [ - 0.0, - 0.0 - ], - "offsetX": 0.0, - "offsetY": 10.881 - }, - "coveredRange": { - "x": [ - 0.0, - 1005 - ], - "y": [ - 10.9, - 1566 - ] - }, - "inputSha256": { - "front": "5b6026cfcea5ad7337fa4431d485d3cbce647cf00e4016221f695158756f9fa1", - "faceless": "bf844904bbfae18ac3086bb6176a07fcd421671322b4980a077166c003a62b0a", - "body": "91b515db47aeae74c27d0a75753af6ee60df0b00dfbc7fde826aa24e4d0c2a3b" - } -} \ No newline at end of file diff --git a/docs/avatar-art/p2-linocut/manifest.json b/docs/avatar-art/p2-linocut/manifest.json deleted file mode 100644 index e6dec53..0000000 --- a/docs/avatar-art/p2-linocut/manifest.json +++ /dev/null @@ -1,1698 +0,0 @@ -{ - "landmarks": { - "coordSystem": "screen (image pixel: x=0 좌측, y=0 상단; left=작은 x, right=큰 x; 인물 해부학적 좌우 아님)", - "eyeLeft": { - "innerCorner": [ - 436.93, - 589.72 - ], - "outerCorner": [ - 345.9, - 578.58 - ], - "upperLidTop": [ - 382.5, - 563.54 - ], - "lowerLidBottom": [ - 387.01, - 595.53 - ], - "iris": { - "center": [ - 390.36, - 578.05 - ], - "radius": 20.26 - } - }, - "eyeRight": { - "innerCorner": [ - 553.44, - 588.28 - ], - "outerCorner": [ - 643.72, - 575.13 - ], - "upperLidTop": [ - 606.63, - 560.81 - ], - "lowerLidBottom": [ - 603.48, - 592.96 - ], - "iris": { - "center": [ - 598.2, - 575.68 - ], - "radius": 20.48 - } - }, - "eyebrowLeft": { - "inner": [ - 459.17, - 529.9 - ], - "peak": [ - 359.35, - 517.35 - ], - "outer": [ - 307.83, - 526.22 - ] - }, - "eyebrowRight": { - "inner": [ - 522.25, - 526.89 - ], - "peak": [ - 630.75, - 512.66 - ], - "outer": [ - 686.75, - 520.91 - ] - }, - "noseTip": [ - 494.67, - 718.39 - ], - "mouthCornerLeft": [ - 417.35, - 818.27 - ], - "mouthCornerRight": [ - 572.28, - 816.91 - ], - "upperLipTopCenter": [ - 494.64, - 787.63 - ], - "lowerLipBottomCenter": [ - 494.14, - 843.92 - ], - "chinTip": [ - 496.44, - 950.72 - ], - "faceWidthAtEyeLevelLeft": [ - 270.67, - 641.28 - ], - "faceWidthAtEyeLevelRight": [ - 725.41, - 637.27 - ] - }, - "landmarkDetector": "mediapipe FaceLandmarker (tasks) 1.0.1, model=face_landmarker(float16, v1)", - "browCenterline": { - "method": "Otsu(front lum in bbox) AND diff(F-front)>=25, morphological closing to merge hatching, single global degree-2 polyfit on column-weighted centroid with one-pass outlier removal", - "diffThresh": 25.0, - "bboxPad": { - "x": 25.0, - "yUp": 42.0, - "yDown": 14.0 - }, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "outlierStdMult": 2.0, - "edgeInsetFrac": 0.03, - "peakWindow": [ - 0.25, - 0.6 - ], - "peakFallbackFrac": 0.35, - "peakFlatHeightPx": 3.0, - "peakFailEdgeFrac": 0.2, - "oldPoints": { - "browLeft": { - "inner": [ - 450.44, - 534.93 - ], - "peak": [ - 352.85, - 500.08 - ], - "outer": [ - 303.22, - 530.63 - ] - }, - "browRight": { - "inner": [ - 537.41, - 533.76 - ], - "peak": [ - 635.0, - 496.78 - ], - "outer": [ - 687.28, - 526.3 - ] - } - }, - "newPoints": { - "browLeft": { - "name": "browLeft", - "bbox": [ - 278, - 458, - 475, - 549 - ], - "otsuThresh": 96.0, - "colRange": [ - 303, - 464 - ], - "width": 161.0, - "nColumns": 162, - "nOutliersRemoved": 7, - "fitCoeffs": [ - 0.00196806, - -1.48522863, - 796.92780822 - ], - "inner": [ - 459.17, - 529.9 - ], - "peak": [ - 359.35, - 517.35 - ], - "outer": [ - 307.83, - 526.22 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.9321, - "checkA_pass": true, - "halfWidthInner": 12.85, - "halfWidthPeak": 16.459, - "halfWidthOuter": 2.411, - "bandX0": 278, - "bandY0": 458, - "colMin": 303, - "colMax": 464 - }, - "browRight": { - "name": "browRight", - "bbox": [ - 512, - 455, - 712, - 548 - ], - "otsuThresh": 109.0, - "colRange": [ - 517, - 692 - ], - "width": 175.0, - "nColumns": 174, - "nOutliersRemoved": 8, - "fitCoeffs": [ - 0.00169369, - -2.08406041, - 1153.34922962 - ], - "inner": [ - 522.25, - 526.89 - ], - "peak": [ - 630.75, - 512.66 - ], - "outer": [ - 686.75, - 520.91 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.9195, - "checkA_pass": true, - "halfWidthInner": 15.261, - "halfWidthPeak": 12.233, - "halfWidthOuter": 6.803, - "bandX0": 512, - "bandY0": 455, - "colMin": 517, - "colMax": 692 - } - }, - "halfWidthLeft": [ - 12.85, - 16.459, - 2.411 - ], - "halfWidthRight": [ - 15.261, - 12.233, - 6.803 - ], - "halfWidthNote": "원화 눈썹 잉크 띠 반두께[inner,peak,outer](px, 중심선 법선 방향). export_rig.py가 persona.json browHalfWidthFromArt!=false(기본)면 RigBrow.halfWidth로 내보낸다.", - "checkB_innerMinusPeakY": { - "browLeft": 12.55, - "browRight": 14.23 - }, - "checkD_symmetry": { - "innerYDiff": 3.01, - "peakYDiff": 4.69 - }, - "evidenceImage": "preview/v2/brow-centerline.jpg" - }, - "schemaVersion": "vignette.avatar.v3.layers.v1", - "persona": "P2", - "canvas": { - "w": 1005, - "h": 1566 - }, - "base": { - "front": "base/base-front.png", - "faceless": "base/base-faceless.png", - "facelessPadded": "base/base-faceless-padded.png", - "facelessSize": [ - 1005, - 1566 - ], - "padNote": "base-faceless.png 마지막 행을 0번 복제해 1005x1566(base-front.png 크기)으로 패딩한 것이 F다." - }, - "segmenter": { - "model": "selfie_multiclass_256x256.tflite (mediapipe ImageSegmenter, storage.googleapis.com)", - "categories": { - "0": "background", - "1": "hair", - "2": "bodySkin", - "3": "faceSkin", - "4": "clothes", - "5": "others" - }, - "categoryPixelCounts": { - "0": 565865, - "1": 185705, - "2": 68467, - "3": 220816, - "4": 532832, - "5": 145 - }, - "hairEdgeRefine": { - "dilatePx": 6, - "lumThreshold": 110, - "addedPixels": 6806 - } - }, - "chinLine": { - "landmarkIndex": 152, - "xy": [ - 496.44, - 950.72 - ], - "marginPx": 8, - "cutY": 958.72 - }, - "faceOval": { - "landmarkLoop": [ - 10, - 338, - 297, - 332, - 284, - 251, - 389, - 356, - 454, - 323, - 361, - 288, - 397, - 365, - 379, - 378, - 400, - 377, - 152, - 148, - 176, - 149, - 150, - 136, - 172, - 58, - 132, - 93, - 234, - 127, - 162, - 21, - 54, - 103, - 67, - 109 - ], - "scale": 1.04, - "sourceImage": "base/base-front.png" - }, - "layers": [ - { - "id": "body", - "file": "layers/body.png", - "source": "base-faceless-masked+regenerated-fill", - "alphaBBox": [ - 0, - 751, - 1005, - 1566 - ], - "opaquePixels": 600798, - "greenResidue": 0, - "fillTargetPixels": 20892, - "fillSourceRaw": "raw/body.png (rejected 재생성이 아니라 수용된 body.png의 크로마키 결과; head/hairFront와 달리 body 재생성은 승인됨)" - }, - { - "id": "head", - "file": "layers/head.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 170, - 95, - 827, - 961 - ], - "opaquePixels": 417526, - "greenResidue": 0 - }, - { - "id": "hairFront", - "file": "layers/hairFront.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 255, - 398, - 738, - 822 - ], - "opaquePixels": 19720, - "greenResidue": 0 - } - ], - "rejectedRawEdits": { - "body": { - "file": "raw/body.png", - "status": "accepted-as-fill-source", - "reason": "정렬 (0,0), 형태 변형 없음 — head_mask 채움 전용 소스로 재사용" - } - }, - "composite": { - "meanAbsDiff": { - "full": 1.461588396883187, - "face": 0.18478636311778845, - "hairOutline": 5.348185720428226 - }, - "target": { - "full": 3.0, - "pass": true - }, - "backgroundNote": "카테고리0(배경)은 종이결 텍스처 노이즈(표준편차 ~29/채널)를 포함해 평탄한 크림(#EEE5D3)과 자체적으로 평균절대차 ~7 차이가 난다." - }, - "layersV2": { - "chinBandFix": { - "targetRegionPixels": 20892, - "sourceBandHeight": 150, - "rowJitterRange": [ - -2.0, - 2.0 - ], - "rngSeed": 20260930 - }, - "hairEdgeFix": { - "bandOuterPx": 40, - "bandInnerPx": 6, - "lumSmoothstep": [ - 205.0, - 150.0 - ], - "minFragmentAreaPx": 60, - "head": { - "bandOuterPixels": 70076, - "bandInnerPixels": 21428, - "componentsBefore": 6, - "removed": 5, - "removedPixels": 16 - }, - "hairFront": { - "bandOuterPixels": 7840, - "bandInnerPixels": 11194, - "componentsBefore": 70, - "removed": 66, - "removedPixels": 427 - } - }, - "hairPaperGapFix": { - "regionDilatePx": 6, - "paperColorDistThreshold": 40.0, - "paperLumThreshold": 195.0, - "paperSatThreshold": 40.0, - "opaqueAlphaThreshold": 0.95, - "regionPixels": 195749, - "head": { - "targetPixels": 794, - "componentsBefore": 1, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - }, - "hairFront": { - "targetPixels": 0, - "componentsBefore": 4, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - } - }, - "jawInkFix": { - "mouthCenterY": 812.28, - "distThresholdPx": 14.0, - "mergePx": 3, - "aspectThreshold": 1.0, - "removedGroups": [], - "keptGroups": [ - { - "pixels": 53, - "bbox": [ - 304, - 813, - 315, - 828 - ], - "width": 11, - "height": 15, - "aspect": 0.733 - }, - { - "pixels": 83, - "bbox": [ - 684, - 813, - 695, - 824 - ], - "width": 11, - "height": 11, - "aspect": 1.0 - }, - { - "pixels": 2, - "bbox": [ - 690, - 844, - 691, - 846 - ], - "width": 1, - "height": 2, - "aspect": 0.5 - }, - { - "pixels": 2, - "bbox": [ - 689, - 853, - 690, - 855 - ], - "width": 1, - "height": 2, - "aspect": 0.5 - } - ], - "removedPixels": 0, - "strayFragmentCleanup": { - "componentsBefore": 4, - "removed": 0, - "removedPixels": 0 - } - }, - "metrics": { - "body": { - "haloPct": 0.044228217602830605 - }, - "head": { - "haloPct": 4.264659767952336, - "bgLeakAlphaMean": 2.8734067319145284e-05, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - }, - "hairFront": { - "haloPct": 0.0, - "bgLeakAlphaMean": 0.0, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - } - }, - "layers": [ - { - "id": "body", - "bbox": [ - 0, - 751, - 1005, - 1566 - ], - "opaque": 599752, - "greenResidue": 0 - }, - { - "id": "head", - "bbox": [ - 164, - 84, - 831, - 961 - ], - "opaque": 420250, - "greenResidue": 0 - }, - { - "id": "hairFront", - "bbox": [ - 240, - 398, - 738, - 858 - ], - "opaque": 24155, - "greenResidue": 0 - } - ], - "faceDetail": { - "browLandmarksSource": "manifest.landmarks.eyebrowLeft/Right", - "region": "face", - "faceInsetPx": 24.0, - "matchFacelessColor": true, - "matchFacelessColorDeltaRgb": { - "eyeLeft": [ - -15.66, - -13.97, - -11.51 - ], - "eyeRight": [ - -8.91, - -8.23, - -7.54 - ], - "browLeft": [ - -4.74, - -3.66, - -3.41 - ], - "browRight": [ - -2.21, - -1.58, - -1.88 - ], - "mouth": [ - -7.06, - -5.11, - -4.14 - ] - }, - "regions": { - "eyeLeft": [ - 315.9, - 507.4, - 461.9, - 665.5 - ], - "eyeRight": [ - 528.4, - 502.7, - 673.7, - 663.0 - ], - "mouth": [ - 372.4, - 743.4, - 617.3, - 935.7 - ], - "moles": [] - }, - "exclusionPixels": { - "eyeLeft": 12408, - "eyeRight": 11446, - "browLeft": 15459, - "browRight": 15182, - "mouth": 19731, - "union": 62281 - }, - "hairMaskPixels": 194350, - "bbox": [ - 274, - 405, - 723, - 955 - ], - "textureCloneStamp": { - "cloneTargetThreshold": 65.0, - "avoidThreshold": 110.0, - "lineOpeningIterations": 3, - "featherPx": 4.0, - "badTexturePixels": 173, - "preservedShadowPixels": 0, - "components": [ - { - "componentId": 1, - "pixels": 1, - "offset": [ - 100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 2, - "pixels": 19, - "offset": [ - 100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 3, - "pixels": 13, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 4, - "pixels": 9, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 5, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 6, - "pixels": 1, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 7, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 8, - "pixels": 8, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 9, - "pixels": 2, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 10, - "pixels": 1, - "offset": [ - -120, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 11, - "pixels": 1, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 12, - "pixels": 1, - "offset": [ - -120, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 13, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 14, - "pixels": 2, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 15, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 16, - "pixels": 1, - "offset": [ - -40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 17, - "pixels": 2, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 18, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 19, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 20, - "pixels": 6, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 21, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 22, - "pixels": 6, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 23, - "pixels": 1, - "offset": [ - 0, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 24, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 25, - "pixels": 4, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 26, - "pixels": 11, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 27, - "pixels": 2, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 28, - "pixels": 1, - "offset": [ - -40, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 29, - "pixels": 3, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 30, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 31, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 32, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 33, - "pixels": 8, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 34, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 35, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 36, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 37, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 38, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 39, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 40, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 41, - "pixels": 1, - "offset": [ - 0, - -80 - ], - "method": "clone-stamp" - }, - { - "componentId": 42, - "pixels": 9, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 43, - "pixels": 1, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 44, - "pixels": 1, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 45, - "pixels": 25, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 46, - "pixels": 1, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 47, - "pixels": 1, - "offset": [ - 0, - -80 - ], - "method": "clone-stamp" - }, - { - "componentId": 48, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 49, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 50, - "pixels": 1, - "offset": [ - 0, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 51, - "pixels": 7, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 52, - "pixels": 1, - "offset": [ - 40, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 53, - "pixels": 1, - "offset": [ - -40, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 54, - "pixels": 1, - "offset": [ - 0, - 120 - ], - "method": "clone-stamp" - } - ] - }, - "genericPatches": { - "forehead": [ - 496, - 412, - 544, - 460 - ], - "cheekLeft": [ - 321, - 643, - 369, - 691 - ], - "cheekRight": [ - 514, - 640, - 562, - 688 - ] - }, - "genericPatchBaseline": { - "highFreqEnergy": 9.550611294730496, - "lineEnergy": 3.7758152639145983 - }, - "checkA_meanAbsDiff": 0.5536637943489896, - "checkB_seamLumDiff": 0.9985878703074036, - "checkC_holeDarkResiduePct": 0.1944520925110132, - "checkC_violationPixels": 113, - "checkD_texturePreservation": { - "eyeLeft": { - "insideEnergy": 8.86627100371463, - "baselineEnergy": 9.550611294730496, - "ratio": 0.9283459173557354, - "pass": true - }, - "eyeRight": { - "insideEnergy": 7.971412240626346, - "baselineEnergy": 9.550611294730496, - "ratio": 0.8346494265790646, - "pass": true - }, - "browLeft": { - "insideEnergy": 11.665703959963366, - "baselineEnergy": 9.550611294730496, - "ratio": 1.2214614960196173, - "pass": true - }, - "browRight": { - "insideEnergy": 9.922083278546825, - "baselineEnergy": 9.550611294730496, - "ratio": 1.0388951002561782, - "pass": true - }, - "mouth": { - "insideEnergy": 8.190923228958187, - "baselineEnergy": 9.550611294730496, - "ratio": 0.8576333991812115, - "pass": true - } - }, - "checkD_allPass": true, - "checkE_ghostOutline": { - "eyeLeft": { - "bandLineEnergy": 7.5041240509892235, - "baselineLineEnergy": 3.7758152639145983, - "ratio": 1.9874182200347588, - "pass": false - }, - "eyeRight": { - "bandLineEnergy": 5.421789669498705, - "baselineLineEnergy": 3.7758152639145983, - "ratio": 1.4359255658810048, - "pass": true - }, - "browLeft": { - "bandLineEnergy": 6.336968296699441, - "baselineLineEnergy": 3.7758152639145983, - "ratio": 1.678304645161467, - "pass": false - }, - "browRight": { - "bandLineEnergy": 4.752161517472518, - "baselineLineEnergy": 3.7758152639145983, - "ratio": 1.2585789254280644, - "pass": true - }, - "mouth": { - "bandLineEnergy": 5.409947647889773, - "baselineLineEnergy": 3.7758152639145983, - "ratio": 1.4327892838382612, - "pass": true - } - }, - "checkE_allPass": false, - "checkDarkCircle": { - "eyeLeft": { - "compositeMean": 125.51828571428571, - "frontMean": 125.43542857142857, - "diff": 0.08285714285713652, - "pass": true - }, - "eyeRight": { - "compositeMean": 126.36457142857142, - "frontMean": 126.28990476190475, - "diff": 0.07466666666667265, - "pass": true - } - }, - "checkDarkCircle_allPass": true - }, - "paperGrain": { - "sourceImage": "base/base-front.png", - "sourceOrigin": [ - 739, - 10 - ], - "tileSize": 256, - "personMarginPx": 20, - "cleanFraction": 0.96636962890625, - "contaminatedPixelsFilled": 2204, - "chromaKeep": 0.3, - "targetLumRange": [ - 245.0, - 250.0 - ], - "lumShift": 22.814595540364564, - "finalMeanLum": 247.48806402723034, - "finalLumStd": 2.3873055618421484, - "seamContinuity": { - "leftRightDiff": 0.08759996609734344, - "topBottomDiff": 0.4901310520663742 - } - } - }, - "lipTexture": { - "designVersion": "A5(아랫입술 그늘 띠를 lip-shadow 조각으로 분리)", - "landmarks": { - "mouthCornerLeft": [ - 417.35, - 818.27 - ], - "mouthCornerRight": [ - 572.28, - 816.91 - ], - "upperLipTop": [ - 494.64, - 787.63 - ], - "lowerLipBottom": [ - 494.14, - 843.92 - ], - "mouthCenter": [ - 494.6075988560915, - 812.2768317461014 - ] - }, - "capBBox": [ - 413.81, - 780.77, - 575.82, - 850.77 - ], - "otsuThreshLabA": 135.0, - "diffThresh": 20.0, - "dilatePx": 10.0, - "alphaRampPx": 4.0, - "splitOverlapPx": 2.0, - "shadowBandPx": 18.0, - "shadowTopOverlapPx": 6.0, - "shadowSideRampPx": 6.0, - "shadowTopRampPx": 4.0, - "checks": { - "lip-upper": { - "regionPixelCount": 6559, - "fullOpacityPixelCount": 5652, - "rampPixelCount": 907, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 30, - 31, - 182, - 51 - ] - }, - "lip-lower": { - "regionPixelCount": 6659, - "fullOpacityPixelCount": 5889, - "rampPixelCount": 770, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 30, - 72, - 181, - 48 - ] - }, - "lip-shadow": { - "regionPixelCount": 3720, - "fullOpacityPixelCount": 2004, - "rampPixelCount": 1716, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 40, - 76, - 155, - 52 - ] - }, - "splitOverlapPixelCount": 360, - "splitOverlapBandOk": true, - "splitOverlapAlphaMaxDiff": 0, - "splitOverlapConsistentOk": true, - "splitCoverageMismatchPixelCount": 0, - "splitCheckOk": true, - "shadowLowerOverlapPixelCount": 2770, - "shadowLowerOverlapMaxDiff": 55.057347174163795, - "shadowLowerOverlapDiffGt1PixelCount": 153, - "shadowLowerOverlapOk": false - }, - "check3NeutralComposite": { - "fullOpacityMaxDiff": 0.0, - "rampMeanAbsDiff": 3.432, - "mouthBBoxMeanAbsDiff": 0.841 - }, - "pngFileSizes": { - "lip-upper": 23842, - "lip-lower": 21817, - "lip-shadow": 20971 - }, - "canvasOrigin": { - "upper": [ - 404, - 772 - ], - "lower": [ - 404, - 813 - ], - "shadow": [ - 414, - 817 - ] - }, - "evidenceImage": "preview/v2/lip-texture.jpg", - "check4WebpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jaw": { - "designVersion": "A5(결정문 §8.4 하관 띠 변형 — 턱 조각 원본)", - "noseTip": [ - 494.67, - 718.39 - ], - "chinTip": [ - 496.44, - 950.72 - ], - "yFormula": "noseTip.y - 50 - 10 .. chinTip.y + 40", - "noseToTopPx": 60.0, - "chinToBottomPx": 40.0, - "ovalOutsetPx": 18.0, - "faceOvalSource": "export_rig.detect_face_landmarks + compute_face_oval(base-front.png, 결정적)", - "bboxCanvas": [ - 246, - 658, - 752, - 991 - ], - "naiveXRangeForComparison": [ - 246, - 752 - ], - "checks": { - "head": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 343465 - }, - "detail": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 388506 - } - }, - "evidenceImage": "preview/v2/jaw-pieces.jpg", - "check1VsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.795, - "maxAbsDiffPremultipliedRgb": 36.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.83, - "maxAbsDiffPremultipliedRgb": 35.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 15864, - "lip-lower": 14532, - "lip-shadow": 14592 - }, - "totalBytes": 44988, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 26526, - "jaw-detail": 27760 - }, - "totalBytes": 54286, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.795, - "maxAbsDiffPremultipliedRgb": 36.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.83, - "maxAbsDiffPremultipliedRgb": 35.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "pipelineRun": { - "persona": "P2", - "timestamp": "2026-10-01T21:59:56", - "completed": true, - "stages": [ - { - "stage": "brow_sprite", - "ok": true, - "elapsedSec": 3.05 - }, - { - "stage": "paper_grain", - "ok": true, - "elapsedSec": 2.92 - }, - { - "stage": "lip_texture", - "ok": true, - "elapsedSec": 2.2 - }, - { - "stage": "jaw_pieces", - "ok": true, - "elapsedSec": 2.17 - }, - { - "stage": "export_rig", - "ok": true, - "elapsedSec": 17.99 - }, - { - "stage": "final_previews", - "ok": true, - "elapsedSec": 19.72 - } - ] - }, - "browSprite": { - "enabled": true, - "bandExtraHalfHeightPx": 8.0, - "bandEndExtendPx": 6.0, - "diffThresh": 25.0, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "hairSource": "layers/v2/hairFront.png alpha(게시되는 레이어 그 자체, 분할 카테고리 아님)", - "hairAlphaThresh": 128.0, - "hairDilateIterations": 1, - "rampPx": 1.5, - "inkLumMethod": "median(ink-classified pixels in band, region after hair removal)", - "minAlphaForUnmix": 0.05, - "hairMaskPixelCount": 26909, - "left": { - "bboxCanvas": [ - 307, - 500, - 158, - 55 - ], - "halfHeightPx": 24.459, - "otsuThresh": 96.0, - "lSkin": 165.0, - "lInk": 41.333, - "regionPixelCount": 3422, - "hairOverlapPixelCount": 335, - "neutralCompositeMeanAbsDiffAlphaRegion": 3.292, - "pngFileSizeBytes": 11795 - }, - "right": { - "bboxCanvas": [ - 515, - 492, - 176, - 57 - ], - "halfHeightPx": 23.261, - "otsuThresh": 109.0, - "lSkin": 172.0, - "lInk": 54.0, - "regionPixelCount": 4122, - "hairOverlapPixelCount": 47, - "neutralCompositeMeanAbsDiffAlphaRegion": 1.755, - "pngFileSizeBytes": 14396 - }, - "canvasOrigin": { - "left": [ - 307.0, - 500.0 - ], - "right": [ - 515.0, - 492.0 - ] - }, - "evidenceImage": "preview/v2/brow-sprite.jpg", - "checkWebpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "eyeLineScale": { - "enabled": true, - "method": "눈 폭 가운데 60% 열마다 upperLidTop.y-12 ~ 홍채중심y 사이 첫 연속 어두운(lum<80) 구간 길이의 중앙값", - "windowAbovePx": 12.0, - "darkLumThresh": 80.0, - "columnMiddleFrac": 0.6, - "p1BaselinePx": 2.0, - "lineScaleClampRange": [ - 1.0, - 2.5 - ], - "left": { - "measuredPx": 19.0, - "nColumns": 56, - "ratio": 9.5, - "lineScale": 2.5 - }, - "right": { - "measuredPx": 19.0, - "nColumns": 56, - "ratio": 9.5, - "lineScale": 2.5 - } - }, - "iris": { - "method": "가로: 중심 y 부근 상하 각 4행에서 중심 x를 포함하는 연속 어두운(lum<100) 구간의 중앙값으로 반지름·중심x. 세로: 중심 x 부근 좌우 각 4열에서 중심 y를 포함하는 연속 어두운 구간의 아래쪽 끝 중앙값에서 가로 반지름을 빼 중심y(세로축은 윗눈꺼풀 잉크와 홍채가 같은 톤으로 이어져 위쪽 경계를 가를 수 없다).", - "darkLumThresh": 100.0, - "scanBandPx": 4, - "left": { - "measuredCenter": [ - 391.0, - 576.0 - ], - "measuredRadius": 20.0, - "rigCenter": [ - 390.36, - 578.05 - ], - "rigRadius": 20.26, - "nRowsHorizontal": 9, - "nColsVertical": 9 - }, - "right": { - "measuredCenter": [ - 598.5, - 573.5 - ], - "measuredRadius": 20.5, - "rigCenter": [ - 598.2, - 575.68 - ], - "rigRadius": 20.48, - "nRowsHorizontal": 9, - "nColsVertical": 9 - } - }, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 8120, - "brow-right": 10126 - }, - "totalBytes": 18246, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p2-linocut/persona.json b/docs/avatar-art/p2-linocut/persona.json deleted file mode 100644 index d3a7d23..0000000 --- a/docs/avatar-art/p2-linocut/persona.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "code": "P2", - "publicSlug": "p2", - "rigFileName": "p2Rig.ts", - "rigExportName": "P2_LINOCUT_RIG", - "styleFrame": "../art-direction-v3/p1/r2-b-linocut.png", - "paletteSamples": { - "ink": { - "box": [330, 150, 670, 310], - "lumThreshold": 55 - }, - "motifPetalBoxes": [ - { "label": "sun", "kind": "ochre", "box": [1230, 10, 1536, 210] }, - { "label": "tulip", "kind": "ochre", "box": [1030, 280, 1170, 560] } - ], - "motifLeafBoxes": [ - { "label": "cloudLeft", "kind": "blue", "box": [20, 20, 380, 190] }, - { "label": "raincloud", "kind": "blue", "box": [520, 10, 930, 230] }, - { "label": "wiltedFlowerLeaf", "kind": "blue", "box": [520, 280, 650, 580] }, - { "label": "closedBudLeaf", "kind": "blue", "box": [10, 290, 110, 570] } - ] - }, - "faceDetail": { - "moles": [], - "region": "face", - "faceInsetPx": 24, - "matchFacelessColor": true - }, - "pivots": { - "neck": [496.44, 1021.17], - "body": [502, 1566], - "face": [492.0, 658.93] - }, - "crops": { - "bustYOffset": 40 - } -} diff --git a/docs/avatar-art/p2-linocut/preview/qa/face-art-vs-render.jpg b/docs/avatar-art/p2-linocut/preview/qa/face-art-vs-render.jpg deleted file mode 100644 index 7ce6f70..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/qa/face-art-vs-render.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/qa/face-zoom.jpg b/docs/avatar-art/p2-linocut/preview/qa/face-zoom.jpg deleted file mode 100644 index f1b2b62..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/qa/face-zoom.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/qa/head-halo-check.jpg b/docs/avatar-art/p2-linocut/preview/qa/head-halo-check.jpg deleted file mode 100644 index e8acae1..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/qa/head-halo-check.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/qa/neutral-vs-art.jpg b/docs/avatar-art/p2-linocut/preview/qa/neutral-vs-art.jpg deleted file mode 100644 index d64d431..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/qa/neutral-vs-art.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/qa/normalized.jpg b/docs/avatar-art/p2-linocut/preview/qa/normalized.jpg deleted file mode 100644 index 0c49d6b..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/qa/normalized.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/qa/sheet-expr-bust.jpg b/docs/avatar-art/p2-linocut/preview/qa/sheet-expr-bust.jpg deleted file mode 100644 index adb801b..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/qa/sheet-expr-bust.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/qa/sheet-expr-face.jpg b/docs/avatar-art/p2-linocut/preview/qa/sheet-expr-face.jpg deleted file mode 100644 index e4c5bbe..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/qa/sheet-expr-face.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/qa/sheet-viseme.jpg b/docs/avatar-art/p2-linocut/preview/qa/sheet-viseme.jpg deleted file mode 100644 index 98d4419..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/qa/sheet-viseme.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/qa/viseme-timeline.json b/docs/avatar-art/p2-linocut/preview/qa/viseme-timeline.json deleted file mode 100644 index 5f40aac..0000000 --- a/docs/avatar-art/p2-linocut/preview/qa/viseme-timeline.json +++ /dev/null @@ -1,287 +0,0 @@ -[ - { - "t": 0, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00000.png" - }, - { - "t": 50, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00050.png" - }, - { - "t": 100, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00100.png" - }, - { - "t": 150, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00150.png" - }, - { - "t": 200, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00200.png" - }, - { - "t": 250, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00250.png" - }, - { - "t": 300, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00300.png" - }, - { - "t": 350, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00350.png" - }, - { - "t": 400, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00400.png" - }, - { - "t": 450, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00450.png" - }, - { - "t": 500, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00500.png" - }, - { - "t": 550, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00550.png" - }, - { - "t": 600, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00600.png" - }, - { - "t": 650, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00650.png" - }, - { - "t": 700, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00700.png" - }, - { - "t": 750, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00750.png" - }, - { - "t": 800, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00800.png" - }, - { - "t": 850, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00850.png" - }, - { - "t": 900, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00900.png" - }, - { - "t": 950, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t00950.png" - }, - { - "t": 1000, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01000.png" - }, - { - "t": 1050, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01050.png" - }, - { - "t": 1100, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01100.png" - }, - { - "t": 1150, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01150.png" - }, - { - "t": 1200, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01200.png" - }, - { - "t": 1250, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01250.png" - }, - { - "t": 1300, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01300.png" - }, - { - "t": 1350, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01350.png" - }, - { - "t": 1400, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01400.png" - }, - { - "t": 1450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01450.png" - }, - { - "t": 1500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01500.png" - }, - { - "t": 1550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01550.png" - }, - { - "t": 1600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01600.png" - }, - { - "t": 1650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01650.png" - }, - { - "t": 1700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01700.png" - }, - { - "t": 1750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01750.png" - }, - { - "t": 1800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01800.png" - }, - { - "t": 1850, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01850.png" - }, - { - "t": 1900, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01900.png" - }, - { - "t": 1950, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t01950.png" - }, - { - "t": 2000, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02000.png" - }, - { - "t": 2050, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02050.png" - }, - { - "t": 2100, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02100.png" - }, - { - "t": 2150, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02150.png" - }, - { - "t": 2200, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02200.png" - }, - { - "t": 2250, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02250.png" - }, - { - "t": 2300, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02300.png" - }, - { - "t": 2350, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02350.png" - }, - { - "t": 2400, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02400.png" - }, - { - "t": 2450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02450.png" - }, - { - "t": 2500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02500.png" - }, - { - "t": 2550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02550.png" - }, - { - "t": 2600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02600.png" - }, - { - "t": 2650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02650.png" - }, - { - "t": 2700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02700.png" - }, - { - "t": 2750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02750.png" - }, - { - "t": 2800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p2-linocut\\preview\\qa\\frames\\viseme-scan-t02800.png" - } -] \ No newline at end of file diff --git a/docs/avatar-art/p2-linocut/preview/v2/brow-centerline.jpg b/docs/avatar-art/p2-linocut/preview/v2/brow-centerline.jpg deleted file mode 100644 index 93c4bfd..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/v2/brow-centerline.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/v2/brow-sprite.jpg b/docs/avatar-art/p2-linocut/preview/v2/brow-sprite.jpg deleted file mode 100644 index dd14fef..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/v2/brow-sprite.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/v2/export-rig-report.json b/docs/avatar-art/p2-linocut/preview/v2/export-rig-report.json deleted file mode 100644 index c1af573..0000000 --- a/docs/avatar-art/p2-linocut/preview/v2/export-rig-report.json +++ /dev/null @@ -1,676 +0,0 @@ -{ - "faceOvalBBox": [ - 257.9, - 398.4, - 736.0, - 961.4 - ], - "cropsFace": [ - 181.6, - 364.6, - 630.6, - 630.6 - ], - "mouthCenter": [ - 494.6, - 812.3 - ], - "palette": { - "ink": "#1E1E1D", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#9D7253", - "lipLower": "#A87857", - "mouthLine": "#242220", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "paletteSamples": { - "eyeLeft": { - "sclera": "#D0A275", - "iris": "#2E2823", - "irisRing": "#302B23", - "n": { - "sclera": 942, - "iris": 185, - "irisRing": 377 - } - }, - "eyeRight": { - "sclera": "#CAA075", - "iris": "#2E2923", - "irisRing": "#372D26", - "n": { - "sclera": 968, - "iris": 191, - "irisRing": 384 - } - }, - "scleraIrisIrisRingFixedOverride": { - "reason": "persona.json paletteSamples.eyeOverride(고정 설계값)", - "measured": { - "sclera": "#CDA175", - "iris": "#2E2823", - "irisRing": "#342C24" - }, - "fixed": { - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F" - } - }, - "ink": { - "box": [ - 330, - 150, - 670, - 310 - ], - "lumThreshold": 55.0, - "n": 45379, - "hex": "#1E1E1D" - }, - "lipUpper": { - "box": [ - 412, - 784, - 577, - 810 - ], - "hex": "#9D7253" - }, - "lipLower": { - "box": [ - 412, - 814, - 577, - 846 - ], - "hex": "#A87857" - }, - "mouthLine": { - "box": [ - 432, - 810, - 557, - 815 - ], - "hex": "#242220" - }, - "motifPetal": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "sun": { - "box": [ - 1230, - 10, - 1536, - 210 - ], - "n": 20570, - "hex": "#D3A661" - }, - "tulip": { - "box": [ - 1030, - 280, - 1170, - 560 - ], - "n": 4314, - "hex": "#CCA063" - } - }, - "hex": "#D0A362" - }, - "motifLeaf": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "cloudLeft": { - "box": [ - 20, - 20, - 380, - 190 - ], - "n": 21312, - "hex": "#636D76" - }, - "raincloud": { - "box": [ - 520, - 10, - 930, - 230 - ], - "n": 27607, - "hex": "#586B76" - }, - "wiltedFlowerLeaf": { - "box": [ - 520, - 280, - 650, - 580 - ], - "n": 5818, - "hex": "#495961" - }, - "closedBudLeaf": { - "box": [ - 10, - 290, - 110, - 570 - ], - "n": 6589, - "hex": "#495961" - } - }, - "hex": "#53626C" - } - }, - "publish": { - "fileSizes": { - "body": 125460, - "head": 89170, - "hair-front": 16984, - "face-detail": 47270, - "paper-grain": 1034 - }, - "encodeMeta": { - "quality": 80, - "scale": 1.0, - "totalBytes": 279918 - }, - "totalBytes": 279918 - }, - "grain": { - "href": "/avatar/v3/p2/paper-grain.webp", - "size": 256 - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 15864, - "lip-lower": 14532, - "lip-shadow": 14592 - }, - "totalBytes": 44988, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 26526, - "jaw-detail": 27760 - }, - "totalBytes": 54286, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.795, - "maxAbsDiffPremultipliedRgb": 36.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.83, - "maxAbsDiffPremultipliedRgb": 35.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 8120, - "brow-right": 10126 - }, - "totalBytes": 18246, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "rig": { - "schemaVersion": "vignette.avatar.v3.rig.v1", - "persona": "P2", - "canvas": { - "w": 1005, - "h": 1566 - }, - "layers": { - "body": { - "href": "/avatar/v3/p2/body.webp", - "x": 0, - "y": 751, - "w": 1005, - "h": 815 - }, - "head": { - "href": "/avatar/v3/p2/head.webp", - "x": 164, - "y": 84, - "w": 667, - "h": 877 - }, - "hairFront": { - "href": "/avatar/v3/p2/hair-front.webp", - "x": 240, - "y": 398, - "w": 498, - "h": 460 - }, - "faceDetail": { - "href": "/avatar/v3/p2/face-detail.webp", - "x": 274, - "y": 405, - "w": 449, - "h": 550 - } - }, - "grain": { - "href": "/avatar/v3/p2/paper-grain.webp", - "size": 256 - }, - "lipTexture": { - "upper": { - "href": "/avatar/v3/p2/lip-upper.webp", - "x": 404, - "y": 772, - "w": 182, - "h": 51 - }, - "lower": { - "href": "/avatar/v3/p2/lip-lower.webp", - "x": 404, - "y": 813, - "w": 181, - "h": 48 - }, - "shadow": { - "href": "/avatar/v3/p2/lip-shadow.webp", - "x": 414, - "y": 817, - "w": 155, - "h": 52 - } - }, - "jaw": { - "head": { - "href": "/avatar/v3/p2/jaw-head.webp", - "x": 246, - "y": 658, - "w": 506, - "h": 333 - }, - "detail": { - "href": "/avatar/v3/p2/jaw-detail.webp", - "x": 246, - "y": 658, - "w": 506, - "h": 333 - } - }, - "motif": null, - "pivots": { - "neck": [ - 496.44, - 1021.17 - ], - "body": [ - 502, - 1566 - ], - "face": [ - 492.0, - 658.93 - ] - }, - "crops": { - "portrait": [ - 0, - 0, - 1005, - 1566 - ], - "bust": [ - 0.0, - 40.0, - 1005.0, - 1005.0 - ], - "face": [ - 181.6128492420554, - 364.57844417896274, - 630.602422984314, - 630.602422984314 - ] - }, - "faceOval": [ - [ - 492.2, - 398.4 - ], - [ - 558.0, - 399.3 - ], - [ - 614.2, - 407.7 - ], - [ - 665.7, - 426.4 - ], - [ - 699.0, - 456.1 - ], - [ - 719.9, - 494.6 - ], - [ - 731.3, - 536.0 - ], - [ - 736.0, - 586.9 - ], - [ - 734.6, - 635.4 - ], - [ - 730.5, - 685.8 - ], - [ - 721.5, - 739.0 - ], - [ - 704.8, - 795.1 - ], - [ - 682.2, - 840.4 - ], - [ - 657.5, - 873.5 - ], - [ - 627.1, - 903.3 - ], - [ - 601.0, - 924.5 - ], - [ - 572.8, - 943.3 - ], - [ - 539.3, - 957.3 - ], - [ - 496.4, - 961.4 - ], - [ - 453.8, - 957.3 - ], - [ - 421.0, - 943.3 - ], - [ - 393.2, - 924.7 - ], - [ - 367.4, - 904.2 - ], - [ - 338.1, - 874.9 - ], - [ - 315.0, - 842.0 - ], - [ - 293.7, - 797.6 - ], - [ - 277.1, - 741.8 - ], - [ - 267.0, - 688.9 - ], - [ - 261.6, - 639.6 - ], - [ - 257.9, - 591.7 - ], - [ - 260.7, - 541.3 - ], - [ - 270.6, - 499.5 - ], - [ - 290.1, - 460.8 - ], - [ - 321.9, - 430.6 - ], - [ - 371.3, - 410.5 - ], - [ - 426.6, - 401.0 - ] - ], - "landmarks": { - "eyeLeft": { - "inner": [ - 436.9, - 589.7 - ], - "outer": [ - 345.9, - 578.6 - ], - "upperLidTop": [ - 382.5, - 563.5 - ], - "lowerLidBottom": [ - 387.0, - 595.5 - ], - "iris": { - "center": [ - 391.0, - 576.0 - ], - "radius": 20.0 - }, - "lineScale": 2.5 - }, - "eyeRight": { - "inner": [ - 553.4, - 588.3 - ], - "outer": [ - 643.7, - 575.1 - ], - "upperLidTop": [ - 606.6, - 560.8 - ], - "lowerLidBottom": [ - 603.5, - 593.0 - ], - "iris": { - "center": [ - 598.5, - 573.5 - ], - "radius": 20.5 - }, - "lineScale": 2.5 - }, - "browLeft": { - "inner": [ - 459.2, - 529.9 - ], - "peak": [ - 359.4, - 517.4 - ], - "outer": [ - 307.8, - 526.2 - ], - "halfWidth": [ - 12.85, - 16.46, - 2.41 - ], - "sprite": { - "href": "/avatar/v3/p2/brow-left.webp", - "x": 307.0, - "y": 500.0, - "w": 158, - "h": 55 - } - }, - "browRight": { - "inner": [ - 522.2, - 526.9 - ], - "peak": [ - 630.8, - 512.7 - ], - "outer": [ - 686.8, - 520.9 - ], - "halfWidth": [ - 15.26, - 12.23, - 6.8 - ], - "sprite": { - "href": "/avatar/v3/p2/brow-right.webp", - "x": 515.0, - "y": 492.0, - "w": 176, - "h": 57 - } - }, - "noseTip": [ - 494.7, - 718.4 - ], - "mouthCornerLeft": [ - 417.4, - 818.3 - ], - "mouthCornerRight": [ - 572.3, - 816.9 - ], - "upperLipTop": [ - 494.6, - 787.6 - ], - "lowerLipBottom": [ - 494.1, - 843.9 - ], - "mouthCenter": [ - 494.6, - 812.3 - ], - "chinTip": [ - 496.4, - 950.7 - ] - }, - "palette": { - "ink": "#1E1E1D", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#9D7253", - "lipLower": "#A87857", - "mouthLine": "#242220", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "backdrop": { - "cognitive": "#ECE3D1", - "positive": "#F1DEC2", - "negative": "#DCE0E2", - "defensive": "#E6DAD3", - "energy": "#E2E0D0" - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p2-linocut/preview/v2/ghost-check.jpg b/docs/avatar-art/p2-linocut/preview/v2/ghost-check.jpg deleted file mode 100644 index 91e22c5..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/v2/ghost-check.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/v2/holes-texture.jpg b/docs/avatar-art/p2-linocut/preview/v2/holes-texture.jpg deleted file mode 100644 index 496029f..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/v2/holes-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/v2/jaw-pieces.jpg b/docs/avatar-art/p2-linocut/preview/v2/jaw-pieces.jpg deleted file mode 100644 index 96490e4..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/v2/jaw-pieces.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/preview/v2/lip-texture.jpg b/docs/avatar-art/p2-linocut/preview/v2/lip-texture.jpg deleted file mode 100644 index a874738..0000000 Binary files a/docs/avatar-art/p2-linocut/preview/v2/lip-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p2-linocut/prompts/base-faceless.txt b/docs/avatar-art/p2-linocut/prompts/base-faceless.txt deleted file mode 100644 index 636f1c8..0000000 --- a/docs/avatar-art/p2-linocut/prompts/base-faceless.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as faceless.png: - -Redraw exactly the same man, same frontal pose, same framing and same linocut relief-print style as the reference, but leave the face blank: remove the eyes, eyebrows and mouth completely and fill those areas with plain skin in the same flat skin color, keeping the nose, the face outline, the ears, the hair, the neck, the light blue shirt collar, the dark grey V-neck sweater and the lanyard cord exactly as they are. Do not move, resize or redraw anything else. Plain flat cream background, nothing else, no text. diff --git a/docs/avatar-art/p2-linocut/prompts/body.txt b/docs/avatar-art/p2-linocut/prompts/body.txt deleted file mode 100644 index 245e013..0000000 --- a/docs/avatar-art/p2-linocut/prompts/body.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as body.png: - -Keep only the neck, the light blue shirt, the dark grey V-neck sweater and the lanyard cord exactly as they are in the reference: same shapes, same position, same linocut relief-print style, same ink colors and texture. Remove the head, the face, the ears and all of the hair completely. Where the hair or the chin previously covered the neck or the clothes, continue them naturally in the same style so there are no holes. Place the result on a plain flat pure green background (#00ff00) with nothing else, no text. diff --git a/docs/avatar-art/p2-linocut/raw/body.png b/docs/avatar-art/p2-linocut/raw/body.png deleted file mode 100644 index 783325b..0000000 Binary files a/docs/avatar-art/p2-linocut/raw/body.png and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/base/base-faceless.png b/docs/avatar-art/p3-linocut/base/base-faceless.png deleted file mode 100644 index bb450e8..0000000 Binary files a/docs/avatar-art/p3-linocut/base/base-faceless.png and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/base/base-front.png b/docs/avatar-art/p3-linocut/base/base-front.png deleted file mode 100644 index e63af36..0000000 Binary files a/docs/avatar-art/p3-linocut/base/base-front.png and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/base/edits/aligned-body-1.json b/docs/avatar-art/p3-linocut/base/edits/aligned-body-1.json deleted file mode 100644 index a61595d..0000000 --- a/docs/avatar-art/p3-linocut/base/edits/aligned-body-1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\body-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\aligned-body-1.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.98505, - "transform": { - "scaleSqrtDet": 1.00052, - "rotationDeg": 0.006, - "translateX": -0.405, - "translateY": -2.096, - "matrix": [ - [ - 0.999904, - 0.000487, - -0.404834 - ], - [ - 0.000102, - 1.001144, - -2.096108 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 502186, - "chamferInMask": { - "median": 0.0, - "p90": 1.0, - "nSamples": 103878 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p3-linocut/base/edits/aligned-body-2.json b/docs/avatar-art/p3-linocut/base/edits/aligned-body-2.json deleted file mode 100644 index a620dda..0000000 --- a/docs/avatar-art/p3-linocut/base/edits/aligned-body-2.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\body-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\aligned-body-2.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.91082, - "transform": { - "scaleSqrtDet": 1.01057, - "rotationDeg": 0.161, - "translateX": -5.125, - "translateY": -34.475, - "matrix": [ - [ - 1.000922, - 0.003842, - -5.125106 - ], - [ - 0.00281, - 1.020314, - -34.474659 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 506002, - "chamferInMask": { - "median": 1.0, - "p90": 2.0, - "nSamples": 104082 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p3-linocut/base/edits/aligned-faceless-1.json b/docs/avatar-art/p3-linocut/base/edits/aligned-faceless-1.json deleted file mode 100644 index d640398..0000000 --- a/docs/avatar-art/p3-linocut/base/edits/aligned-faceless-1.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\faceless-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\aligned-faceless-1.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.99268, - "transform": { - "scaleSqrtDet": 1.00001, - "rotationDeg": 0.001, - "translateX": 0.1, - "translateY": -0.799, - "matrix": [ - [ - 0.999892, - -7.7e-05, - 0.100431 - ], - [ - 1.9e-05, - 1.000137, - -0.79903 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 886543, - "chamferInMask": { - "median": 0.0, - "p90": 1.0, - "nSamples": 171406 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 6518 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 378, - 445, - 527, - 530 - ], - "refInkRatio": 0.2204, - "editedInkRatio": 0.0136 - }, - "eyeB": { - "box": [ - 605, - 425, - 753, - 511 - ], - "refInkRatio": 0.1909, - "editedInkRatio": 0.0001 - }, - "browA": { - "box": [ - 331, - 367, - 536, - 473 - ], - "refInkRatio": 0.3156, - "editedInkRatio": 0.2181 - }, - "browB": { - "box": [ - 580, - 348, - 791, - 443 - ], - "refInkRatio": 0.1773, - "editedInkRatio": 0.0944 - }, - "mouth": { - "box": [ - 470, - 662, - 692, - 785 - ], - "refInkRatio": 0.0531, - "editedInkRatio": 0.0001 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 378, - 445, - 527, - 530 - ], - "eyeB": [ - 605, - 425, - 753, - 511 - ], - "browA": [ - 331, - 367, - 536, - 473 - ], - "browB": [ - 580, - 348, - 791, - 443 - ], - "mouth": [ - 470, - 662, - 692, - 785 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p3-linocut/base/edits/aligned-faceless-2.json b/docs/avatar-art/p3-linocut/base/edits/aligned-faceless-2.json deleted file mode 100644 index 965856f..0000000 --- a/docs/avatar-art/p3-linocut/base/edits/aligned-faceless-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\faceless-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\aligned-faceless-2.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.99256, - "transform": { - "scaleSqrtDet": 0.99996, - "rotationDeg": 0.001, - "translateX": -0.216, - "translateY": -0.467, - "matrix": [ - [ - 1.000085, - -2.1e-05, - -0.216023 - ], - [ - 2.1e-05, - 0.999836, - -0.467444 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 886543, - "chamferInMask": { - "median": 0.0, - "p90": 1.0, - "nSamples": 170619 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 6490 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 378, - 445, - 527, - 530 - ], - "refInkRatio": 0.2204, - "editedInkRatio": 0.0136 - }, - "eyeB": { - "box": [ - 605, - 425, - 753, - 511 - ], - "refInkRatio": 0.1909, - "editedInkRatio": 0.0 - }, - "browA": { - "box": [ - 331, - 367, - 536, - 473 - ], - "refInkRatio": 0.3156, - "editedInkRatio": 0.2181 - }, - "browB": { - "box": [ - 580, - 348, - 791, - 443 - ], - "refInkRatio": 0.1773, - "editedInkRatio": 0.0956 - }, - "mouth": { - "box": [ - 470, - 662, - 692, - 785 - ], - "refInkRatio": 0.0531, - "editedInkRatio": 0.0001 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 378, - 445, - 527, - 530 - ], - "eyeB": [ - 605, - 425, - 753, - 511 - ], - "browA": [ - 331, - 367, - 536, - 473 - ], - "browB": [ - 580, - 348, - 791, - 443 - ], - "mouth": [ - 470, - 662, - 692, - 785 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p3-linocut/base/edits/sheet.jpg b/docs/avatar-art/p3-linocut/base/edits/sheet.jpg deleted file mode 100644 index 5cf87cc..0000000 Binary files a/docs/avatar-art/p3-linocut/base/edits/sheet.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/base/normalize.json b/docs/avatar-art/p3-linocut/base/normalize.json deleted file mode 100644 index 600851d..0000000 --- a/docs/avatar-art/p3-linocut/base/normalize.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "canvas": { - "w": 1005, - "h": 1566 - }, - "reference": { - "image": "docs\\avatar-art\\p1-linocut\\base\\base-front.png", - "eyesCenter": [ - 492.69, - 584.33 - ], - "chin": [ - 500.61, - 919.55 - ], - "eyeDistance": 215.427, - "eyeChinDistance": 335.308 - }, - "input": { - "front": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\source-front.png", - "faceless": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\aligned-faceless-1.png", - "body": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\base\\edits\\aligned-body-1.png", - "frontSize": [ - 1145, - 1374 - ], - "eyesCenter": [ - 565.52, - 484.33 - ], - "chin": [ - 590.16, - 868.28 - ], - "eyeDistance": 226.984, - "eyeChinDistance": 384.738 - }, - "transform": { - "sHead": 0.910305, - "sWidth": 0.877729, - "s": 0.910305, - "rotationDeg": 0.0, - "offsetXNatural": -22.104, - "offsetXClampRange": [ - -37.299, - 0.0 - ], - "offsetX": -22.104, - "offsetY": 143.442 - }, - "coveredRange": { - "x": [ - 0.0, - 1005 - ], - "y": [ - 143.4, - 1394.2 - ] - }, - "inputSha256": { - "front": "00b8d9c3f930dd94798fa41e66fac08be58d9c493ae66284cef460e690f25bd8", - "faceless": "ce66bd0c2ecfcc09f853bba77197cace6e74da03035316519dd16010c928c8ff", - "body": "fbaba0b506a3b0fe868a63caa3f36fd2a1947f2965a0b2c6508f3152d2e8b5ba" - } -} \ No newline at end of file diff --git a/docs/avatar-art/p3-linocut/manifest.json b/docs/avatar-art/p3-linocut/manifest.json deleted file mode 100644 index 8972956..0000000 --- a/docs/avatar-art/p3-linocut/manifest.json +++ /dev/null @@ -1,1258 +0,0 @@ -{ - "landmarks": { - "coordSystem": "screen (image pixel: x=0 좌측, y=0 상단; left=작은 x, right=큰 x; 인물 해부학적 좌우 아님)", - "eyeLeft": { - "innerCorner": [ - 435.06, - 593.75 - ], - "outerCorner": [ - 346.29, - 589.44 - ], - "upperLidTop": [ - 382.51, - 570.06 - ], - "lowerLidBottom": [ - 386.71, - 602.25 - ], - "iris": { - "center": [ - 394.43, - 584.74 - ], - "radius": 20.58 - } - }, - "eyeRight": { - "innerCorner": [ - 552.12, - 583.64 - ], - "outerCorner": [ - 640.75, - 564.73 - ], - "upperLidTop": [ - 600.78, - 552.08 - ], - "lowerLidBottom": [ - 601.04, - 583.93 - ], - "iris": { - "center": [ - 596.13, - 567.87 - ], - "radius": 20.39 - } - }, - "eyebrowLeft": { - "inner": [ - 437.98, - 513.19 - ], - "peak": [ - 354.9, - 519.79 - ], - "outer": [ - 312.02, - 530.82 - ] - }, - "eyebrowRight": { - "inner": [ - 521.26, - 505.36 - ], - "peak": [ - 609.3, - 496.5 - ], - "outer": [ - 654.74, - 500.99 - ] - }, - "noseTip": [ - 499.58, - 704.43 - ], - "mouthCornerLeft": [ - 432.63, - 803.41 - ], - "mouthCornerRight": [ - 583.64, - 792.2 - ], - "upperLipTopCenter": [ - 505.16, - 769.79 - ], - "lowerLipBottomCenter": [ - 509.44, - 832.35 - ], - "chinTip": [ - 518.27, - 935.21 - ], - "faceWidthAtEyeLevelLeft": [ - 271.58, - 658.08 - ], - "faceWidthAtEyeLevelRight": [ - 727.33, - 621.97 - ] - }, - "landmarkDetector": "mediapipe FaceLandmarker (tasks) 1.0.1, model=face_landmarker(float16, v1)", - "pipelineRun": { - "persona": "P3", - "timestamp": "2026-10-01T22:00:44", - "completed": true, - "stages": [ - { - "stage": "brow_sprite", - "ok": true, - "elapsedSec": 2.67 - }, - { - "stage": "paper_grain", - "ok": true, - "elapsedSec": 2.44 - }, - { - "stage": "lip_texture", - "ok": true, - "elapsedSec": 2.24 - }, - { - "stage": "jaw_pieces", - "ok": true, - "elapsedSec": 5.76 - }, - { - "stage": "export_rig", - "ok": true, - "elapsedSec": 15.14 - }, - { - "stage": "final_previews", - "ok": true, - "elapsedSec": 19.25 - } - ] - }, - "schemaVersion": "vignette.avatar.v3.layers.v1", - "persona": "P3", - "canvas": { - "w": 1005, - "h": 1566 - }, - "base": { - "front": "base/base-front.png", - "faceless": "base/base-faceless.png", - "facelessPadded": "base/base-faceless-padded.png", - "facelessSize": [ - 1005, - 1566 - ], - "padNote": "base-faceless.png 마지막 행을 0번 복제해 1005x1566(base-front.png 크기)으로 패딩한 것이 F다." - }, - "segmenter": { - "model": "selfie_multiclass_256x256.tflite (mediapipe ImageSegmenter, storage.googleapis.com)", - "categories": { - "0": "background", - "1": "hair", - "2": "bodySkin", - "3": "faceSkin", - "4": "clothes", - "5": "others" - }, - "categoryPixelCounts": { - "0": 559937, - "1": 207575, - "2": 71269, - "3": 219079, - "4": 515826, - "5": 144 - }, - "hairEdgeRefine": { - "dilatePx": 6, - "lumThreshold": 110, - "addedPixels": 10285 - } - }, - "chinLine": { - "landmarkIndex": 152, - "xy": [ - 518.27, - 935.21 - ], - "marginPx": 8, - "cutY": 943.21 - }, - "faceOval": { - "landmarkLoop": [ - 10, - 338, - 297, - 332, - 284, - 251, - 389, - 356, - 454, - 323, - 361, - 288, - 397, - 365, - 379, - 378, - 400, - 377, - 152, - 148, - 176, - 149, - 150, - 136, - 172, - 58, - 132, - 93, - 234, - 127, - 162, - 21, - 54, - 103, - 67, - 109 - ], - "scale": 1.04, - "sourceImage": "base/base-front.png" - }, - "layers": [ - { - "id": "body", - "file": "layers/body.png", - "source": "base-faceless-masked+regenerated-fill", - "alphaBBox": [ - 0, - 848, - 1005, - 1566 - ], - "opaquePixels": 609331, - "greenResidue": 0, - "fillTargetPixels": 35617, - "fillSourceRaw": "raw/body.png (rejected 재생성이 아니라 수용된 body.png의 크로마키 결과; head/hairFront와 달리 body 재생성은 승인됨)" - }, - { - "id": "head", - "file": "layers/head.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 152, - 155, - 870, - 1044 - ], - "opaquePixels": 425507, - "greenResidue": 0 - }, - { - "id": "hairFront", - "file": "layers/hairFront.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 255, - 398, - 739, - 941 - ], - "opaquePixels": 24248, - "greenResidue": 0 - } - ], - "rejectedRawEdits": { - "body": { - "file": "raw/body.png", - "status": "accepted-as-fill-source", - "reason": "정렬 (0,0), 형태 변형 없음 — head_mask 채움 전용 소스로 재사용" - } - }, - "composite": { - "meanAbsDiff": { - "full": 1.9005205983704294, - "face": 0.49715101986881705, - "hairOutline": 11.53726341942018 - }, - "target": { - "full": 3.0, - "pass": true - }, - "backgroundNote": "카테고리0(배경)은 종이결 텍스처 노이즈(표준편차 ~29/채널)를 포함해 평탄한 크림(#EEE5D3)과 자체적으로 평균절대차 ~7 차이가 난다." - }, - "browCenterline": { - "method": "Otsu(front lum in bbox) AND diff(F-front)>=25, morphological closing to merge hatching, single global degree-2 polyfit on column-weighted centroid with one-pass outlier removal", - "diffThresh": 25.0, - "bboxPad": { - "x": 25.0, - "yUp": 42.0, - "yDown": 14.0 - }, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "outlierStdMult": 2.0, - "edgeInsetFrac": 0.03, - "peakWindow": [ - 0.25, - 0.6 - ], - "peakFallbackFrac": 0.35, - "peakFlatHeightPx": 3.0, - "peakFailEdgeFrac": 0.2, - "oldPoints": { - "browLeft": { - "inner": [ - 443.39, - 528.19 - ], - "peak": [ - 388.1, - 499.38 - ], - "outer": [ - 303.13, - 541.4 - ] - }, - "browRight": { - "inner": [ - 528.36, - 521.71 - ], - "peak": [ - 617.58, - 482.54 - ], - "outer": [ - 673.41, - 510.91 - ] - } - }, - "newPoints": { - "browLeft": { - "name": "browLeft", - "bbox": [ - 278, - 457, - 468, - 555 - ], - "otsuThresh": 109.0, - "colRange": [ - 308, - 442 - ], - "width": 134.0, - "nColumns": 127, - "nOutliersRemoved": 10, - "fitCoeffs": [ - 0.0014127, - -1.19951702, - 767.56190241 - ], - "inner": [ - 437.98, - 513.19 - ], - "peak": [ - 354.9, - 519.79 - ], - "outer": [ - 312.02, - 530.82 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.9055, - "checkA_pass": true, - "halfWidthInner": 6.995, - "halfWidthPeak": 8.34, - "halfWidthOuter": 3.812, - "bandX0": 278, - "bandY0": 457, - "colMin": 308, - "colMax": 442 - }, - "browRight": { - "name": "browRight", - "bbox": [ - 503, - 441, - 698, - 536 - ], - "otsuThresh": 113.0, - "colRange": [ - 517, - 659 - ], - "width": 142.0, - "nColumns": 143, - "nOutliersRemoved": 6, - "fitCoeffs": [ - 0.00149431, - -1.79001855, - 1032.39992065 - ], - "inner": [ - 521.26, - 505.36 - ], - "peak": [ - 609.3, - 496.5 - ], - "outer": [ - 654.74, - 500.99 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.8951, - "checkA_pass": true, - "halfWidthInner": 3.896, - "halfWidthPeak": 6.997, - "halfWidthOuter": 1.973, - "bandX0": 503, - "bandY0": 441, - "colMin": 517, - "colMax": 659 - } - }, - "halfWidthLeft": [ - 6.995, - 8.34, - 3.812 - ], - "halfWidthRight": [ - 3.896, - 6.997, - 1.973 - ], - "halfWidthNote": "원화 눈썹 잉크 띠 반두께[inner,peak,outer](px, 중심선 법선 방향). export_rig.py가 persona.json browHalfWidthFromArt!=false(기본)면 RigBrow.halfWidth로 내보낸다.", - "checkB_innerMinusPeakY": { - "browLeft": -6.6, - "browRight": 8.86 - }, - "checkD_symmetry": { - "innerYDiff": 7.83, - "peakYDiff": 23.29 - }, - "evidenceImage": "preview/v2/brow-centerline.jpg" - }, - "layersV2": { - "chinBandFix": { - "targetRegionPixels": 15342, - "sourceBandHeight": 150, - "rowJitterRange": [ - -2.0, - 2.0 - ], - "rngSeed": 20260930 - }, - "hairEdgeFix": { - "bandOuterPx": 40, - "bandInnerPx": 6, - "lumSmoothstep": [ - 205.0, - 150.0 - ], - "minFragmentAreaPx": 60, - "head": { - "bandOuterPixels": 102178, - "bandInnerPixels": 41504, - "componentsBefore": 175, - "removed": 170, - "removedPixels": 963 - }, - "hairFront": { - "bandOuterPixels": 6606, - "bandInnerPixels": 16598, - "componentsBefore": 191, - "removed": 179, - "removedPixels": 536 - } - }, - "hairPaperGapFix": { - "regionDilatePx": 6, - "paperColorDistThreshold": 40.0, - "paperLumThreshold": 195.0, - "paperSatThreshold": 40.0, - "opaqueAlphaThreshold": 0.95, - "regionPixels": 230717, - "head": { - "targetPixels": 821, - "componentsBefore": 5, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - }, - "hairFront": { - "targetPixels": 0, - "componentsBefore": 12, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - } - }, - "jawInkFix": { - "mouthCenterY": 796.4, - "distThresholdPx": 14.0, - "mergePx": 3, - "aspectThreshold": 1.0, - "removedGroups": [ - { - "pixels": 838, - "bbox": [ - 554, - 900, - 608, - 942 - ], - "width": 54, - "height": 42, - "aspect": 1.286 - } - ], - "keptGroups": [ - { - "pixels": 2419, - "bbox": [ - 288, - 797, - 388, - 898 - ], - "width": 100, - "height": 101, - "aspect": 0.99 - }, - { - "pixels": 1884, - "bbox": [ - 634, - 797, - 714, - 884 - ], - "width": 80, - "height": 87, - "aspect": 0.92 - }, - { - "pixels": 84, - "bbox": [ - 434, - 924, - 444, - 934 - ], - "width": 10, - "height": 10, - "aspect": 1.0 - } - ], - "removedPixels": 838, - "strayFragmentCleanup": { - "componentsBefore": 11, - "removed": 0, - "removedPixels": 0 - } - }, - "metrics": { - "body": { - "haloPct": 0.17023346303501946 - }, - "head": { - "haloPct": 0.0, - "bgLeakAlphaMean": 0.0, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - }, - "hairFront": { - "haloPct": 0.06115272894052897, - "bgLeakAlphaMean": 0.0, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - } - }, - "layers": [ - { - "id": "body", - "bbox": [ - 0, - 848, - 1005, - 1566 - ], - "opaque": 609284, - "greenResidue": 0 - }, - { - "id": "head", - "bbox": [ - 132, - 156, - 874, - 1044 - ], - "opaque": 438690, - "greenResidue": 1 - }, - { - "id": "hairFront", - "bbox": [ - 237, - 398, - 777, - 933 - ], - "opaque": 29897, - "greenResidue": 1 - } - ], - "faceDetail": { - "browLandmarksSource": "manifest.landmarks.eyebrowLeft/Right", - "region": "face", - "faceInsetPx": 24.0, - "matchFacelessColor": true, - "matchFacelessColorDeltaRgb": { - "eyeLeft": [ - -11.32, - -12.21, - -12.78 - ], - "eyeRight": [ - -10.52, - -12.16, - -13.3 - ], - "browLeft": [ - -0.25, - 1.09, - 1.48 - ], - "browRight": [ - -0.9, - -0.03, - -0.07 - ], - "mouth": [ - -3.26, - -3.73, - -4.96 - ] - }, - "regions": { - "eyeLeft": [ - 316.3, - 509.8, - 460.1, - 672.2 - ], - "eyeRight": [ - 527.1, - 486.5, - 670.8, - 653.9 - ], - "mouth": [ - 387.6, - 729.4, - 628.6, - 920.2 - ], - "moles": [] - }, - "exclusionPixels": { - "eyeLeft": 11485, - "eyeRight": 10642, - "browLeft": 11017, - "browRight": 9084, - "mouth": 18995, - "union": 58172 - }, - "hairMaskPixels": 225679, - "bbox": [ - 279, - 397, - 722, - 939 - ], - "textureCloneStamp": { - "cloneTargetThreshold": 65.0, - "avoidThreshold": 110.0, - "lineOpeningIterations": 3, - "featherPx": 4.0, - "badTexturePixels": 5, - "preservedShadowPixels": 0, - "components": [ - { - "componentId": 1, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 2, - "pixels": 1, - "offset": [ - -100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 3, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 4, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 5, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - } - ] - }, - "genericPatches": { - "forehead": [ - 468, - 398, - 516, - 446 - ], - "cheekLeft": [ - 326, - 650, - 374, - 698 - ], - "cheekRight": [ - 519, - 631, - 567, - 679 - ] - }, - "genericPatchBaseline": { - "highFreqEnergy": 10.784726068172136, - "lineEnergy": 3.811262288996353 - }, - "checkA_meanAbsDiff": 0.41166174567512276, - "checkB_seamLumDiff": 0.06290680428432438, - "checkC_holeDarkResiduePct": 0.03185918241373131, - "checkC_violationPixels": 16, - "checkD_texturePreservation": { - "eyeLeft": { - "insideEnergy": 9.147273470585267, - "baselineEnergy": 10.784726068172136, - "ratio": 0.8481692917153162, - "pass": true - }, - "eyeRight": { - "insideEnergy": 9.392379443644488, - "baselineEnergy": 10.784726068172136, - "ratio": 0.8708964311447152, - "pass": true - }, - "browLeft": { - "insideEnergy": 11.064785620046433, - "baselineEnergy": 10.784726068172136, - "ratio": 1.0259681655430088, - "pass": true - }, - "browRight": { - "insideEnergy": 11.025329524088622, - "baselineEnergy": 10.784726068172136, - "ratio": 1.0223096492572543, - "pass": true - }, - "mouth": { - "insideEnergy": 9.73095282876833, - "baselineEnergy": 10.784726068172136, - "ratio": 0.9022902174109272, - "pass": true - } - }, - "checkD_allPass": true, - "checkE_ghostOutline": { - "eyeLeft": { - "bandLineEnergy": 5.365413565857688, - "baselineLineEnergy": 3.811262288996353, - "ratio": 1.4077786200515212, - "pass": true - }, - "eyeRight": { - "bandLineEnergy": 5.572405832019536, - "baselineLineEnergy": 3.811262288996353, - "ratio": 1.4620893051910517, - "pass": true - }, - "browLeft": { - "bandLineEnergy": 4.320421303108777, - "baselineLineEnergy": 3.811262288996353, - "ratio": 1.1335932757980045, - "pass": true - }, - "browRight": { - "bandLineEnergy": 4.382619320579252, - "baselineLineEnergy": 3.811262288996353, - "ratio": 1.149912807951446, - "pass": true - }, - "mouth": { - "bandLineEnergy": 3.0566944722024156, - "baselineLineEnergy": 3.811262288996353, - "ratio": 0.8020162981245137, - "pass": true - } - }, - "checkE_allPass": true, - "checkDarkCircle": { - "eyeLeft": { - "compositeMean": 145.38990476190475, - "frontMean": 145.17142857142855, - "diff": 0.2184761904761956, - "pass": true - }, - "eyeRight": { - "compositeMean": 145.47980952380954, - "frontMean": 145.4384761904762, - "diff": 0.041333333333341216, - "pass": true - } - }, - "checkDarkCircle_allPass": true - }, - "paperGrain": { - "sourceImage": "base/base-front.png", - "sourceOrigin": [ - 739, - 10 - ], - "tileSize": 256, - "personMarginPx": 20, - "cleanFraction": 0.9979400634765625, - "contaminatedPixelsFilled": 135, - "chromaKeep": 0.3, - "targetLumRange": [ - 245.0, - 250.0 - ], - "lumShift": 23.88299560546875, - "finalMeanLum": 247.47297462295472, - "finalLumStd": 2.194378158845552, - "seamContinuity": { - "leftRightDiff": 0.1583767181773131, - "topBottomDiff": 0.03202277288771901 - } - } - }, - "lipTexture": { - "designVersion": "A5(아랫입술 그늘 띠를 lip-shadow 조각으로 분리)", - "landmarks": { - "mouthCornerLeft": [ - 432.63, - 803.41 - ], - "mouthCornerRight": [ - 583.64, - 792.2 - ], - "upperLipTop": [ - 505.16, - 769.79 - ], - "lowerLipBottom": [ - 509.44, - 832.35 - ], - "mouthCenter": [ - 507.2048154473305, - 796.4007625579834 - ] - }, - "capBBox": [ - 427.13, - 766.07, - 589.13, - 836.07 - ], - "otsuThreshLabA": 139.0, - "diffThresh": 20.0, - "dilatePx": 10.0, - "alphaRampPx": 4.0, - "splitOverlapPx": 2.0, - "shadowBandPx": 18.0, - "shadowTopOverlapPx": 6.0, - "shadowSideRampPx": 6.0, - "shadowTopRampPx": 4.0, - "checks": { - "lip-upper": { - "regionPixelCount": 5597, - "fullOpacityPixelCount": 4832, - "rampPixelCount": 765, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 37, - 30, - 173, - 51 - ] - }, - "lip-lower": { - "regionPixelCount": 6060, - "fullOpacityPixelCount": 5346, - "rampPixelCount": 714, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 36, - 66, - 171, - 54 - ] - }, - "lip-shadow": { - "regionPixelCount": 3552, - "fullOpacityPixelCount": 1953, - "rampPixelCount": 1599, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 46, - 65, - 148, - 63 - ] - }, - "splitOverlapPixelCount": 339, - "splitOverlapBandOk": true, - "splitOverlapAlphaMaxDiff": 0, - "splitOverlapConsistentOk": true, - "splitCoverageMismatchPixelCount": 0, - "splitCheckOk": true, - "shadowLowerOverlapPixelCount": 2593, - "shadowLowerOverlapMaxDiff": 15.339100346020729, - "shadowLowerOverlapDiffGt1PixelCount": 115, - "shadowLowerOverlapOk": false - }, - "check3NeutralComposite": { - "fullOpacityMaxDiff": 0.0, - "rampMeanAbsDiff": 7.457, - "mouthBBoxMeanAbsDiff": 1.516 - }, - "pngFileSizes": { - "lip-upper": 23244, - "lip-lower": 24183, - "lip-shadow": 24527 - }, - "canvasOrigin": { - "upper": [ - 424, - 756 - ], - "lower": [ - 423, - 792 - ], - "shadow": [ - 433, - 791 - ] - }, - "evidenceImage": "preview/v2/lip-texture.jpg", - "check4WebpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jaw": { - "designVersion": "A5(결정문 §8.4 하관 띠 변형 — 턱 조각 원본)", - "noseTip": [ - 499.58, - 704.43 - ], - "chinTip": [ - 518.27, - 935.21 - ], - "yFormula": "noseTip.y - 50 - 10 .. chinTip.y + 40", - "noseToTopPx": 60.0, - "chinToBottomPx": 40.0, - "ovalOutsetPx": 18.0, - "faceOvalSource": "export_rig.detect_face_landmarks + compute_face_oval(base-front.png, 결정적)", - "bboxCanvas": [ - 243, - 644, - 755, - 976 - ], - "naiveXRangeForComparison": [ - 243, - 755 - ], - "checks": { - "head": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 381852 - }, - "detail": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 416090 - } - }, - "evidenceImage": "preview/v2/jaw-pieces.jpg", - "check1VsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 3.222, - "maxAbsDiffPremultipliedRgb": 38.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.885, - "maxAbsDiffPremultipliedRgb": 36.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 15788, - "lip-lower": 16602, - "lip-shadow": 17250 - }, - "totalBytes": 49640, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 46270, - "jaw-detail": 25322 - }, - "totalBytes": 71592, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 3.222, - "maxAbsDiffPremultipliedRgb": 38.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.885, - "maxAbsDiffPremultipliedRgb": 36.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "browSprite": { - "enabled": true, - "bandExtraHalfHeightPx": 8.0, - "bandEndExtendPx": 6.0, - "diffThresh": 25.0, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "hairSource": "layers/v2/hairFront.png alpha(게시되는 레이어 그 자체, 분할 카테고리 아님)", - "hairAlphaThresh": 128.0, - "hairDilateIterations": 1, - "rampPx": 1.5, - "inkLumMethod": "median(ink-classified pixels in band, region after hair removal)", - "minAlphaForUnmix": 0.05, - "hairMaskPixelCount": 36954, - "left": { - "bboxCanvas": [ - 314, - 502, - 129, - 38 - ], - "halfHeightPx": 16.34, - "otsuThresh": 111.0, - "lSkin": 181.667, - "lInk": 46.667, - "regionPixelCount": 1559, - "hairOverlapPixelCount": 183, - "neutralCompositeMeanAbsDiffAlphaRegion": 8.002, - "pngFileSizeBytes": 6225 - }, - "right": { - "bboxCanvas": [ - 517, - 486, - 142, - 32 - ], - "halfHeightPx": 14.997, - "otsuThresh": 117.0, - "lSkin": 181.0, - "lInk": 48.833, - "regionPixelCount": 1750, - "hairOverlapPixelCount": 8, - "neutralCompositeMeanAbsDiffAlphaRegion": 6.26, - "pngFileSizeBytes": 6292 - }, - "canvasOrigin": { - "left": [ - 314.0, - 502.0 - ], - "right": [ - 517.0, - 486.0 - ] - }, - "evidenceImage": "preview/v2/brow-sprite.jpg", - "checkWebpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "eyeLineScale": { - "enabled": true, - "method": "눈 폭 가운데 60% 열마다 upperLidTop.y-12 ~ 홍채중심y 사이 첫 연속 어두운(lum<80) 구간 길이의 중앙값", - "windowAbovePx": 12.0, - "darkLumThresh": 80.0, - "columnMiddleFrac": 0.6, - "p1BaselinePx": 2.0, - "lineScaleClampRange": [ - 1.0, - 2.5 - ], - "left": { - "measuredPx": 1.5, - "nColumns": 54, - "ratio": 0.75, - "lineScale": 1.0 - }, - "right": { - "measuredPx": 2.0, - "nColumns": 54, - "ratio": 1.0, - "lineScale": 1.0 - } - }, - "iris": { - "method": "가로: 중심 y 부근 상하 각 4행에서 중심 x를 포함하는 연속 어두운(lum<100) 구간의 중앙값으로 반지름·중심x. 세로: 중심 x 부근 좌우 각 4열에서 중심 y를 포함하는 연속 어두운 구간의 아래쪽 끝 중앙값에서 가로 반지름을 빼 중심y(세로축은 윗눈꺼풀 잉크와 홍채가 같은 톤으로 이어져 위쪽 경계를 가를 수 없다).", - "darkLumThresh": 100.0, - "scanBandPx": 4, - "left": { - "measuredCenter": [ - 389.0, - 580.0 - ], - "measuredRadius": 20.0, - "rigCenter": [ - 394.43, - 584.74 - ], - "rigRadius": 20.58, - "nRowsHorizontal": 9, - "nColsVertical": 9 - }, - "right": { - "measuredCenter": [ - 593.5, - 558.5 - ], - "measuredRadius": 19.5, - "rigCenter": [ - 596.13, - 567.87 - ], - "rigRadius": 20.39, - "nRowsHorizontal": 9, - "nColsVertical": 9 - } - }, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 3972, - "brow-right": 3772 - }, - "totalBytes": 7744, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p3-linocut/persona.json b/docs/avatar-art/p3-linocut/persona.json deleted file mode 100644 index 20305ad..0000000 --- a/docs/avatar-art/p3-linocut/persona.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "code": "P3", - "publicSlug": "p3", - "styleFrame": "../art-direction-v3/p1/r2-b-linocut.png", - "paletteSamples": { - "ink": { "box": [430, 200, 580, 320], "lumThreshold": 55 }, - "motifPetalBoxes": [ - { "label": "sun", "kind": "ochre", "box": [1230, 10, 1536, 210] }, - { "label": "tulip", "kind": "ochre", "box": [1030, 280, 1170, 560] } - ], - "motifLeafBoxes": [ - { "label": "cloudLeft", "kind": "blue", "box": [20, 20, 380, 190] }, - { "label": "raincloud", "kind": "blue", "box": [520, 10, 930, 230] }, - { "label": "wiltedFlowerLeaf", "kind": "blue", "box": [520, 280, 650, 580] }, - { "label": "closedBudLeaf", "kind": "blue", "box": [10, 290, 110, 570] } - ] - }, - "faceDetail": { - "moles": [], - "region": "face", - "faceInsetPx": 24, - "matchFacelessColor": true - }, - "pivots": { - "neck": [518, 1005], - "body": [502, 1566], - "face": [492, 652] - }, - "crops": { - "bustYOffset": 40 - } -} diff --git a/docs/avatar-art/p3-linocut/preview/qa/face-art-vs-render.jpg b/docs/avatar-art/p3-linocut/preview/qa/face-art-vs-render.jpg deleted file mode 100644 index 4876e6c..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/qa/face-art-vs-render.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/qa/face-zoom.jpg b/docs/avatar-art/p3-linocut/preview/qa/face-zoom.jpg deleted file mode 100644 index ae8785d..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/qa/face-zoom.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/qa/neutral-vs-art.jpg b/docs/avatar-art/p3-linocut/preview/qa/neutral-vs-art.jpg deleted file mode 100644 index 9ea9460..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/qa/neutral-vs-art.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/qa/normalized.jpg b/docs/avatar-art/p3-linocut/preview/qa/normalized.jpg deleted file mode 100644 index 2fc9880..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/qa/normalized.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/qa/sheet-expr-bust.jpg b/docs/avatar-art/p3-linocut/preview/qa/sheet-expr-bust.jpg deleted file mode 100644 index c61e195..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/qa/sheet-expr-bust.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/qa/sheet-expr-face.jpg b/docs/avatar-art/p3-linocut/preview/qa/sheet-expr-face.jpg deleted file mode 100644 index c37e95f..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/qa/sheet-expr-face.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/qa/sheet-viseme.jpg b/docs/avatar-art/p3-linocut/preview/qa/sheet-viseme.jpg deleted file mode 100644 index 6146d6d..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/qa/sheet-viseme.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/qa/viseme-timeline.json b/docs/avatar-art/p3-linocut/preview/qa/viseme-timeline.json deleted file mode 100644 index 2c5e4b0..0000000 --- a/docs/avatar-art/p3-linocut/preview/qa/viseme-timeline.json +++ /dev/null @@ -1,287 +0,0 @@ -[ - { - "t": 0, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00000.png" - }, - { - "t": 50, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00050.png" - }, - { - "t": 100, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00100.png" - }, - { - "t": 150, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00150.png" - }, - { - "t": 200, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00200.png" - }, - { - "t": 250, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00250.png" - }, - { - "t": 300, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00300.png" - }, - { - "t": 350, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00350.png" - }, - { - "t": 400, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00400.png" - }, - { - "t": 450, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00450.png" - }, - { - "t": 500, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00500.png" - }, - { - "t": 550, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00550.png" - }, - { - "t": 600, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00600.png" - }, - { - "t": 650, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00650.png" - }, - { - "t": 700, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00700.png" - }, - { - "t": 750, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00750.png" - }, - { - "t": 800, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00800.png" - }, - { - "t": 850, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00850.png" - }, - { - "t": 900, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00900.png" - }, - { - "t": 950, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t00950.png" - }, - { - "t": 1000, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01000.png" - }, - { - "t": 1050, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01050.png" - }, - { - "t": 1100, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01100.png" - }, - { - "t": 1150, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01150.png" - }, - { - "t": 1200, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01200.png" - }, - { - "t": 1250, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01250.png" - }, - { - "t": 1300, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01300.png" - }, - { - "t": 1350, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01350.png" - }, - { - "t": 1400, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01400.png" - }, - { - "t": 1450, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01450.png" - }, - { - "t": 1500, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01500.png" - }, - { - "t": 1550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01550.png" - }, - { - "t": 1600, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01600.png" - }, - { - "t": 1650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01650.png" - }, - { - "t": 1700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01700.png" - }, - { - "t": 1750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01750.png" - }, - { - "t": 1800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01800.png" - }, - { - "t": 1850, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01850.png" - }, - { - "t": 1900, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01900.png" - }, - { - "t": 1950, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t01950.png" - }, - { - "t": 2000, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02000.png" - }, - { - "t": 2050, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02050.png" - }, - { - "t": 2100, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02100.png" - }, - { - "t": 2150, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02150.png" - }, - { - "t": 2200, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02200.png" - }, - { - "t": 2250, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02250.png" - }, - { - "t": 2300, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02300.png" - }, - { - "t": 2350, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02350.png" - }, - { - "t": 2400, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02400.png" - }, - { - "t": 2450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02450.png" - }, - { - "t": 2500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02500.png" - }, - { - "t": 2550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02550.png" - }, - { - "t": 2600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02600.png" - }, - { - "t": 2650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02650.png" - }, - { - "t": 2700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02700.png" - }, - { - "t": 2750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02750.png" - }, - { - "t": 2800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p3-linocut\\preview\\qa\\frames\\viseme-scan-t02800.png" - } -] \ No newline at end of file diff --git a/docs/avatar-art/p3-linocut/preview/v2/brow-centerline.jpg b/docs/avatar-art/p3-linocut/preview/v2/brow-centerline.jpg deleted file mode 100644 index 55a5a63..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/v2/brow-centerline.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/v2/brow-sprite.jpg b/docs/avatar-art/p3-linocut/preview/v2/brow-sprite.jpg deleted file mode 100644 index 1dd0d81..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/v2/brow-sprite.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/v2/export-rig-report.json b/docs/avatar-art/p3-linocut/preview/v2/export-rig-report.json deleted file mode 100644 index 5ccf791..0000000 --- a/docs/avatar-art/p3-linocut/preview/v2/export-rig-report.json +++ /dev/null @@ -1,676 +0,0 @@ -{ - "faceOvalBBox": [ - 257.2, - 390.2, - 736.4, - 945.4 - ], - "cropsFace": [ - 185.9, - 356.9, - 621.9, - 621.9 - ], - "mouthCenter": [ - 507.2, - 796.4 - ], - "palette": { - "ink": "#242322", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#B4835D", - "lipLower": "#BD8A62", - "mouthLine": "#76553F", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "paletteSamples": { - "eyeLeft": { - "sclera": "#D2A373", - "iris": "#43382C", - "irisRing": "#725C45", - "n": { - "sclera": 968, - "iris": 191, - "irisRing": 392 - } - }, - "eyeRight": { - "sclera": "#D3A778", - "iris": "#513F2F", - "irisRing": "#47392D", - "n": { - "sclera": 961, - "iris": 189, - "irisRing": 383 - } - }, - "scleraIrisIrisRingFixedOverride": { - "reason": "persona.json paletteSamples.eyeOverride(고정 설계값)", - "measured": { - "sclera": "#D2A576", - "iris": "#4A3C2E", - "irisRing": "#5C4A39" - }, - "fixed": { - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F" - } - }, - "ink": { - "box": [ - 430, - 200, - 580, - 320 - ], - "lumThreshold": 55.0, - "n": 13942, - "hex": "#242322" - }, - "lipUpper": { - "box": [ - 427, - 766, - 588, - 794 - ], - "hex": "#B4835D" - }, - "lipLower": { - "box": [ - 427, - 798, - 588, - 835 - ], - "hex": "#BD8A62" - }, - "mouthLine": { - "box": [ - 447, - 794, - 568, - 799 - ], - "hex": "#76553F" - }, - "motifPetal": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "sun": { - "box": [ - 1230, - 10, - 1536, - 210 - ], - "n": 20570, - "hex": "#D3A661" - }, - "tulip": { - "box": [ - 1030, - 280, - 1170, - 560 - ], - "n": 4314, - "hex": "#CCA063" - } - }, - "hex": "#D0A362" - }, - "motifLeaf": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "cloudLeft": { - "box": [ - 20, - 20, - 380, - 190 - ], - "n": 21312, - "hex": "#636D76" - }, - "raincloud": { - "box": [ - 520, - 10, - 930, - 230 - ], - "n": 27607, - "hex": "#586B76" - }, - "wiltedFlowerLeaf": { - "box": [ - 520, - 280, - 650, - 580 - ], - "n": 5818, - "hex": "#495961" - }, - "closedBudLeaf": { - "box": [ - 10, - 290, - 110, - 570 - ], - "n": 6589, - "hex": "#495961" - } - }, - "hex": "#53626C" - } - }, - "publish": { - "fileSizes": { - "body": 196976, - "head": 138282, - "hair-front": 32594, - "face-detail": 45922, - "paper-grain": 980 - }, - "encodeMeta": { - "quality": 80, - "scale": 1.0, - "totalBytes": 414754 - }, - "totalBytes": 414754 - }, - "grain": { - "href": "/avatar/v3/p3/paper-grain.webp", - "size": 256 - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 15788, - "lip-lower": 16602, - "lip-shadow": 17250 - }, - "totalBytes": 49640, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 46270, - "jaw-detail": 25322 - }, - "totalBytes": 71592, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 3.222, - "maxAbsDiffPremultipliedRgb": 38.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.885, - "maxAbsDiffPremultipliedRgb": 36.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 3972, - "brow-right": 3772 - }, - "totalBytes": 7744, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "rig": { - "schemaVersion": "vignette.avatar.v3.rig.v1", - "persona": "P3", - "canvas": { - "w": 1005, - "h": 1566 - }, - "layers": { - "body": { - "href": "/avatar/v3/p3/body.webp", - "x": 0, - "y": 848, - "w": 1005, - "h": 718 - }, - "head": { - "href": "/avatar/v3/p3/head.webp", - "x": 132, - "y": 156, - "w": 742, - "h": 888 - }, - "hairFront": { - "href": "/avatar/v3/p3/hair-front.webp", - "x": 237, - "y": 398, - "w": 540, - "h": 535 - }, - "faceDetail": { - "href": "/avatar/v3/p3/face-detail.webp", - "x": 279, - "y": 397, - "w": 443, - "h": 542 - } - }, - "grain": { - "href": "/avatar/v3/p3/paper-grain.webp", - "size": 256 - }, - "lipTexture": { - "upper": { - "href": "/avatar/v3/p3/lip-upper.webp", - "x": 424, - "y": 756, - "w": 173, - "h": 51 - }, - "lower": { - "href": "/avatar/v3/p3/lip-lower.webp", - "x": 423, - "y": 792, - "w": 171, - "h": 54 - }, - "shadow": { - "href": "/avatar/v3/p3/lip-shadow.webp", - "x": 433, - "y": 791, - "w": 148, - "h": 63 - } - }, - "jaw": { - "head": { - "href": "/avatar/v3/p3/jaw-head.webp", - "x": 243, - "y": 644, - "w": 512, - "h": 332 - }, - "detail": { - "href": "/avatar/v3/p3/jaw-detail.webp", - "x": 243, - "y": 644, - "w": 512, - "h": 332 - } - }, - "motif": null, - "pivots": { - "neck": [ - 518, - 1005 - ], - "body": [ - 502, - 1566 - ], - "face": [ - 492, - 652 - ] - }, - "crops": { - "portrait": [ - 0, - 0, - 1005, - 1566 - ], - "bust": [ - 0.0, - 40.0, - 1005.0, - 1005.0 - ], - "face": [ - 185.87612950367526, - 356.8740379088401, - 621.8837808288575, - 621.8837808288575 - ] - }, - "faceOval": [ - [ - 475.8, - 393.7 - ], - [ - 540.7, - 390.2 - ], - [ - 596.5, - 395.8 - ], - [ - 649.6, - 413.2 - ], - [ - 685.4, - 442.2 - ], - [ - 709.3, - 480.3 - ], - [ - 724.7, - 521.2 - ], - [ - 733.9, - 571.8 - ], - [ - 736.4, - 619.7 - ], - [ - 736.4, - 669.4 - ], - [ - 731.0, - 722.5 - ], - [ - 718.2, - 777.9 - ], - [ - 698.5, - 822.4 - ], - [ - 674.5, - 854.4 - ], - [ - 643.8, - 883.5 - ], - [ - 618.4, - 904.1 - ], - [ - 591.4, - 922.9 - ], - [ - 559.9, - 938.4 - ], - [ - 519.0, - 945.4 - ], - [ - 477.9, - 944.3 - ], - [ - 445.2, - 933.5 - ], - [ - 416.7, - 919.2 - ], - [ - 389.2, - 903.2 - ], - [ - 355.4, - 879.7 - ], - [ - 328.1, - 851.8 - ], - [ - 302.7, - 811.3 - ], - [ - 282.3, - 758.0 - ], - [ - 269.2, - 706.1 - ], - [ - 262.4, - 657.2 - ], - [ - 257.2, - 610.1 - ], - [ - 258.2, - 560.0 - ], - [ - 266.7, - 517.2 - ], - [ - 283.8, - 476.0 - ], - [ - 312.7, - 441.9 - ], - [ - 359.0, - 416.0 - ], - [ - 411.7, - 401.4 - ] - ], - "landmarks": { - "eyeLeft": { - "inner": [ - 435.1, - 593.8 - ], - "outer": [ - 346.3, - 589.4 - ], - "upperLidTop": [ - 382.5, - 570.1 - ], - "lowerLidBottom": [ - 386.7, - 602.2 - ], - "iris": { - "center": [ - 389.0, - 580.0 - ], - "radius": 20.0 - }, - "lineScale": 1.0 - }, - "eyeRight": { - "inner": [ - 552.1, - 583.6 - ], - "outer": [ - 640.8, - 564.7 - ], - "upperLidTop": [ - 600.8, - 552.1 - ], - "lowerLidBottom": [ - 601.0, - 583.9 - ], - "iris": { - "center": [ - 593.5, - 558.5 - ], - "radius": 19.5 - }, - "lineScale": 1.0 - }, - "browLeft": { - "inner": [ - 438.0, - 513.2 - ], - "peak": [ - 354.9, - 519.8 - ], - "outer": [ - 312.0, - 530.8 - ], - "halfWidth": [ - 7.0, - 8.34, - 3.81 - ], - "sprite": { - "href": "/avatar/v3/p3/brow-left.webp", - "x": 314.0, - "y": 502.0, - "w": 129, - "h": 38 - } - }, - "browRight": { - "inner": [ - 521.3, - 505.4 - ], - "peak": [ - 609.3, - 496.5 - ], - "outer": [ - 654.7, - 501.0 - ], - "halfWidth": [ - 3.9, - 7.0, - 1.97 - ], - "sprite": { - "href": "/avatar/v3/p3/brow-right.webp", - "x": 517.0, - "y": 486.0, - "w": 142, - "h": 32 - } - }, - "noseTip": [ - 499.6, - 704.4 - ], - "mouthCornerLeft": [ - 432.6, - 803.4 - ], - "mouthCornerRight": [ - 583.6, - 792.2 - ], - "upperLipTop": [ - 505.2, - 769.8 - ], - "lowerLipBottom": [ - 509.4, - 832.4 - ], - "mouthCenter": [ - 507.2, - 796.4 - ], - "chinTip": [ - 518.3, - 935.2 - ] - }, - "palette": { - "ink": "#242322", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#B4835D", - "lipLower": "#BD8A62", - "mouthLine": "#76553F", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "backdrop": { - "cognitive": "#ECE3D1", - "positive": "#F1DEC2", - "negative": "#DCE0E2", - "defensive": "#E6DAD3", - "energy": "#E2E0D0" - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p3-linocut/preview/v2/ghost-check.jpg b/docs/avatar-art/p3-linocut/preview/v2/ghost-check.jpg deleted file mode 100644 index cde7ae4..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/v2/ghost-check.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/v2/holes-texture.jpg b/docs/avatar-art/p3-linocut/preview/v2/holes-texture.jpg deleted file mode 100644 index a8df071..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/v2/holes-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/v2/jaw-pieces.jpg b/docs/avatar-art/p3-linocut/preview/v2/jaw-pieces.jpg deleted file mode 100644 index b3ec278..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/v2/jaw-pieces.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/preview/v2/lip-texture.jpg b/docs/avatar-art/p3-linocut/preview/v2/lip-texture.jpg deleted file mode 100644 index 3cd74a2..0000000 Binary files a/docs/avatar-art/p3-linocut/preview/v2/lip-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p3-linocut/prompts/base-faceless.txt b/docs/avatar-art/p3-linocut/prompts/base-faceless.txt deleted file mode 100644 index 8d095a7..0000000 --- a/docs/avatar-art/p3-linocut/prompts/base-faceless.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as faceless.png: - -Redraw exactly the same woman, same frontal pose, same framing and same linocut relief-print style as the reference, but leave the face blank: remove the eyes, eyebrows and mouth completely and fill those areas with plain skin in the same flat skin color, keeping the nose, the face outline, the ears, the hair, the neck, the low bun and the loose strands of hair, the cream t-shirt and the ochre knit sweater with its small pale stain exactly as they are. Do not move, resize or redraw anything else. Plain flat cream background, nothing else, no text. diff --git a/docs/avatar-art/p3-linocut/prompts/body.txt b/docs/avatar-art/p3-linocut/prompts/body.txt deleted file mode 100644 index 38d8836..0000000 --- a/docs/avatar-art/p3-linocut/prompts/body.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as body.png: - -Keep only the neck, the cream t-shirt and the ochre knit sweater, including the small pale stain near the shoulder exactly as they are in the reference: same shapes, same position, same linocut relief-print style, same ink colors and texture. Remove the head, the face, the ears and all of the hair completely. Where the hair or the chin previously covered the neck or the clothes, continue them naturally in the same style so there are no holes. Place the result on a plain flat pure green background (#00ff00) with nothing else, no text. diff --git a/docs/avatar-art/p3-linocut/raw/body.png b/docs/avatar-art/p3-linocut/raw/body.png deleted file mode 100644 index 0a15f43..0000000 Binary files a/docs/avatar-art/p3-linocut/raw/body.png and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/base/base-faceless.png b/docs/avatar-art/p4-linocut/base/base-faceless.png deleted file mode 100644 index 496a940..0000000 Binary files a/docs/avatar-art/p4-linocut/base/base-faceless.png and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/base/base-front.png b/docs/avatar-art/p4-linocut/base/base-front.png deleted file mode 100644 index 57260d7..0000000 Binary files a/docs/avatar-art/p4-linocut/base/base-front.png and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/base/edits/aligned-body-1.json b/docs/avatar-art/p4-linocut/base/edits/aligned-body-1.json deleted file mode 100644 index 802e995..0000000 --- a/docs/avatar-art/p4-linocut/base/edits/aligned-body-1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\body-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\aligned-body-1.png", - "refSize": [ - 1024, - 1536 - ], - "editedOriginalSize": [ - 1024, - 1536 - ], - "eccCorrelation": 0.99798, - "transform": { - "scaleSqrtDet": 1.00131, - "rotationDeg": -0.001, - "translateX": -0.141, - "translateY": -3.301, - "matrix": [ - [ - 1.000428, - -0.000176, - -0.141357 - ], - [ - -1.9e-05, - 1.002198, - -3.300659 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 554683, - "chamferInMask": { - "median": 0.0, - "p90": 3.0, - "nSamples": 59193 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p4-linocut/base/edits/aligned-body-2.json b/docs/avatar-art/p4-linocut/base/edits/aligned-body-2.json deleted file mode 100644 index d59d233..0000000 --- a/docs/avatar-art/p4-linocut/base/edits/aligned-body-2.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\body-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\aligned-body-2.png", - "refSize": [ - 1024, - 1536 - ], - "editedOriginalSize": [ - 1024, - 1536 - ], - "eccCorrelation": 0.99671, - "transform": { - "scaleSqrtDet": 1.00221, - "rotationDeg": 0.0, - "translateX": 0.229, - "translateY": -5.606, - "matrix": [ - [ - 1.000477, - -0.000566, - 0.229476 - ], - [ - 9e-06, - 1.003949, - -5.605988 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 555167, - "chamferInMask": { - "median": 0.0, - "p90": 3.0, - "nSamples": 60472 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p4-linocut/base/edits/aligned-faceless-1.json b/docs/avatar-art/p4-linocut/base/edits/aligned-faceless-1.json deleted file mode 100644 index 8af694d..0000000 --- a/docs/avatar-art/p4-linocut/base/edits/aligned-faceless-1.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\faceless-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\aligned-faceless-1.png", - "refSize": [ - 1024, - 1536 - ], - "editedOriginalSize": [ - 1024, - 1536 - ], - "eccCorrelation": 0.99622, - "transform": { - "scaleSqrtDet": 0.99978, - "rotationDeg": 0.003, - "translateX": 0.238, - "translateY": -0.442, - "matrix": [ - [ - 0.999337, - -1.3e-05, - 0.238082 - ], - [ - 4.8e-05, - 1.00023, - -0.441719 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 849049, - "chamferInMask": { - "median": 0.0, - "p90": 1.414, - "nSamples": 100203 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 6849 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 319, - 506, - 465, - 595 - ], - "refInkRatio": 0.1879, - "editedInkRatio": 0.0 - }, - "eyeB": { - "box": [ - 550, - 501, - 696, - 590 - ], - "refInkRatio": 0.1823, - "editedInkRatio": 0.0 - }, - "browA": { - "box": [ - 273, - 434, - 481, - 534 - ], - "refInkRatio": 0.0979, - "editedInkRatio": 0.015 - }, - "browB": { - "box": [ - 531, - 427, - 742, - 532 - ], - "refInkRatio": 0.0989, - "editedInkRatio": 0.0125 - }, - "mouth": { - "box": [ - 409, - 738, - 608, - 852 - ], - "refInkRatio": 0.0547, - "editedInkRatio": 0.0 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 319, - 506, - 465, - 595 - ], - "eyeB": [ - 550, - 501, - 696, - 590 - ], - "browA": [ - 273, - 434, - 481, - 534 - ], - "browB": [ - 531, - 427, - 742, - 532 - ], - "mouth": [ - 409, - 738, - 608, - 852 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p4-linocut/base/edits/aligned-faceless-2.json b/docs/avatar-art/p4-linocut/base/edits/aligned-faceless-2.json deleted file mode 100644 index eba07d4..0000000 --- a/docs/avatar-art/p4-linocut/base/edits/aligned-faceless-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\faceless-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\aligned-faceless-2.png", - "refSize": [ - 1024, - 1536 - ], - "editedOriginalSize": [ - 1024, - 1536 - ], - "eccCorrelation": 0.99715, - "transform": { - "scaleSqrtDet": 1.00005, - "rotationDeg": 0.002, - "translateX": -0.14, - "translateY": -0.529, - "matrix": [ - [ - 0.999762, - 5e-06, - -0.140065 - ], - [ - 2.8e-05, - 1.000345, - -0.529061 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 849049, - "chamferInMask": { - "median": 0.0, - "p90": 2.0, - "nSamples": 107586 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 7307 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 319, - 506, - 465, - 595 - ], - "refInkRatio": 0.1879, - "editedInkRatio": 0.0 - }, - "eyeB": { - "box": [ - 550, - 501, - 696, - 590 - ], - "refInkRatio": 0.1823, - "editedInkRatio": 0.0 - }, - "browA": { - "box": [ - 273, - 434, - 481, - 534 - ], - "refInkRatio": 0.0979, - "editedInkRatio": 0.0147 - }, - "browB": { - "box": [ - 531, - 427, - 742, - 532 - ], - "refInkRatio": 0.0989, - "editedInkRatio": 0.0126 - }, - "mouth": { - "box": [ - 409, - 738, - 608, - 852 - ], - "refInkRatio": 0.0547, - "editedInkRatio": 0.0 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 319, - 506, - 465, - 595 - ], - "eyeB": [ - 550, - 501, - 696, - 590 - ], - "browA": [ - 273, - 434, - 481, - 534 - ], - "browB": [ - 531, - 427, - 742, - 532 - ], - "mouth": [ - 409, - 738, - 608, - 852 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p4-linocut/base/edits/sheet-face-zoom.jpg b/docs/avatar-art/p4-linocut/base/edits/sheet-face-zoom.jpg deleted file mode 100644 index 5aa5dcc..0000000 Binary files a/docs/avatar-art/p4-linocut/base/edits/sheet-face-zoom.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/base/edits/sheet.jpg b/docs/avatar-art/p4-linocut/base/edits/sheet.jpg deleted file mode 100644 index bc3af55..0000000 Binary files a/docs/avatar-art/p4-linocut/base/edits/sheet.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/base/normalize.json b/docs/avatar-art/p4-linocut/base/normalize.json deleted file mode 100644 index 3eeb26a..0000000 --- a/docs/avatar-art/p4-linocut/base/normalize.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "canvas": { - "w": 1005, - "h": 1566 - }, - "reference": { - "image": "docs\\avatar-art\\p1-linocut\\base\\base-front.png", - "eyesCenter": [ - 492.69, - 584.33 - ], - "chin": [ - 500.61, - 919.55 - ], - "eyeDistance": 215.427, - "eyeChinDistance": 335.308 - }, - "input": { - "front": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\source-front.png", - "faceless": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\aligned-faceless-1.png", - "body": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\base\\edits\\aligned-body-1.png", - "frontSize": [ - 1024, - 1536 - ], - "eyesCenter": [ - 507.78, - 552.52 - ], - "chin": [ - 512.62, - 915.91 - ], - "eyeDistance": 231.073, - "eyeChinDistance": 363.428 - }, - "transform": { - "sHead": 0.927458, - "sWidth": 0.981445, - "s": 0.981445, - "rotationDeg": 0.0, - "offsetXNatural": -5.667, - "offsetXClampRange": [ - 0.0, - 0.0 - ], - "offsetX": 0.0, - "offsetY": 42.064 - }, - "coveredRange": { - "x": [ - 0.0, - 1005 - ], - "y": [ - 42.1, - 1549.6 - ] - }, - "inputSha256": { - "front": "b19f4a737e976352dc27adf1ec63d14c777c141acfb90685605f21c1be9921f5", - "faceless": "b2e6111c745e54dc6ba3eb43f67b4da5b07cf9ce42082c0951e27908f7d9a6be", - "body": "fbec8186707a8150c8989ed0e887714611bfe55d46f5cf760b0aafecc96f17ef" - } -} \ No newline at end of file diff --git a/docs/avatar-art/p4-linocut/manifest.json b/docs/avatar-art/p4-linocut/manifest.json deleted file mode 100644 index ad9b7af..0000000 --- a/docs/avatar-art/p4-linocut/manifest.json +++ /dev/null @@ -1,1216 +0,0 @@ -{ - "landmarks": { - "coordSystem": "screen (image pixel: x=0 좌측, y=0 상단; left=작은 x, right=큰 x; 인물 해부학적 좌우 아님)", - "eyeLeft": { - "innerCorner": [ - 430.65, - 594.22 - ], - "outerCorner": [ - 337.59, - 576.66 - ], - "upperLidTop": [ - 375.88, - 563.92 - ], - "lowerLidBottom": [ - 380.92, - 598.49 - ], - "iris": { - "center": [ - 384.73, - 580.01 - ], - "radius": 21.31 - } - }, - "eyeRight": { - "innerCorner": [ - 566.39, - 593.44 - ], - "outerCorner": [ - 659.22, - 572.86 - ], - "upperLidTop": [ - 621.39, - 561.68 - ], - "lowerLidBottom": [ - 616.77, - 596.57 - ], - "iris": { - "center": [ - 613.31, - 577.95 - ], - "radius": 21.41 - } - }, - "eyebrowLeft": { - "inner": [ - 449.35, - 524.62 - ], - "peak": [ - 353.25, - 508.35 - ], - "outer": [ - 303.65, - 507.6 - ] - }, - "eyebrowRight": { - "inner": [ - 540.92, - 523.73 - ], - "peak": [ - 642.6, - 502.27 - ], - "outer": [ - 695.08, - 500.19 - ] - }, - "noseTip": [ - 498.62, - 728.19 - ], - "mouthCornerLeft": [ - 426.22, - 819.4 - ], - "mouthCornerRight": [ - 571.46, - 818.64 - ], - "upperLipTopCenter": [ - 497.87, - 791.61 - ], - "lowerLipBottomCenter": [ - 498.73, - 852.31 - ], - "chinTip": [ - 502.66, - 940.61 - ], - "faceWidthAtEyeLevelLeft": [ - 268.63, - 621.0 - ], - "faceWidthAtEyeLevelRight": [ - 734.35, - 616.97 - ] - }, - "landmarkDetector": "mediapipe FaceLandmarker (tasks) 1.0.1, model=face_landmarker(float16, v1)", - "pipelineRun": { - "persona": "P4", - "timestamp": "2026-10-01T22:01:29", - "completed": true, - "stages": [ - { - "stage": "brow_sprite", - "ok": true, - "elapsedSec": 2.66 - }, - { - "stage": "paper_grain", - "ok": true, - "elapsedSec": 2.27 - }, - { - "stage": "lip_texture", - "ok": true, - "elapsedSec": 2.11 - }, - { - "stage": "jaw_pieces", - "ok": true, - "elapsedSec": 2.16 - }, - { - "stage": "export_rig", - "ok": true, - "elapsedSec": 16.36 - }, - { - "stage": "final_previews", - "ok": true, - "elapsedSec": 19.22 - } - ] - }, - "browCenterline": { - "method": "Otsu(front lum in bbox) AND diff(F-front)>=25, morphological closing to merge hatching, single global degree-2 polyfit on column-weighted centroid with one-pass outlier removal", - "diffThresh": 25.0, - "bboxPad": { - "x": 25.0, - "yUp": 42.0, - "yDown": 14.0 - }, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "outlierStdMult": 2.0, - "edgeInsetFrac": 0.03, - "peakWindow": [ - 0.25, - 0.6 - ], - "peakFallbackFrac": 0.35, - "peakFlatHeightPx": 3.0, - "peakFailEdgeFrac": 0.2, - "oldPoints": { - "browLeft": { - "inner": [ - 446.99, - 539.65 - ], - "peak": [ - 342.01, - 492.32 - ], - "outer": [ - 291.42, - 518.67 - ] - }, - "browRight": { - "inner": [ - 548.3, - 538.45 - ], - "peak": [ - 652.48, - 487.92 - ], - "outer": [ - 706.06, - 512.97 - ] - } - }, - "newPoints": { - "browLeft": { - "name": "browLeft", - "bbox": [ - 266, - 450, - 472, - 554 - ], - "otsuThresh": 121.0, - "colRange": [ - 299, - 454 - ], - "width": 155.0, - "nColumns": 156, - "nOutliersRemoved": 4, - "fitCoeffs": [ - 0.00105736, - -0.67933648, - 616.38397622 - ], - "inner": [ - 449.35, - 524.62 - ], - "peak": [ - 353.25, - 508.35 - ], - "outer": [ - 303.65, - 507.6 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.9295, - "checkA_pass": true, - "halfWidthInner": 6.756, - "halfWidthPeak": 6.485, - "halfWidthOuter": 2.498, - "bandX0": 266, - "bandY0": 450, - "colMin": 299, - "colMax": 454 - }, - "browRight": { - "name": "browRight", - "bbox": [ - 523, - 446, - 731, - 552 - ], - "otsuThresh": 121.0, - "colRange": [ - 536, - 700 - ], - "width": 164.0, - "nColumns": 165, - "nOutliersRemoved": 7, - "fitCoeffs": [ - 0.00111283, - -1.52815002, - 1024.73237057 - ], - "inner": [ - 540.92, - 523.73 - ], - "peak": [ - 642.6, - 502.27 - ], - "outer": [ - 695.08, - 500.19 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.903, - "checkA_pass": true, - "halfWidthInner": 2.378, - "halfWidthPeak": 6.718, - "halfWidthOuter": 1.5, - "bandX0": 523, - "bandY0": 446, - "colMin": 536, - "colMax": 700 - } - }, - "halfWidthLeft": [ - 6.756, - 6.485, - 2.498 - ], - "halfWidthRight": [ - 2.378, - 6.718, - 1.5 - ], - "halfWidthNote": "원화 눈썹 잉크 띠 반두께[inner,peak,outer](px, 중심선 법선 방향). export_rig.py가 persona.json browHalfWidthFromArt!=false(기본)면 RigBrow.halfWidth로 내보낸다.", - "checkB_innerMinusPeakY": { - "browLeft": 16.27, - "browRight": 21.46 - }, - "checkD_symmetry": { - "innerYDiff": 0.89, - "peakYDiff": 6.08 - }, - "evidenceImage": "preview/v2/brow-centerline.jpg" - }, - "schemaVersion": "vignette.avatar.v3.layers.v1", - "persona": "P4", - "canvas": { - "w": 1005, - "h": 1566 - }, - "base": { - "front": "base/base-front.png", - "faceless": "base/base-faceless.png", - "facelessPadded": "base/base-faceless-padded.png", - "facelessSize": [ - 1005, - 1566 - ], - "padNote": "base-faceless.png 마지막 행을 0번 복제해 1005x1566(base-front.png 크기)으로 패딩한 것이 F다." - }, - "segmenter": { - "model": "selfie_multiclass_256x256.tflite (mediapipe ImageSegmenter, storage.googleapis.com)", - "categories": { - "0": "background", - "1": "hair", - "2": "bodySkin", - "3": "faceSkin", - "4": "clothes", - "5": "others" - }, - "categoryPixelCounts": { - "0": 614690, - "1": 147103, - "2": 69058, - "3": 257454, - "4": 475638, - "5": 9887 - }, - "hairEdgeRefine": { - "dilatePx": 6, - "lumThreshold": 110, - "addedPixels": 9030 - } - }, - "chinLine": { - "landmarkIndex": 152, - "xy": [ - 502.66, - 940.61 - ], - "marginPx": 8, - "cutY": 948.61 - }, - "faceOval": { - "landmarkLoop": [ - 10, - 338, - 297, - 332, - 284, - 251, - 389, - 356, - 454, - 323, - 361, - 288, - 397, - 365, - 379, - 378, - 400, - 377, - 152, - 148, - 176, - 149, - 150, - 136, - 172, - 58, - 132, - 93, - 234, - 127, - 162, - 21, - 54, - 103, - 67, - 109 - ], - "scale": 1.04, - "sourceImage": "base/base-front.png" - }, - "layers": [ - { - "id": "body", - "file": "layers/body.png", - "source": "base-faceless-masked+regenerated-fill", - "alphaBBox": [ - 0, - 436, - 1005, - 1566 - ], - "opaquePixels": 566179, - "greenResidue": 0, - "fillTargetPixels": 25601, - "fillSourceRaw": "raw/body.png (rejected 재생성이 아니라 수용된 body.png의 크로마키 결과; head/hairFront와 달리 body 재생성은 승인됨)" - }, - { - "id": "head", - "file": "layers/head.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 171, - 114, - 822, - 1011 - ], - "opaquePixels": 405288, - "greenResidue": 0 - }, - { - "id": "hairFront", - "file": "layers/hairFront.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 250, - 405, - 751, - 856 - ], - "opaquePixels": 7245, - "greenResidue": 0 - } - ], - "rejectedRawEdits": { - "body": { - "file": "raw/body.png", - "status": "accepted-as-fill-source", - "reason": "정렬 (0,0), 형태 변형 없음 — head_mask 채움 전용 소스로 재사용" - } - }, - "composite": { - "meanAbsDiff": { - "full": 2.063827732347204, - "face": 0.31170282724581405, - "hairOutline": 10.115052289163106 - }, - "target": { - "full": 3.0, - "pass": true - }, - "backgroundNote": "카테고리0(배경)은 종이결 텍스처 노이즈(표준편차 ~29/채널)를 포함해 평탄한 크림(#EEE5D3)과 자체적으로 평균절대차 ~7 차이가 난다." - }, - "layersV2": { - "chinBandFix": { - "targetRegionPixels": 15310, - "sourceBandHeight": 150, - "rowJitterRange": [ - -2.0, - 2.0 - ], - "rngSeed": 20260930 - }, - "hairEdgeFix": { - "bandOuterPx": 40, - "bandInnerPx": 6, - "lumSmoothstep": [ - 205.0, - 150.0 - ], - "minFragmentAreaPx": 60, - "head": { - "bandOuterPixels": 91955, - "bandInnerPixels": 28677, - "componentsBefore": 203, - "removed": 197, - "removedPixels": 1271 - }, - "hairFront": { - "bandOuterPixels": 10689, - "bandInnerPixels": 9516, - "componentsBefore": 228, - "removed": 220, - "removedPixels": 983 - } - }, - "hairPaperGapFix": { - "regionDilatePx": 6, - "paperColorDistThreshold": 40.0, - "paperLumThreshold": 195.0, - "paperSatThreshold": 40.0, - "opaqueAlphaThreshold": 0.95, - "regionPixels": 162151, - "head": { - "targetPixels": 943, - "componentsBefore": 6, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - }, - "hairFront": { - "targetPixels": 1, - "componentsBefore": 8, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - } - }, - "jawInkFix": { - "mouthCenterY": 815.65, - "distThresholdPx": 14.0, - "mergePx": 3, - "aspectThreshold": 1.0, - "removedGroups": [ - { - "pixels": 237, - "bbox": [ - 308, - 816, - 342, - 843 - ], - "width": 34, - "height": 27, - "aspect": 1.259 - } - ], - "keptGroups": [ - { - "pixels": 755, - "bbox": [ - 650, - 816, - 686, - 857 - ], - "width": 36, - "height": 41, - "aspect": 0.878 - } - ], - "removedPixels": 237, - "strayFragmentCleanup": { - "componentsBefore": 8, - "removed": 0, - "removedPixels": 0 - } - }, - "metrics": { - "body": { - "haloPct": 1.1048826062230888 - }, - "head": { - "haloPct": 0.4306284483918719, - "bgLeakAlphaMean": 0.0002120053053522035, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - }, - "hairFront": { - "haloPct": 0.0, - "bgLeakAlphaMean": 0.0, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - } - }, - "layers": [ - { - "id": "body", - "bbox": [ - 0, - 436, - 1005, - 1566 - ], - "opaque": 528300, - "greenResidue": 0 - }, - { - "id": "head", - "bbox": [ - 161, - 97, - 822, - 1021 - ], - "opaque": 412754, - "greenResidue": 0 - }, - { - "id": "hairFront", - "bbox": [ - 234, - 405, - 768, - 890 - ], - "opaque": 11323, - "greenResidue": 0 - } - ], - "faceDetail": { - "browLandmarksSource": "manifest.landmarks.eyebrowLeft/Right", - "region": "face", - "faceInsetPx": 24.0, - "matchFacelessColor": true, - "matchFacelessColorDeltaRgb": { - "eyeLeft": [ - -12.17, - -11.69, - -12.49 - ], - "eyeRight": [ - -11.08, - -10.8, - -11.58 - ], - "browLeft": [ - -4.26, - -3.81, - -5.51 - ], - "browRight": [ - -4.9, - -4.31, - -5.97 - ], - "mouth": [ - -3.64, - -3.3, - -4.77 - ] - }, - "regions": { - "eyeLeft": [ - 307.6, - 498.4, - 455.6, - 668.5 - ], - "eyeRight": [ - 541.4, - 492.3, - 689.2, - 666.6 - ], - "mouth": [ - 381.2, - 753.2, - 616.5, - 925.6 - ], - "moles": [] - }, - "exclusionPixels": { - "eyeLeft": 12156, - "eyeRight": 11941, - "browLeft": 9643, - "browRight": 9985, - "mouth": 18139, - "union": 58587 - }, - "hairMaskPixels": 159429, - "bbox": [ - 266, - 394, - 736, - 945 - ], - "textureCloneStamp": { - "cloneTargetThreshold": 65.0, - "avoidThreshold": 110.0, - "lineOpeningIterations": 3, - "featherPx": 4.0, - "badTexturePixels": 13, - "preservedShadowPixels": 0, - "components": [ - { - "componentId": 1, - "pixels": 3, - "offset": [ - 0, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 2, - "pixels": 1, - "offset": [ - -40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 3, - "pixels": 9, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - } - ] - }, - "genericPatches": { - "forehead": [ - 380, - 402, - 428, - 450 - ], - "cheekLeft": [ - 327, - 646, - 375, - 694 - ], - "cheekRight": [ - 518, - 644, - 566, - 692 - ] - }, - "genericPatchBaseline": { - "highFreqEnergy": 7.458109656082686, - "lineEnergy": 3.8710488075882026 - }, - "checkA_meanAbsDiff": 0.4965093898211362, - "checkB_seamLumDiff": 1.6296153373646405, - "checkC_holeDarkResiduePct": 0.010252729789306404, - "checkC_violationPixels": 6, - "checkD_texturePreservation": { - "eyeLeft": { - "insideEnergy": 6.2165950887128805, - "baselineEnergy": 7.458109656082686, - "ratio": 0.8335349539467751, - "pass": true - }, - "eyeRight": { - "insideEnergy": 5.8870599073096015, - "baselineEnergy": 7.458109656082686, - "ratio": 0.7893501408239865, - "pass": true - }, - "browLeft": { - "insideEnergy": 7.674279349434956, - "baselineEnergy": 7.458109656082686, - "ratio": 1.0289845152887993, - "pass": true - }, - "browRight": { - "insideEnergy": 8.00513210373071, - "baselineEnergy": 7.458109656082686, - "ratio": 1.0733459915277437, - "pass": true - }, - "mouth": { - "insideEnergy": 5.4462341485874255, - "baselineEnergy": 7.458109656082686, - "ratio": 0.7302432385323786, - "pass": true - } - }, - "checkD_allPass": true, - "checkE_ghostOutline": { - "eyeLeft": { - "bandLineEnergy": 7.300975232426853, - "baselineLineEnergy": 3.8710488075882026, - "ratio": 1.8860457708813063, - "pass": false - }, - "eyeRight": { - "bandLineEnergy": 6.426403576872062, - "baselineLineEnergy": 3.8710488075882026, - "ratio": 1.6601194912021617, - "pass": false - }, - "browLeft": { - "bandLineEnergy": 5.16448064507108, - "baselineLineEnergy": 3.8710488075882026, - "ratio": 1.3341295606884198, - "pass": true - }, - "browRight": { - "bandLineEnergy": 4.6860081826909505, - "baselineLineEnergy": 3.8710488075882026, - "ratio": 1.2105267630584322, - "pass": true - }, - "mouth": { - "bandLineEnergy": 3.2433439561515796, - "baselineLineEnergy": 3.8710488075882026, - "ratio": 0.8378463091950255, - "pass": true - } - }, - "checkE_allPass": false, - "checkDarkCircle": { - "eyeLeft": { - "compositeMean": 149.70914285714284, - "frontMean": 149.53695238095236, - "diff": 0.17219047619047956, - "pass": true - }, - "eyeRight": { - "compositeMean": 155.92419047619046, - "frontMean": 155.87638095238097, - "diff": 0.047809523809490884, - "pass": true - } - }, - "checkDarkCircle_allPass": true - }, - "paperGrain": { - "sourceImage": "base/base-front.png", - "sourceOrigin": [ - 10, - 734 - ], - "tileSize": 256, - "personMarginPx": 20, - "cleanFraction": 0.9996795654296875, - "contaminatedPixelsFilled": 21, - "chromaKeep": 0.3, - "targetLumRange": [ - 245.0, - 250.0 - ], - "lumShift": 23.586695353190095, - "finalMeanLum": 247.41994095532309, - "finalLumStd": 7.731769387649097, - "seamContinuity": { - "leftRightDiff": 0.33841442178375775, - "topBottomDiff": 0.11046582323295222 - } - } - }, - "lipTexture": { - "designVersion": "A5(아랫입술 그늘 띠를 lip-shadow 조각으로 분리)", - "landmarks": { - "mouthCornerLeft": [ - 426.22, - 819.4 - ], - "mouthCornerRight": [ - 571.46, - 818.64 - ], - "upperLipTop": [ - 497.87, - 791.61 - ], - "lowerLipBottom": [ - 498.73, - 852.31 - ], - "mouthCenter": [ - 498.3611898869276, - 815.6509176492691 - ] - }, - "capBBox": [ - 417.84, - 786.96, - 579.84, - 856.96 - ], - "otsuThreshLabA": 139.0, - "diffThresh": 20.0, - "dilatePx": 10.0, - "alphaRampPx": 4.0, - "splitOverlapPx": 2.0, - "shadowBandPx": 18.0, - "shadowTopOverlapPx": 6.0, - "shadowSideRampPx": 6.0, - "shadowTopRampPx": 4.0, - "checks": { - "lip-upper": { - "regionPixelCount": 5425, - "fullOpacityPixelCount": 4663, - "rampPixelCount": 762, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 32, - 30, - 179, - 46 - ] - }, - "lip-lower": { - "regionPixelCount": 5651, - "fullOpacityPixelCount": 4868, - "rampPixelCount": 783, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 32, - 69, - 178, - 51 - ] - }, - "lip-shadow": { - "regionPixelCount": 3264, - "fullOpacityPixelCount": 1504, - "rampPixelCount": 1760, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 42, - 67, - 159, - 61 - ] - }, - "splitOverlapPixelCount": 355, - "splitOverlapBandOk": true, - "splitOverlapAlphaMaxDiff": 0, - "splitOverlapConsistentOk": true, - "splitCoverageMismatchPixelCount": 0, - "splitCheckOk": true, - "shadowLowerOverlapPixelCount": 2539, - "shadowLowerOverlapMaxDiff": 64.72410611303343, - "shadowLowerOverlapDiffGt1PixelCount": 306, - "shadowLowerOverlapOk": false - }, - "check3NeutralComposite": { - "fullOpacityMaxDiff": 0.0, - "rampMeanAbsDiff": 4.973, - "mouthBBoxMeanAbsDiff": 1.491 - }, - "pngFileSizes": { - "lip-upper": 21921, - "lip-lower": 24895, - "lip-shadow": 26686 - }, - "canvasOrigin": { - "upper": [ - 410, - 777 - ], - "lower": [ - 410, - 816 - ], - "shadow": [ - 420, - 814 - ] - }, - "evidenceImage": "preview/v2/lip-texture.jpg", - "check4WebpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jaw": { - "designVersion": "A5(결정문 §8.4 하관 띠 변형 — 턱 조각 원본)", - "noseTip": [ - 498.62, - 728.19 - ], - "chinTip": [ - 502.66, - 940.61 - ], - "yFormula": "noseTip.y - 50 - 10 .. chinTip.y + 40", - "noseToTopPx": 60.0, - "chinToBottomPx": 40.0, - "ovalOutsetPx": 18.0, - "faceOvalSource": "export_rig.detect_face_landmarks + compute_face_oval(base-front.png, 결정적)", - "bboxCanvas": [ - 250, - 668, - 754, - 981 - ], - "naiveXRangeForComparison": [ - 250, - 754 - ], - "checks": { - "head": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 328118 - }, - "detail": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 379279 - } - }, - "evidenceImage": "preview/v2/jaw-pieces.jpg", - "check1VsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.629, - "maxAbsDiffPremultipliedRgb": 42.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.718, - "maxAbsDiffPremultipliedRgb": 36.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 14906, - "lip-lower": 17226, - "lip-shadow": 19124 - }, - "totalBytes": 51256, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 33156, - "jaw-detail": 22606 - }, - "totalBytes": 55762, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.629, - "maxAbsDiffPremultipliedRgb": 42.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.718, - "maxAbsDiffPremultipliedRgb": 36.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "browSprite": { - "enabled": true, - "bandExtraHalfHeightPx": 8.0, - "bandEndExtendPx": 6.0, - "diffThresh": 25.0, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "hairSource": "layers/v2/hairFront.png alpha(게시되는 레이어 그 자체, 분할 카테고리 아님)", - "hairAlphaThresh": 128.0, - "hairDilateIterations": 1, - "rampPx": 1.5, - "inkLumMethod": "median(ink-classified pixels in band, region after hair removal)", - "minAlphaForUnmix": 0.05, - "hairMaskPixelCount": 14569, - "left": { - "bboxCanvas": [ - 299, - 499, - 156, - 36 - ], - "halfHeightPx": 14.756, - "otsuThresh": 123.0, - "lSkin": 183.667, - "lInk": 62.0, - "regionPixelCount": 1987, - "hairOverlapPixelCount": 0, - "neutralCompositeMeanAbsDiffAlphaRegion": 2.883, - "pngFileSizeBytes": 7862 - }, - "right": { - "bboxCanvas": [ - 536, - 491, - 164, - 42 - ], - "halfHeightPx": 14.718, - "otsuThresh": 122.0, - "lSkin": 182.667, - "lInk": 60.333, - "regionPixelCount": 1987, - "hairOverlapPixelCount": 0, - "neutralCompositeMeanAbsDiffAlphaRegion": 3.014, - "pngFileSizeBytes": 7777 - }, - "canvasOrigin": { - "left": [ - 299.0, - 499.0 - ], - "right": [ - 536.0, - 491.0 - ] - }, - "evidenceImage": "preview/v2/brow-sprite.jpg", - "checkWebpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "eyeLineScale": { - "enabled": true, - "method": "눈 폭 가운데 60% 열마다 upperLidTop.y-12 ~ 홍채중심y 사이 첫 연속 어두운(lum<80) 구간 길이의 중앙값", - "windowAbovePx": 12.0, - "darkLumThresh": 80.0, - "columnMiddleFrac": 0.6, - "p1BaselinePx": 2.0, - "lineScaleClampRange": [ - 1.0, - 2.5 - ], - "left": { - "measuredPx": 10.0, - "nColumns": 57, - "ratio": 5.0, - "lineScale": 2.5 - }, - "right": { - "measuredPx": 11.0, - "nColumns": 57, - "ratio": 5.5, - "lineScale": 2.5 - } - }, - "iris": { - "method": "가로: 중심 y 부근 상하 각 4행에서 중심 x를 포함하는 연속 어두운(lum<100) 구간의 중앙값으로 반지름·중심x. 세로: 중심 x 부근 좌우 각 4열에서 중심 y를 포함하는 연속 어두운 구간의 아래쪽 끝 중앙값에서 가로 반지름을 빼 중심y(세로축은 윗눈꺼풀 잉크와 홍채가 같은 톤으로 이어져 위쪽 경계를 가를 수 없다).", - "darkLumThresh": 100.0, - "scanBandPx": 4, - "left": { - "measuredCenter": [ - 385.0, - 576.0 - ], - "measuredRadius": 20.0, - "rigCenter": [ - 384.73, - 580.01 - ], - "rigRadius": 21.31, - "nRowsHorizontal": 9, - "nColsVertical": 9 - }, - "right": { - "measuredCenter": [ - 611.5, - 574.5 - ], - "measuredRadius": 18.5, - "rigCenter": [ - 613.31, - 577.95 - ], - "rigRadius": 21.41, - "nRowsHorizontal": 9, - "nColsVertical": 9 - } - }, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 5150, - "brow-right": 4998 - }, - "totalBytes": 10148, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p4-linocut/persona.json b/docs/avatar-art/p4-linocut/persona.json deleted file mode 100644 index 09fd463..0000000 --- a/docs/avatar-art/p4-linocut/persona.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "code": "P4", - "publicSlug": "p4", - "styleFrame": "../art-direction-v3/p1/r2-b-linocut.png", - "paletteSamples": { - "ink": { "box": [350, 170, 580, 270], "lumThreshold": 55 }, - "motifPetalBoxes": [ - { "label": "sun", "kind": "ochre", "box": [1230, 10, 1536, 210] }, - { "label": "tulip", "kind": "ochre", "box": [1030, 280, 1170, 560] } - ], - "motifLeafBoxes": [ - { "label": "cloudLeft", "kind": "blue", "box": [20, 20, 380, 190] }, - { "label": "raincloud", "kind": "blue", "box": [520, 10, 930, 230] }, - { "label": "wiltedFlowerLeaf", "kind": "blue", "box": [520, 280, 650, 580] }, - { "label": "closedBudLeaf", "kind": "blue", "box": [10, 290, 110, 570] } - ] - }, - "faceDetail": { - "moles": [], - "region": "face", - "faceInsetPx": 24, - "matchFacelessColor": true - }, - "pivots": { - "neck": [502.66, 1011.06], - "face": [496.02, 654.98], - "body": [502, 1566] - }, - "crops": { - "bustYOffset": 40 - } -} diff --git a/docs/avatar-art/p4-linocut/preview/qa/face-art-vs-render.jpg b/docs/avatar-art/p4-linocut/preview/qa/face-art-vs-render.jpg deleted file mode 100644 index bd945a0..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/qa/face-art-vs-render.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/qa/face-zoom.jpg b/docs/avatar-art/p4-linocut/preview/qa/face-zoom.jpg deleted file mode 100644 index 4286bcf..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/qa/face-zoom.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/qa/neutral-vs-art.jpg b/docs/avatar-art/p4-linocut/preview/qa/neutral-vs-art.jpg deleted file mode 100644 index 9c90a72..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/qa/neutral-vs-art.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/qa/normalized.jpg b/docs/avatar-art/p4-linocut/preview/qa/normalized.jpg deleted file mode 100644 index aaa6ece..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/qa/normalized.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/qa/sheet-expr-bust.jpg b/docs/avatar-art/p4-linocut/preview/qa/sheet-expr-bust.jpg deleted file mode 100644 index 24bb25e..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/qa/sheet-expr-bust.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/qa/sheet-expr-face.jpg b/docs/avatar-art/p4-linocut/preview/qa/sheet-expr-face.jpg deleted file mode 100644 index 554dfc4..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/qa/sheet-expr-face.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/qa/sheet-viseme.jpg b/docs/avatar-art/p4-linocut/preview/qa/sheet-viseme.jpg deleted file mode 100644 index 52ce999..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/qa/sheet-viseme.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/qa/viseme-timeline.json b/docs/avatar-art/p4-linocut/preview/qa/viseme-timeline.json deleted file mode 100644 index b09caab..0000000 --- a/docs/avatar-art/p4-linocut/preview/qa/viseme-timeline.json +++ /dev/null @@ -1,287 +0,0 @@ -[ - { - "t": 0, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00000.png" - }, - { - "t": 50, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00050.png" - }, - { - "t": 100, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00100.png" - }, - { - "t": 150, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00150.png" - }, - { - "t": 200, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00200.png" - }, - { - "t": 250, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00250.png" - }, - { - "t": 300, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00300.png" - }, - { - "t": 350, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00350.png" - }, - { - "t": 400, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00400.png" - }, - { - "t": 450, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00450.png" - }, - { - "t": 500, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00500.png" - }, - { - "t": 550, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00550.png" - }, - { - "t": 600, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00600.png" - }, - { - "t": 650, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00650.png" - }, - { - "t": 700, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00700.png" - }, - { - "t": 750, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00750.png" - }, - { - "t": 800, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00800.png" - }, - { - "t": 850, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00850.png" - }, - { - "t": 900, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00900.png" - }, - { - "t": 950, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t00950.png" - }, - { - "t": 1000, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01000.png" - }, - { - "t": 1050, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01050.png" - }, - { - "t": 1100, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01100.png" - }, - { - "t": 1150, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01150.png" - }, - { - "t": 1200, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01200.png" - }, - { - "t": 1250, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01250.png" - }, - { - "t": 1300, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01300.png" - }, - { - "t": 1350, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01350.png" - }, - { - "t": 1400, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01400.png" - }, - { - "t": 1450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01450.png" - }, - { - "t": 1500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01500.png" - }, - { - "t": 1550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01550.png" - }, - { - "t": 1600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01600.png" - }, - { - "t": 1650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01650.png" - }, - { - "t": 1700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01700.png" - }, - { - "t": 1750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01750.png" - }, - { - "t": 1800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01800.png" - }, - { - "t": 1850, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01850.png" - }, - { - "t": 1900, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01900.png" - }, - { - "t": 1950, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t01950.png" - }, - { - "t": 2000, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02000.png" - }, - { - "t": 2050, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02050.png" - }, - { - "t": 2100, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02100.png" - }, - { - "t": 2150, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02150.png" - }, - { - "t": 2200, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02200.png" - }, - { - "t": 2250, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02250.png" - }, - { - "t": 2300, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02300.png" - }, - { - "t": 2350, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02350.png" - }, - { - "t": 2400, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02400.png" - }, - { - "t": 2450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02450.png" - }, - { - "t": 2500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02500.png" - }, - { - "t": 2550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02550.png" - }, - { - "t": 2600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02600.png" - }, - { - "t": 2650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02650.png" - }, - { - "t": 2700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02700.png" - }, - { - "t": 2750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02750.png" - }, - { - "t": 2800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p4-linocut\\preview\\qa\\frames\\viseme-scan-t02800.png" - } -] \ No newline at end of file diff --git a/docs/avatar-art/p4-linocut/preview/v2/brow-centerline.jpg b/docs/avatar-art/p4-linocut/preview/v2/brow-centerline.jpg deleted file mode 100644 index cbf15c5..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/v2/brow-centerline.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/v2/brow-sprite.jpg b/docs/avatar-art/p4-linocut/preview/v2/brow-sprite.jpg deleted file mode 100644 index 419a67b..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/v2/brow-sprite.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/v2/export-rig-report.json b/docs/avatar-art/p4-linocut/preview/v2/export-rig-report.json deleted file mode 100644 index 230a7ee..0000000 --- a/docs/avatar-art/p4-linocut/preview/v2/export-rig-report.json +++ /dev/null @@ -1,676 +0,0 @@ -{ - "faceOvalBBox": [ - 252.2, - 388.0, - 748.9, - 951.6 - ], - "cropsFace": [ - 184.9, - 354.2, - 631.2, - 631.2 - ], - "mouthCenter": [ - 498.4, - 815.7 - ], - "palette": { - "ink": "#22211F", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#B48460", - "lipLower": "#C08E69", - "mouthLine": "#332B26", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "paletteSamples": { - "eyeLeft": { - "sclera": "#D0AB82", - "iris": "#292926", - "irisRing": "#886E5B", - "n": { - "sclera": 1146, - "iris": 204, - "irisRing": 420 - } - }, - "eyeRight": { - "sclera": "#CFAA82", - "iris": "#2E2D2A", - "irisRing": "#8C705A", - "n": { - "sclera": 1077, - "iris": 208, - "irisRing": 424 - } - }, - "scleraIrisIrisRingFixedOverride": { - "reason": "persona.json paletteSamples.eyeOverride(고정 설계값)", - "measured": { - "sclera": "#D0AA82", - "iris": "#2C2B28", - "irisRing": "#8A705A" - }, - "fixed": { - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F" - } - }, - "ink": { - "box": [ - 350, - 170, - 580, - 270 - ], - "lumThreshold": 55.0, - "n": 16599, - "hex": "#22211F" - }, - "lipUpper": { - "box": [ - 421, - 788, - 576, - 813 - ], - "hex": "#B48460" - }, - "lipLower": { - "box": [ - 421, - 817, - 576, - 855 - ], - "hex": "#C08E69" - }, - "mouthLine": { - "box": [ - 441, - 813, - 556, - 818 - ], - "hex": "#332B26" - }, - "motifPetal": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "sun": { - "box": [ - 1230, - 10, - 1536, - 210 - ], - "n": 20570, - "hex": "#D3A661" - }, - "tulip": { - "box": [ - 1030, - 280, - 1170, - 560 - ], - "n": 4314, - "hex": "#CCA063" - } - }, - "hex": "#D0A362" - }, - "motifLeaf": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "cloudLeft": { - "box": [ - 20, - 20, - 380, - 190 - ], - "n": 21312, - "hex": "#636D76" - }, - "raincloud": { - "box": [ - 520, - 10, - 930, - 230 - ], - "n": 27607, - "hex": "#586B76" - }, - "wiltedFlowerLeaf": { - "box": [ - 520, - 280, - 650, - 580 - ], - "n": 5818, - "hex": "#495961" - }, - "closedBudLeaf": { - "box": [ - 10, - 290, - 110, - 570 - ], - "n": 6589, - "hex": "#495961" - } - }, - "hex": "#53626C" - } - }, - "publish": { - "fileSizes": { - "body": 137422, - "head": 116202, - "hair-front": 19270, - "face-detail": 50508, - "paper-grain": 2428 - }, - "encodeMeta": { - "quality": 80, - "scale": 1.0, - "totalBytes": 325830 - }, - "totalBytes": 325830 - }, - "grain": { - "href": "/avatar/v3/p4/paper-grain.webp", - "size": 256 - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 14906, - "lip-lower": 17226, - "lip-shadow": 19124 - }, - "totalBytes": 51256, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 33156, - "jaw-detail": 22606 - }, - "totalBytes": 55762, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.629, - "maxAbsDiffPremultipliedRgb": 42.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.718, - "maxAbsDiffPremultipliedRgb": 36.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 5150, - "brow-right": 4998 - }, - "totalBytes": 10148, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "rig": { - "schemaVersion": "vignette.avatar.v3.rig.v1", - "persona": "P4", - "canvas": { - "w": 1005, - "h": 1566 - }, - "layers": { - "body": { - "href": "/avatar/v3/p4/body.webp", - "x": 0, - "y": 436, - "w": 1005, - "h": 1130 - }, - "head": { - "href": "/avatar/v3/p4/head.webp", - "x": 161, - "y": 97, - "w": 661, - "h": 924 - }, - "hairFront": { - "href": "/avatar/v3/p4/hair-front.webp", - "x": 234, - "y": 405, - "w": 534, - "h": 485 - }, - "faceDetail": { - "href": "/avatar/v3/p4/face-detail.webp", - "x": 266, - "y": 394, - "w": 470, - "h": 551 - } - }, - "grain": { - "href": "/avatar/v3/p4/paper-grain.webp", - "size": 256 - }, - "lipTexture": { - "upper": { - "href": "/avatar/v3/p4/lip-upper.webp", - "x": 410, - "y": 777, - "w": 179, - "h": 46 - }, - "lower": { - "href": "/avatar/v3/p4/lip-lower.webp", - "x": 410, - "y": 816, - "w": 178, - "h": 51 - }, - "shadow": { - "href": "/avatar/v3/p4/lip-shadow.webp", - "x": 420, - "y": 814, - "w": 159, - "h": 61 - } - }, - "jaw": { - "head": { - "href": "/avatar/v3/p4/jaw-head.webp", - "x": 250, - "y": 668, - "w": 504, - "h": 313 - }, - "detail": { - "href": "/avatar/v3/p4/jaw-detail.webp", - "x": 250, - "y": 668, - "w": 504, - "h": 313 - } - }, - "motif": null, - "pivots": { - "neck": [ - 502.66, - 1011.06 - ], - "body": [ - 502, - 1566 - ], - "face": [ - 496.02, - 654.98 - ] - }, - "crops": { - "portrait": [ - 0, - 0, - 1005, - 1566 - ], - "bust": [ - 0.0, - 40.0, - 1005.0, - 1005.0 - ], - "face": [ - 184.90839997490644, - 354.1878642927646, - 631.1857242919922, - 631.1857242919922 - ] - }, - "faceOval": [ - [ - 495.7, - 388.0 - ], - [ - 569.6, - 388.6 - ], - [ - 631.4, - 396.4 - ], - [ - 687.2, - 414.1 - ], - [ - 721.3, - 442.7 - ], - [ - 739.9, - 479.9 - ], - [ - 748.2, - 519.6 - ], - [ - 748.9, - 568.1 - ], - [ - 743.7, - 615.0 - ], - [ - 735.8, - 663.9 - ], - [ - 723.6, - 715.1 - ], - [ - 704.8, - 769.9 - ], - [ - 681.9, - 816.1 - ], - [ - 657.7, - 851.5 - ], - [ - 628.5, - 883.7 - ], - [ - 603.9, - 907.3 - ], - [ - 576.9, - 929.1 - ], - [ - 544.6, - 946.1 - ], - [ - 502.7, - 951.6 - ], - [ - 461.8, - 946.3 - ], - [ - 430.4, - 929.7 - ], - [ - 403.7, - 908.3 - ], - [ - 378.7, - 885.5 - ], - [ - 348.8, - 854.0 - ], - [ - 324.0, - 819.1 - ], - [ - 300.1, - 773.9 - ], - [ - 280.7, - 719.0 - ], - [ - 267.6, - 667.8 - ], - [ - 259.3, - 619.2 - ], - [ - 252.7, - 572.5 - ], - [ - 252.2, - 524.3 - ], - [ - 258.8, - 484.1 - ], - [ - 275.1, - 446.8 - ], - [ - 306.7, - 417.8 - ], - [ - 360.2, - 398.8 - ], - [ - 421.5, - 390.1 - ] - ], - "landmarks": { - "eyeLeft": { - "inner": [ - 430.6, - 594.2 - ], - "outer": [ - 337.6, - 576.7 - ], - "upperLidTop": [ - 375.9, - 563.9 - ], - "lowerLidBottom": [ - 380.9, - 598.5 - ], - "iris": { - "center": [ - 385.0, - 576.0 - ], - "radius": 20.0 - }, - "lineScale": 2.5 - }, - "eyeRight": { - "inner": [ - 566.4, - 593.4 - ], - "outer": [ - 659.2, - 572.9 - ], - "upperLidTop": [ - 621.4, - 561.7 - ], - "lowerLidBottom": [ - 616.8, - 596.6 - ], - "iris": { - "center": [ - 611.5, - 574.5 - ], - "radius": 18.5 - }, - "lineScale": 2.5 - }, - "browLeft": { - "inner": [ - 449.4, - 524.6 - ], - "peak": [ - 353.2, - 508.4 - ], - "outer": [ - 303.6, - 507.6 - ], - "halfWidth": [ - 6.76, - 6.49, - 2.5 - ], - "sprite": { - "href": "/avatar/v3/p4/brow-left.webp", - "x": 299.0, - "y": 499.0, - "w": 156, - "h": 36 - } - }, - "browRight": { - "inner": [ - 540.9, - 523.7 - ], - "peak": [ - 642.6, - 502.3 - ], - "outer": [ - 695.1, - 500.2 - ], - "halfWidth": [ - 2.38, - 6.72, - 1.5 - ], - "sprite": { - "href": "/avatar/v3/p4/brow-right.webp", - "x": 536.0, - "y": 491.0, - "w": 164, - "h": 42 - } - }, - "noseTip": [ - 498.6, - 728.2 - ], - "mouthCornerLeft": [ - 426.2, - 819.4 - ], - "mouthCornerRight": [ - 571.5, - 818.6 - ], - "upperLipTop": [ - 497.9, - 791.6 - ], - "lowerLipBottom": [ - 498.7, - 852.3 - ], - "mouthCenter": [ - 498.4, - 815.7 - ], - "chinTip": [ - 502.7, - 940.6 - ] - }, - "palette": { - "ink": "#22211F", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#B48460", - "lipLower": "#C08E69", - "mouthLine": "#332B26", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "backdrop": { - "cognitive": "#ECE3D1", - "positive": "#F1DEC2", - "negative": "#DCE0E2", - "defensive": "#E6DAD3", - "energy": "#E2E0D0" - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p4-linocut/preview/v2/ghost-check.jpg b/docs/avatar-art/p4-linocut/preview/v2/ghost-check.jpg deleted file mode 100644 index 04c4fff..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/v2/ghost-check.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/v2/holes-texture.jpg b/docs/avatar-art/p4-linocut/preview/v2/holes-texture.jpg deleted file mode 100644 index 33b531e..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/v2/holes-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/v2/jaw-pieces.jpg b/docs/avatar-art/p4-linocut/preview/v2/jaw-pieces.jpg deleted file mode 100644 index f4de558..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/v2/jaw-pieces.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/preview/v2/lip-texture.jpg b/docs/avatar-art/p4-linocut/preview/v2/lip-texture.jpg deleted file mode 100644 index b318e89..0000000 Binary files a/docs/avatar-art/p4-linocut/preview/v2/lip-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p4-linocut/prompts/base-faceless.txt b/docs/avatar-art/p4-linocut/prompts/base-faceless.txt deleted file mode 100644 index 0c5e914..0000000 --- a/docs/avatar-art/p4-linocut/prompts/base-faceless.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as faceless.png: - -Redraw exactly the same girl, same frontal pose, same framing and same linocut relief-print style as the reference, but leave the face blank: remove the eyes, eyebrows and mouth completely and fill those areas with plain skin in the same flat skin color, keeping the nose, the face outline, the ears, the hair, the neck, the pencil tucked behind her ear, the white shirt collar and the navy crew-neck sweater exactly as they are. Do not move, resize or redraw anything else. Plain flat cream background, nothing else, no text. diff --git a/docs/avatar-art/p4-linocut/prompts/body.txt b/docs/avatar-art/p4-linocut/prompts/body.txt deleted file mode 100644 index b868b0a..0000000 --- a/docs/avatar-art/p4-linocut/prompts/body.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as body.png: - -Keep only the neck, the white shirt collar and the navy crew-neck sweater exactly as they are in the reference: same shapes, same position, same linocut relief-print style, same ink colors and texture. Remove the head, the face, the ears and all of the hair completely. Where the hair or the chin previously covered the neck or the clothes, continue them naturally in the same style so there are no holes. Place the result on a plain flat pure green background (#00ff00) with nothing else, no text. diff --git a/docs/avatar-art/p4-linocut/raw/body.png b/docs/avatar-art/p4-linocut/raw/body.png deleted file mode 100644 index 42ec9f8..0000000 Binary files a/docs/avatar-art/p4-linocut/raw/body.png and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/base/base-faceless.png b/docs/avatar-art/p5-linocut/base/base-faceless.png deleted file mode 100644 index 1cb8a01..0000000 Binary files a/docs/avatar-art/p5-linocut/base/base-faceless.png and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/base/base-front.png b/docs/avatar-art/p5-linocut/base/base-front.png deleted file mode 100644 index 502e24f..0000000 Binary files a/docs/avatar-art/p5-linocut/base/base-front.png and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/base/edits/aligned-body-1.json b/docs/avatar-art/p5-linocut/base/edits/aligned-body-1.json deleted file mode 100644 index 620cdc4..0000000 --- a/docs/avatar-art/p5-linocut/base/edits/aligned-body-1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\body-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\aligned-body-1.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.89496, - "transform": { - "scaleSqrtDet": 1.00107, - "rotationDeg": 0.009, - "translateX": 0.868, - "translateY": -2.827, - "matrix": [ - [ - 1.000537, - -0.000853, - 0.867875 - ], - [ - 0.000151, - 1.001599, - -2.827145 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 551610, - "chamferInMask": { - "median": 0.0, - "p90": 2.0, - "nSamples": 76942 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p5-linocut/base/edits/aligned-body-2.json b/docs/avatar-art/p5-linocut/base/edits/aligned-body-2.json deleted file mode 100644 index f2902b7..0000000 --- a/docs/avatar-art/p5-linocut/base/edits/aligned-body-2.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\body-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\aligned-body-2.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.99029, - "transform": { - "scaleSqrtDet": 1.00132, - "rotationDeg": 0.005, - "translateX": -0.386, - "translateY": -3.197, - "matrix": [ - [ - 1.000755, - 2.9e-05, - -0.385584 - ], - [ - 9.5e-05, - 1.001881, - -3.197296 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 540648, - "chamferInMask": { - "median": 0.0, - "p90": 1.414, - "nSamples": 74241 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p5-linocut/base/edits/aligned-faceless-1.json b/docs/avatar-art/p5-linocut/base/edits/aligned-faceless-1.json deleted file mode 100644 index 69c1d57..0000000 --- a/docs/avatar-art/p5-linocut/base/edits/aligned-faceless-1.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\faceless-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\aligned-faceless-1.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.99531, - "transform": { - "scaleSqrtDet": 0.99998, - "rotationDeg": 0.004, - "translateX": -0.129, - "translateY": -0.135, - "matrix": [ - [ - 1.000166, - -9.9e-05, - -0.128815 - ], - [ - 6.5e-05, - 0.999797, - -0.135042 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 828124, - "chamferInMask": { - "median": 0.0, - "p90": 1.0, - "nSamples": 116456 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 5503 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 398, - 453, - 539, - 533 - ], - "refInkRatio": 0.1947, - "editedInkRatio": 0.0 - }, - "eyeB": { - "box": [ - 614, - 452, - 754, - 533 - ], - "refInkRatio": 0.1799, - "editedInkRatio": 0.0 - }, - "browA": { - "box": [ - 352, - 385, - 557, - 475 - ], - "refInkRatio": 0.2908, - "editedInkRatio": 0.1291 - }, - "browB": { - "box": [ - 599, - 387, - 802, - 476 - ], - "refInkRatio": 0.3371, - "editedInkRatio": 0.181 - }, - "mouth": { - "box": [ - 475, - 653, - 672, - 769 - ], - "refInkRatio": 0.1209, - "editedInkRatio": 0.0 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 398, - 453, - 539, - 533 - ], - "eyeB": [ - 614, - 452, - 754, - 533 - ], - "browA": [ - 352, - 385, - 557, - 475 - ], - "browB": [ - 599, - 387, - 802, - 476 - ], - "mouth": [ - 475, - 653, - 672, - 769 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p5-linocut/base/edits/aligned-faceless-2.json b/docs/avatar-art/p5-linocut/base/edits/aligned-faceless-2.json deleted file mode 100644 index f911922..0000000 --- a/docs/avatar-art/p5-linocut/base/edits/aligned-faceless-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\faceless-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\aligned-faceless-2.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.99558, - "transform": { - "scaleSqrtDet": 0.99969, - "rotationDeg": 0.004, - "translateX": 0.599, - "translateY": 0.017, - "matrix": [ - [ - 0.999723, - -0.000189, - 0.599433 - ], - [ - 6.2e-05, - 0.999659, - 0.017416 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 828124, - "chamferInMask": { - "median": 0.0, - "p90": 1.0, - "nSamples": 112934 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 5305 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 398, - 453, - 539, - 533 - ], - "refInkRatio": 0.1947, - "editedInkRatio": 0.0 - }, - "eyeB": { - "box": [ - 614, - 452, - 754, - 533 - ], - "refInkRatio": 0.1799, - "editedInkRatio": 0.0 - }, - "browA": { - "box": [ - 352, - 385, - 557, - 475 - ], - "refInkRatio": 0.2908, - "editedInkRatio": 0.1199 - }, - "browB": { - "box": [ - 599, - 387, - 802, - 476 - ], - "refInkRatio": 0.3371, - "editedInkRatio": 0.1758 - }, - "mouth": { - "box": [ - 475, - 653, - 672, - 769 - ], - "refInkRatio": 0.1209, - "editedInkRatio": 0.0 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 398, - 453, - 539, - 533 - ], - "eyeB": [ - 614, - 452, - 754, - 533 - ], - "browA": [ - 352, - 385, - 557, - 475 - ], - "browB": [ - 599, - 387, - 802, - 476 - ], - "mouth": [ - 475, - 653, - 672, - 769 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p5-linocut/base/edits/sheet.jpg b/docs/avatar-art/p5-linocut/base/edits/sheet.jpg deleted file mode 100644 index 845a901..0000000 Binary files a/docs/avatar-art/p5-linocut/base/edits/sheet.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/base/normalize.json b/docs/avatar-art/p5-linocut/base/normalize.json deleted file mode 100644 index 6a680a5..0000000 --- a/docs/avatar-art/p5-linocut/base/normalize.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "canvas": { - "w": 1005, - "h": 1566 - }, - "reference": { - "image": "docs\\avatar-art\\p1-linocut\\base\\base-front.png", - "eyesCenter": [ - 492.69, - 584.33 - ], - "chin": [ - 500.61, - 919.55 - ], - "eyeDistance": 215.427, - "eyeChinDistance": 335.308 - }, - "input": { - "front": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\source-front.png", - "faceless": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\aligned-faceless-2.png", - "body": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\base\\edits\\aligned-body-2.png", - "frontSize": [ - 1145, - 1374 - ], - "eyesCenter": [ - 576.41, - 497.08 - ], - "chin": [ - 572.45, - 849.95 - ], - "eyeDistance": 215.702, - "eyeChinDistance": 352.891 - }, - "transform": { - "sHead": 0.974449, - "sWidth": 0.877729, - "s": 0.974449, - "rotationDeg": 0.0, - "offsetXNatural": -68.995, - "offsetXClampRange": [ - -110.745, - 0.0 - ], - "offsetX": -68.995, - "offsetY": 99.949 - }, - "coveredRange": { - "x": [ - 0.0, - 1005 - ], - "y": [ - 99.9, - 1438.8 - ] - }, - "inputSha256": { - "front": "a929adfeb240a1abb64302c9a31ab5a9911c93afd791fd91c8bda23c79144ac7", - "faceless": "c27e51dc63a5ae515bbfc918b8aad40c892f70e4cacbead4e416ce4453bec8bc", - "body": "fda852c1132d4338adb2e0e34c81ce824659282756edd2d700120c50e51ea089" - } -} \ No newline at end of file diff --git a/docs/avatar-art/p5-linocut/manifest.json b/docs/avatar-art/p5-linocut/manifest.json deleted file mode 100644 index f117158..0000000 --- a/docs/avatar-art/p5-linocut/manifest.json +++ /dev/null @@ -1,1683 +0,0 @@ -{ - "landmarks": { - "coordSystem": "screen (image pixel: x=0 좌측, y=0 상단; left=작은 x, right=큰 x; 인물 해부학적 좌우 아님)", - "eyeLeft": { - "innerCorner": [ - 431.57, - 588.95 - ], - "outerCorner": [ - 343.75, - 578.52 - ], - "upperLidTop": [ - 381.91, - 566.75 - ], - "lowerLidBottom": [ - 383.03, - 593.99 - ], - "iris": { - "center": [ - 391.83, - 577.93 - ], - "radius": 19.8 - } - }, - "eyeRight": { - "innerCorner": [ - 553.91, - 588.17 - ], - "outerCorner": [ - 641.75, - 576.34 - ], - "upperLidTop": [ - 605.36, - 564.53 - ], - "lowerLidBottom": [ - 602.91, - 593.0 - ], - "iris": { - "center": [ - 595.29, - 577.62 - ], - "radius": 19.89 - } - }, - "eyebrowLeft": { - "inner": [ - 454.2, - 526.5 - ], - "peak": [ - 355.0, - 512.29 - ], - "outer": [ - 303.8, - 521.39 - ] - }, - "eyebrowRight": { - "inner": [ - 524.86, - 526.22 - ], - "peak": [ - 625.3, - 510.74 - ], - "outer": [ - 677.14, - 518.59 - ] - }, - "noseTip": [ - 493.15, - 693.89 - ], - "mouthCornerLeft": [ - 418.91, - 801.37 - ], - "mouthCornerRight": [ - 557.32, - 801.82 - ], - "upperLipTopCenter": [ - 490.87, - 759.43 - ], - "lowerLipBottomCenter": [ - 489.47, - 828.29 - ], - "chinTip": [ - 488.31, - 929.72 - ], - "faceWidthAtEyeLevelLeft": [ - 263.01, - 635.57 - ], - "faceWidthAtEyeLevelRight": [ - 719.75, - 635.96 - ] - }, - "landmarkDetector": "mediapipe FaceLandmarker (tasks) 1.0.1, model=face_landmarker(float16, v1)", - "pipelineRun": { - "persona": "P5", - "timestamp": "2026-10-01T22:02:14", - "completed": true, - "stages": [ - { - "stage": "brow_sprite", - "ok": true, - "elapsedSec": 2.78 - }, - { - "stage": "paper_grain", - "ok": true, - "elapsedSec": 2.26 - }, - { - "stage": "lip_texture", - "ok": true, - "elapsedSec": 2.14 - }, - { - "stage": "jaw_pieces", - "ok": true, - "elapsedSec": 2.11 - }, - { - "stage": "export_rig", - "ok": true, - "elapsedSec": 16.02 - }, - { - "stage": "final_previews", - "ok": true, - "elapsedSec": 19.23 - } - ] - }, - "browCenterline": { - "method": "Otsu(front lum in bbox) AND diff(F-front)>=25, morphological closing to merge hatching, single global degree-2 polyfit on column-weighted centroid with one-pass outlier removal", - "diffThresh": 25.0, - "bboxPad": { - "x": 25.0, - "yUp": 42.0, - "yDown": 14.0 - }, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "outlierStdMult": 2.0, - "edgeInsetFrac": 0.03, - "peakWindow": [ - 0.25, - 0.6 - ], - "peakFallbackFrac": 0.35, - "peakFlatHeightPx": 3.0, - "peakFailEdgeFrac": 0.2, - "oldPoints": { - "browLeft": { - "inner": [ - 450.26, - 535.21 - ], - "peak": [ - 353.7, - 497.84 - ], - "outer": [ - 300.84, - 526.38 - ] - }, - "browRight": { - "inner": [ - 539.17, - 534.15 - ], - "peak": [ - 636.28, - 497.52 - ], - "outer": [ - 687.12, - 526.6 - ] - } - }, - "newPoints": { - "browLeft": { - "name": "browLeft", - "bbox": [ - 276, - 456, - 475, - 549 - ], - "otsuThresh": 107.0, - "colRange": [ - 299, - 459 - ], - "width": 160.0, - "nColumns": 161, - "nOutliersRemoved": 5, - "fitCoeffs": [ - 0.00213457, - -1.58403406, - 805.60949545 - ], - "inner": [ - 454.2, - 526.5 - ], - "peak": [ - 355.0, - 512.29 - ], - "outer": [ - 303.8, - 521.39 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.9627, - "checkA_pass": true, - "halfWidthInner": 7.068, - "halfWidthPeak": 12.97, - "halfWidthOuter": 3.364, - "bandX0": 276, - "bandY0": 456, - "colMin": 299, - "colMax": 459 - }, - "browRight": { - "name": "browRight", - "bbox": [ - 514, - 456, - 712, - 548 - ], - "otsuThresh": 103.0, - "colRange": [ - 520, - 682 - ], - "width": 162.0, - "nColumns": 162, - "nOutliersRemoved": 10, - "fitCoeffs": [ - 0.00200644, - -2.46191027, - 1265.65200503 - ], - "inner": [ - 524.86, - 526.22 - ], - "peak": [ - 625.3, - 510.74 - ], - "outer": [ - 677.14, - 518.59 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.9012, - "checkA_pass": true, - "halfWidthInner": 2.12, - "halfWidthPeak": 12.985, - "halfWidthOuter": 1.938, - "bandX0": 514, - "bandY0": 456, - "colMin": 520, - "colMax": 682 - } - }, - "halfWidthLeft": [ - 7.068, - 12.97, - 3.364 - ], - "halfWidthRight": [ - 2.12, - 12.985, - 1.938 - ], - "halfWidthNote": "원화 눈썹 잉크 띠 반두께[inner,peak,outer](px, 중심선 법선 방향). export_rig.py가 persona.json browHalfWidthFromArt!=false(기본)면 RigBrow.halfWidth로 내보낸다.", - "checkB_innerMinusPeakY": { - "browLeft": 14.21, - "browRight": 15.48 - }, - "checkD_symmetry": { - "innerYDiff": 0.28, - "peakYDiff": 1.55 - }, - "evidenceImage": "preview/v2/brow-centerline.jpg" - }, - "schemaVersion": "vignette.avatar.v3.layers.v1", - "persona": "P5", - "canvas": { - "w": 1005, - "h": 1566 - }, - "base": { - "front": "base/base-front.png", - "faceless": "base/base-faceless.png", - "facelessPadded": "base/base-faceless-padded.png", - "facelessSize": [ - 1005, - 1566 - ], - "padNote": "base-faceless.png 마지막 행을 0번 복제해 1005x1566(base-front.png 크기)으로 패딩한 것이 F다." - }, - "segmenter": { - "model": "selfie_multiclass_256x256.tflite (mediapipe ImageSegmenter, storage.googleapis.com)", - "categories": { - "0": "background", - "1": "hair", - "2": "bodySkin", - "3": "faceSkin", - "4": "clothes", - "5": "others" - }, - "categoryPixelCounts": { - "0": 542067, - "1": 184962, - "2": 65918, - "3": 172849, - "4": 581666, - "5": 26368 - }, - "hairEdgeRefine": { - "dilatePx": 6, - "lumThreshold": 110, - "addedPixels": 6809 - } - }, - "chinLine": { - "landmarkIndex": 152, - "xy": [ - 488.31, - 929.72 - ], - "marginPx": 8, - "cutY": 937.72 - }, - "faceOval": { - "landmarkLoop": [ - 10, - 338, - 297, - 332, - 284, - 251, - 389, - 356, - 454, - 323, - 361, - 288, - 397, - 365, - 379, - 378, - 400, - 377, - 152, - 148, - 176, - 149, - 150, - 136, - 172, - 58, - 132, - 93, - 234, - 127, - 162, - 21, - 54, - 103, - 67, - 109 - ], - "scale": 1.04, - "sourceImage": "base/base-front.png" - }, - "layers": [ - { - "id": "body", - "file": "layers/body.png", - "source": "base-faceless-masked+regenerated-fill", - "alphaBBox": [ - 0, - 696, - 1005, - 1566 - ], - "opaquePixels": 674797, - "greenResidue": 0, - "fillTargetPixels": 22883, - "fillSourceRaw": "raw/body.png (rejected 재생성이 아니라 수용된 body.png의 크로마키 결과; head/hairFront와 달리 body 재생성은 승인됨)" - }, - { - "id": "head", - "file": "layers/head.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 149, - 125, - 816, - 964 - ], - "opaquePixels": 361315, - "greenResidue": 0 - }, - { - "id": "hairFront", - "file": "layers/hairFront.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 249, - 401, - 735, - 817 - ], - "opaquePixels": 26097, - "greenResidue": 0 - } - ], - "rejectedRawEdits": { - "body": { - "file": "raw/body.png", - "status": "accepted-as-fill-source", - "reason": "정렬 (0,0), 형태 변형 없음 — head_mask 채움 전용 소스로 재사용" - } - }, - "composite": { - "meanAbsDiff": { - "full": 1.243436711716005, - "face": 0.2742245656452269, - "hairOutline": 5.635600721411412 - }, - "target": { - "full": 3.0, - "pass": true - }, - "backgroundNote": "카테고리0(배경)은 종이결 텍스처 노이즈(표준편차 ~29/채널)를 포함해 평탄한 크림(#EEE5D3)과 자체적으로 평균절대차 ~7 차이가 난다." - }, - "layersV2": { - "chinBandFix": { - "targetRegionPixels": 20911, - "sourceBandHeight": 150, - "rowJitterRange": [ - -2.0, - 2.0 - ], - "rngSeed": 20260930 - }, - "hairEdgeFix": { - "bandOuterPx": 40, - "bandInnerPx": 6, - "lumSmoothstep": [ - 205.0, - 150.0 - ], - "minFragmentAreaPx": 60, - "head": { - "bandOuterPixels": 70561, - "bandInnerPixels": 23467, - "componentsBefore": 105, - "removed": 103, - "removedPixels": 923 - }, - "hairFront": { - "bandOuterPixels": 7722, - "bandInnerPixels": 15821, - "componentsBefore": 110, - "removed": 106, - "removedPixels": 404 - } - }, - "hairPaperGapFix": { - "regionDilatePx": 6, - "paperColorDistThreshold": 40.0, - "paperLumThreshold": 195.0, - "paperSatThreshold": 40.0, - "opaqueAlphaThreshold": 0.95, - "regionPixels": 195388, - "head": { - "targetPixels": 525, - "componentsBefore": 3, - "removed": 1, - "removedPixels": 1, - "faceSkinAlphaDiff": 0.0 - }, - "hairFront": { - "targetPixels": 1, - "componentsBefore": 4, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - } - }, - "jawInkFix": { - "mouthCenterY": 791.01, - "distThresholdPx": 14.0, - "mergePx": 3, - "aspectThreshold": 1.0, - "removedGroups": [], - "keptGroups": [ - { - "pixels": 349, - "bbox": [ - 292, - 792, - 314, - 833 - ], - "width": 22, - "height": 41, - "aspect": 0.537 - }, - { - "pixels": 231, - "bbox": [ - 670, - 792, - 689, - 825 - ], - "width": 19, - "height": 33, - "aspect": 0.576 - } - ], - "removedPixels": 0, - "strayFragmentCleanup": { - "componentsBefore": 4, - "removed": 0, - "removedPixels": 0 - } - }, - "metrics": { - "body": { - "haloPct": 0.012540757461750688 - }, - "head": { - "haloPct": 0.5167255915148219, - "bgLeakAlphaMean": 7.273158758317319e-06, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - }, - "hairFront": { - "haloPct": 0.021427040925648167, - "bgLeakAlphaMean": 0.0, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - } - }, - "layers": [ - { - "id": "body", - "bbox": [ - 0, - 696, - 1005, - 1566 - ], - "opaque": 674737, - "greenResidue": 0 - }, - { - "id": "head", - "bbox": [ - 144, - 118, - 832, - 964 - ], - "opaque": 363494, - "greenResidue": 0 - }, - { - "id": "hairFront", - "bbox": [ - 231, - 401, - 744, - 837 - ], - "opaque": 31688, - "greenResidue": 0 - } - ], - "faceDetail": { - "browLandmarksSource": "manifest.landmarks.eyebrowLeft/Right", - "region": "face", - "faceInsetPx": 24.0, - "matchFacelessColor": true, - "matchFacelessColorDeltaRgb": { - "eyeLeft": [ - -3.9, - -3.12, - -5.83 - ], - "eyeRight": [ - -2.81, - -1.58, - -3.64 - ], - "browLeft": [ - 1.08, - 1.94, - -0.54 - ], - "browRight": [ - 2.72, - 2.6, - -0.6 - ], - "mouth": [ - 1.12, - 2.15, - 0.21 - ] - }, - "regions": { - "eyeLeft": [ - 313.8, - 502.3, - 456.6, - 664.0 - ], - "eyeRight": [ - 528.9, - 500.7, - 671.8, - 663.0 - ], - "mouth": [ - 373.9, - 718.9, - 602.3, - 914.7 - ], - "moles": [] - }, - "exclusionPixels": { - "eyeLeft": 11010, - "eyeRight": 10929, - "browLeft": 12371, - "browRight": 12781, - "mouth": 19339, - "union": 61340 - }, - "hairMaskPixels": 200393, - "bbox": [ - 262, - 410, - 717, - 933 - ], - "textureCloneStamp": { - "cloneTargetThreshold": 65.0, - "avoidThreshold": 110.0, - "lineOpeningIterations": 3, - "featherPx": 4.0, - "badTexturePixels": 200, - "preservedShadowPixels": 0, - "components": [ - { - "componentId": 1, - "pixels": 10, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 2, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 3, - "pixels": 10, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 4, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 5, - "pixels": 23, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 6, - "pixels": 1, - "offset": [ - -100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 7, - "pixels": 1, - "offset": [ - -100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 8, - "pixels": 1, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 9, - "pixels": 21, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 10, - "pixels": 1, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 11, - "pixels": 1, - "offset": [ - 100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 12, - "pixels": 1, - "offset": [ - 100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 13, - "pixels": 1, - "offset": [ - -120, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 14, - "pixels": 3, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 15, - "pixels": 4, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 16, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 17, - "pixels": 4, - "offset": [ - 80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 18, - "pixels": 12, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 19, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 20, - "pixels": 1, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 21, - "pixels": 1, - "offset": [ - 80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 22, - "pixels": 2, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 23, - "pixels": 10, - "offset": [ - 60, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 24, - "pixels": 12, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 25, - "pixels": 1, - "offset": [ - -120, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 26, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 27, - "pixels": 3, - "offset": [ - 0, - 60 - ], - "method": "clone-stamp" - }, - { - "componentId": 28, - "pixels": 6, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 29, - "pixels": 2, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 30, - "pixels": 1, - "offset": [ - 0, - -80 - ], - "method": "clone-stamp" - }, - { - "componentId": 31, - "pixels": 1, - "offset": [ - 40, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 32, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 33, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 34, - "pixels": 4, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 35, - "pixels": 1, - "offset": [ - 100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 36, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 37, - "pixels": 1, - "offset": [ - 60, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 38, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 39, - "pixels": 2, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 40, - "pixels": 1, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 41, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 42, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 43, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 44, - "pixels": 2, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 45, - "pixels": 1, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 46, - "pixels": 1, - "offset": [ - 0, - -80 - ], - "method": "clone-stamp" - }, - { - "componentId": 47, - "pixels": 2, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 48, - "pixels": 2, - "offset": [ - 0, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 49, - "pixels": 4, - "offset": [ - 0, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 50, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 51, - "pixels": 9, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 52, - "pixels": 17, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 53, - "pixels": 3, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 54, - "pixels": 2, - "offset": [ - 0, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 55, - "pixels": 1, - "offset": [ - 60, - 0 - ], - "method": "clone-stamp" - } - ] - }, - "genericPatches": { - "forehead": [ - 427, - 427, - 475, - 475 - ], - "cheekLeft": [ - 315, - 641, - 363, - 689 - ], - "cheekRight": [ - 513, - 641, - 561, - 689 - ] - }, - "genericPatchBaseline": { - "highFreqEnergy": 12.290699434604397, - "lineEnergy": 5.573410843314015 - }, - "checkA_meanAbsDiff": 0.5169706474597274, - "checkB_seamLumDiff": 4.451138758843115, - "checkC_holeDarkResiduePct": 0.2609019754006709, - "checkC_violationPixels": 133, - "checkD_texturePreservation": { - "eyeLeft": { - "insideEnergy": 9.425555350978705, - "baselineEnergy": 12.290699434604397, - "ratio": 0.7668851883596718, - "pass": true - }, - "eyeRight": { - "insideEnergy": 9.216862851981398, - "baselineEnergy": 12.290699434604397, - "ratio": 0.7499054794254728, - "pass": true - }, - "browLeft": { - "insideEnergy": 12.002887950664995, - "baselineEnergy": 12.290699434604397, - "ratio": 0.9765829857388694, - "pass": true - }, - "browRight": { - "insideEnergy": 14.719453370459323, - "baselineEnergy": 12.290699434604397, - "ratio": 1.197609090416513, - "pass": true - }, - "mouth": { - "insideEnergy": 8.53175518777407, - "baselineEnergy": 12.290699434604397, - "ratio": 0.6941635204057599, - "pass": false - } - }, - "checkD_allPass": false, - "checkE_ghostOutline": { - "eyeLeft": { - "bandLineEnergy": 9.137192706471845, - "baselineLineEnergy": 5.573410843314015, - "ratio": 1.6394256521449555, - "pass": false - }, - "eyeRight": { - "bandLineEnergy": 8.752331751844649, - "baselineLineEnergy": 5.573410843314015, - "ratio": 1.5703726134498297, - "pass": false - }, - "browLeft": { - "bandLineEnergy": 7.052766801064451, - "baselineLineEnergy": 5.573410843314015, - "ratio": 1.265430989987954, - "pass": true - }, - "browRight": { - "bandLineEnergy": 11.933111816196243, - "baselineLineEnergy": 5.573410843314015, - "ratio": 2.141078802850406, - "pass": false - }, - "mouth": { - "bandLineEnergy": 4.056343043170454, - "baselineLineEnergy": 5.573410843314015, - "ratio": 0.7278026252158553, - "pass": true - } - }, - "checkE_allPass": false, - "checkDarkCircle": { - "eyeLeft": { - "compositeMean": 151.9087619047619, - "frontMean": 151.84533333333331, - "diff": 0.06342857142857383, - "pass": true - }, - "eyeRight": { - "compositeMean": 154.33238095238096, - "frontMean": 154.11866666666668, - "diff": 0.21371428571427487, - "pass": true - } - }, - "checkDarkCircle_allPass": true - }, - "paperGrain": { - "sourceImage": "base/base-front.png", - "sourceOrigin": [ - 739, - 10 - ], - "tileSize": 256, - "personMarginPx": 20, - "cleanFraction": 0.98876953125, - "contaminatedPixelsFilled": 736, - "chromaKeep": 0.3, - "targetLumRange": [ - 245.0, - 250.0 - ], - "lumShift": 21.50800069173178, - "finalMeanLum": 247.48452908064607, - "finalLumStd": 3.3447698831173556, - "seamContinuity": { - "leftRightDiff": 0.018985015006137473, - "topBottomDiff": 0.9613975569983685 - } - } - }, - "lipTexture": { - "designVersion": "A5(아랫입술 그늘 띠를 lip-shadow 조각으로 분리)", - "landmarks": { - "mouthCornerLeft": [ - 418.91, - 801.37 - ], - "mouthCornerRight": [ - 557.32, - 801.82 - ], - "upperLipTop": [ - 490.87, - 759.43 - ], - "lowerLipBottom": [ - 489.47, - 828.29 - ], - "mouthCenter": [ - 490.65367832779884, - 791.0127539634705 - ] - }, - "capBBox": [ - 407.12, - 758.86, - 569.12, - 828.86 - ], - "otsuThreshLabA": 139.0, - "diffThresh": 20.0, - "dilatePx": 10.0, - "alphaRampPx": 4.0, - "splitOverlapPx": 2.0, - "shadowBandPx": 18.0, - "shadowTopOverlapPx": 6.0, - "shadowSideRampPx": 6.0, - "shadowTopRampPx": 4.0, - "checks": { - "lip-upper": { - "regionPixelCount": 4380, - "fullOpacityPixelCount": 3701, - "rampPixelCount": 679, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 56, - 30, - 124, - 54 - ] - }, - "lip-lower": { - "regionPixelCount": 4907, - "fullOpacityPixelCount": 4223, - "rampPixelCount": 684, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 56, - 72, - 136, - 46 - ] - }, - "lip-shadow": { - "regionPixelCount": 2664, - "fullOpacityPixelCount": 1167, - "rampPixelCount": 1497, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 66, - 78, - 116, - 48 - ] - }, - "splitOverlapPixelCount": 236, - "splitOverlapBandOk": true, - "splitOverlapAlphaMaxDiff": 0, - "splitOverlapConsistentOk": true, - "splitCoverageMismatchPixelCount": 0, - "splitCheckOk": true, - "shadowLowerOverlapPixelCount": 1925, - "shadowLowerOverlapMaxDiff": 109.4890426758939, - "shadowLowerOverlapDiffGt1PixelCount": 110, - "shadowLowerOverlapOk": false - }, - "check3NeutralComposite": { - "fullOpacityMaxDiff": 0.0, - "rampMeanAbsDiff": 20.088, - "mouthBBoxMeanAbsDiff": 10.82 - }, - "pngFileSizes": { - "lip-upper": 19327, - "lip-lower": 17953, - "lip-shadow": 16440 - }, - "canvasOrigin": { - "upper": [ - 423, - 749 - ], - "lower": [ - 423, - 791 - ], - "shadow": [ - 433, - 797 - ] - }, - "evidenceImage": "preview/v2/lip-texture.jpg", - "check4WebpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jaw": { - "designVersion": "A5(결정문 §8.4 하관 띠 변형 — 턱 조각 원본)", - "noseTip": [ - 493.15, - 693.89 - ], - "chinTip": [ - 488.31, - 929.72 - ], - "yFormula": "noseTip.y - 50 - 10 .. chinTip.y + 40", - "noseToTopPx": 60.0, - "chinToBottomPx": 40.0, - "ovalOutsetPx": 18.0, - "faceOvalSource": "export_rig.detect_face_landmarks + compute_face_oval(base-front.png, 결정적)", - "bboxCanvas": [ - 236, - 633, - 748, - 970 - ], - "naiveXRangeForComparison": [ - 236, - 748 - ], - "checks": { - "head": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 376691 - }, - "detail": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 416696 - } - }, - "evidenceImage": "preview/v2/jaw-pieces.jpg", - "check1VsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.353, - "maxAbsDiffPremultipliedRgb": 40.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 2.178, - "maxAbsDiffPremultipliedRgb": 41.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - } - } - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 13726, - "lip-lower": 12934, - "lip-shadow": 12258 - }, - "totalBytes": 38918, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 34964, - "jaw-detail": 30114 - }, - "totalBytes": 65078, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.353, - "maxAbsDiffPremultipliedRgb": 40.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 2.178, - "maxAbsDiffPremultipliedRgb": 41.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - } - } - }, - "motifPublish": null, - "browSprite": { - "enabled": true, - "bandExtraHalfHeightPx": 8.0, - "bandEndExtendPx": 6.0, - "diffThresh": 25.0, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "hairSource": "layers/v2/hairFront.png alpha(게시되는 레이어 그 자체, 분할 카테고리 아님)", - "hairAlphaThresh": 128.0, - "hairDilateIterations": 1, - "rampPx": 1.5, - "inkLumMethod": "median(ink-classified pixels in band, region after hair removal)", - "minAlphaForUnmix": 0.05, - "hairMaskPixelCount": 36082, - "left": { - "bboxCanvas": [ - 299, - 495, - 161, - 48 - ], - "halfHeightPx": 20.97, - "otsuThresh": 108.0, - "lSkin": 175.333, - "lInk": 36.0, - "regionPixelCount": 3246, - "hairOverlapPixelCount": 121, - "neutralCompositeMeanAbsDiffAlphaRegion": 6.367, - "pngFileSizeBytes": 10174 - }, - "right": { - "bboxCanvas": [ - 520, - 495, - 162, - 44 - ], - "halfHeightPx": 20.985, - "otsuThresh": 107.0, - "lSkin": 174.333, - "lInk": 34.667, - "regionPixelCount": 2808, - "hairOverlapPixelCount": 186, - "neutralCompositeMeanAbsDiffAlphaRegion": 6.668, - "pngFileSizeBytes": 8822 - }, - "canvasOrigin": { - "left": [ - 299.0, - 495.0 - ], - "right": [ - 520.0, - 495.0 - ] - }, - "evidenceImage": "preview/v2/brow-sprite.jpg", - "checkWebpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "eyeLineScale": { - "enabled": true, - "method": "눈 폭 가운데 60% 열마다 upperLidTop.y-12 ~ 홍채중심y 사이 첫 연속 어두운(lum<80) 구간 길이의 중앙값", - "windowAbovePx": 12.0, - "darkLumThresh": 80.0, - "columnMiddleFrac": 0.6, - "p1BaselinePx": 2.0, - "lineScaleClampRange": [ - 1.0, - 2.5 - ], - "left": { - "measuredPx": 8.0, - "nColumns": 54, - "ratio": 4.0, - "lineScale": 2.5 - }, - "right": { - "measuredPx": 13.5, - "nColumns": 54, - "ratio": 6.75, - "lineScale": 2.5 - } - }, - "iris": { - "method": "가로: 중심 y 부근 상하 각 4행에서 중심 x를 포함하는 연속 어두운(lum<100) 구간의 중앙값으로 반지름·중심x. 세로: 중심 x 부근 좌우 각 4열에서 중심 y를 포함하는 연속 어두운 구간의 아래쪽 끝 중앙값에서 가로 반지름을 빼 중심y(세로축은 윗눈꺼풀 잉크와 홍채가 같은 톤으로 이어져 위쪽 경계를 가를 수 없다).", - "darkLumThresh": 100.0, - "scanBandPx": 4, - "left": { - "measuredCenter": [ - 387.5, - 574.5 - ], - "measuredRadius": 18.5, - "rigCenter": [ - 391.83, - 577.93 - ], - "rigRadius": 19.8, - "nRowsHorizontal": 8, - "nColsVertical": 9 - }, - "right": { - "measuredCenter": [ - 594.0, - 573.0 - ], - "measuredRadius": 18.0, - "rigCenter": [ - 595.29, - 577.62 - ], - "rigRadius": 19.89, - "nRowsHorizontal": 9, - "nColsVertical": 9 - } - }, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 6584, - "brow-right": 5736 - }, - "totalBytes": 12320, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p5-linocut/persona.json b/docs/avatar-art/p5-linocut/persona.json deleted file mode 100644 index ae1fb27..0000000 --- a/docs/avatar-art/p5-linocut/persona.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "code": "P5", - "publicSlug": "p5", - "rigFileName": "p5Rig.ts", - "rigExportName": "P5_LINOCUT_RIG", - "styleFrame": "../art-direction-v3/p1/r2-b-linocut.png", - "paletteSamples": { - "ink": { - "box": [260, 245, 660, 330], - "lumThreshold": 55 - }, - "motifPetalBoxes": [ - { "label": "sun", "kind": "ochre", "box": [1230, 10, 1536, 210] }, - { "label": "tulip", "kind": "ochre", "box": [1030, 280, 1170, 560] } - ], - "motifLeafBoxes": [ - { "label": "cloudLeft", "kind": "blue", "box": [20, 20, 380, 190] }, - { "label": "raincloud", "kind": "blue", "box": [520, 10, 930, 230] }, - { "label": "wiltedFlowerLeaf", "kind": "blue", "box": [520, 280, 650, 580] }, - { "label": "closedBudLeaf", "kind": "blue", "box": [10, 290, 110, 570] } - ] - }, - "faceDetail": { - "moles": [], - "region": "face", - "faceInsetPx": 24, - "matchFacelessColor": true - }, - "pivots": { - "neck": [488, 1001], - "body": [502, 1566], - "face": [490, 659] - }, - "crops": { - "bustYOffset": 40 - } -} diff --git a/docs/avatar-art/p5-linocut/preview/qa/face-art-vs-render.jpg b/docs/avatar-art/p5-linocut/preview/qa/face-art-vs-render.jpg deleted file mode 100644 index ffc5ba6..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/qa/face-art-vs-render.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/qa/face-zoom.jpg b/docs/avatar-art/p5-linocut/preview/qa/face-zoom.jpg deleted file mode 100644 index 93f2c01..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/qa/face-zoom.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/qa/neutral-vs-art.jpg b/docs/avatar-art/p5-linocut/preview/qa/neutral-vs-art.jpg deleted file mode 100644 index 9e3c101..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/qa/neutral-vs-art.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/qa/normalized.jpg b/docs/avatar-art/p5-linocut/preview/qa/normalized.jpg deleted file mode 100644 index 662cbd5..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/qa/normalized.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/qa/pad-check.jpg b/docs/avatar-art/p5-linocut/preview/qa/pad-check.jpg deleted file mode 100644 index 66c9b5a..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/qa/pad-check.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/qa/sheet-expr-bust.jpg b/docs/avatar-art/p5-linocut/preview/qa/sheet-expr-bust.jpg deleted file mode 100644 index 71fdaf6..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/qa/sheet-expr-bust.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/qa/sheet-expr-face.jpg b/docs/avatar-art/p5-linocut/preview/qa/sheet-expr-face.jpg deleted file mode 100644 index 20357ed..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/qa/sheet-expr-face.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/qa/sheet-viseme.jpg b/docs/avatar-art/p5-linocut/preview/qa/sheet-viseme.jpg deleted file mode 100644 index e066603..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/qa/sheet-viseme.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/qa/viseme-timeline.json b/docs/avatar-art/p5-linocut/preview/qa/viseme-timeline.json deleted file mode 100644 index d0b2bce..0000000 --- a/docs/avatar-art/p5-linocut/preview/qa/viseme-timeline.json +++ /dev/null @@ -1,287 +0,0 @@ -[ - { - "t": 0, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00000.png" - }, - { - "t": 50, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00050.png" - }, - { - "t": 100, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00100.png" - }, - { - "t": 150, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00150.png" - }, - { - "t": 200, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00200.png" - }, - { - "t": 250, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00250.png" - }, - { - "t": 300, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00300.png" - }, - { - "t": 350, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00350.png" - }, - { - "t": 400, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00400.png" - }, - { - "t": 450, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00450.png" - }, - { - "t": 500, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00500.png" - }, - { - "t": 550, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00550.png" - }, - { - "t": 600, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00600.png" - }, - { - "t": 650, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00650.png" - }, - { - "t": 700, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00700.png" - }, - { - "t": 750, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00750.png" - }, - { - "t": 800, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00800.png" - }, - { - "t": 850, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00850.png" - }, - { - "t": 900, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00900.png" - }, - { - "t": 950, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t00950.png" - }, - { - "t": 1000, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01000.png" - }, - { - "t": 1050, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01050.png" - }, - { - "t": 1100, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01100.png" - }, - { - "t": 1150, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01150.png" - }, - { - "t": 1200, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01200.png" - }, - { - "t": 1250, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01250.png" - }, - { - "t": 1300, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01300.png" - }, - { - "t": 1350, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01350.png" - }, - { - "t": 1400, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01400.png" - }, - { - "t": 1450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01450.png" - }, - { - "t": 1500, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01500.png" - }, - { - "t": 1550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01550.png" - }, - { - "t": 1600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01600.png" - }, - { - "t": 1650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01650.png" - }, - { - "t": 1700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01700.png" - }, - { - "t": 1750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01750.png" - }, - { - "t": 1800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01800.png" - }, - { - "t": 1850, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01850.png" - }, - { - "t": 1900, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01900.png" - }, - { - "t": 1950, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t01950.png" - }, - { - "t": 2000, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02000.png" - }, - { - "t": 2050, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02050.png" - }, - { - "t": 2100, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02100.png" - }, - { - "t": 2150, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02150.png" - }, - { - "t": 2200, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02200.png" - }, - { - "t": 2250, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02250.png" - }, - { - "t": 2300, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02300.png" - }, - { - "t": 2350, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02350.png" - }, - { - "t": 2400, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02400.png" - }, - { - "t": 2450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02450.png" - }, - { - "t": 2500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02500.png" - }, - { - "t": 2550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02550.png" - }, - { - "t": 2600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02600.png" - }, - { - "t": 2650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02650.png" - }, - { - "t": 2700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02700.png" - }, - { - "t": 2750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02750.png" - }, - { - "t": 2800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p5-linocut\\preview\\qa\\frames\\viseme-scan-t02800.png" - } -] \ No newline at end of file diff --git a/docs/avatar-art/p5-linocut/preview/v2/brow-centerline.jpg b/docs/avatar-art/p5-linocut/preview/v2/brow-centerline.jpg deleted file mode 100644 index 1569852..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/v2/brow-centerline.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/v2/brow-sprite.jpg b/docs/avatar-art/p5-linocut/preview/v2/brow-sprite.jpg deleted file mode 100644 index dc6adfa..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/v2/brow-sprite.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/v2/export-rig-report.json b/docs/avatar-art/p5-linocut/preview/v2/export-rig-report.json deleted file mode 100644 index 65cad9c..0000000 --- a/docs/avatar-art/p5-linocut/preview/v2/export-rig-report.json +++ /dev/null @@ -1,676 +0,0 @@ -{ - "faceOvalBBox": [ - 251.6, - 403.0, - 732.9, - 939.9 - ], - "cropsFace": [ - 191.6, - 370.8, - 601.3, - 601.3 - ], - "mouthCenter": [ - 490.7, - 791.0 - ], - "palette": { - "ink": "#222221", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#B1845F", - "lipLower": "#CC926C", - "mouthLine": "#A5694F", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "paletteSamples": { - "eyeLeft": { - "sclera": "#DAAC7D", - "iris": "#262625", - "irisRing": "#AC8660", - "n": { - "sclera": 1011, - "iris": 175, - "irisRing": 366 - } - }, - "eyeRight": { - "sclera": "#DDAD7C", - "iris": "#252423", - "irisRing": "#D6A676", - "n": { - "sclera": 1122, - "iris": 179, - "irisRing": 366 - } - }, - "scleraIrisIrisRingFixedOverride": { - "reason": "persona.json paletteSamples.eyeOverride(고정 설계값)", - "measured": { - "sclera": "#DCAC7C", - "iris": "#262524", - "irisRing": "#C1966B" - }, - "fixed": { - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F" - } - }, - "ink": { - "box": [ - 260, - 245, - 660, - 330 - ], - "lumThreshold": 55.0, - "n": 27060, - "hex": "#222221" - }, - "lipUpper": { - "box": [ - 413, - 756, - 562, - 789 - ], - "hex": "#B1845F" - }, - "lipLower": { - "box": [ - 413, - 793, - 562, - 831 - ], - "hex": "#CC926C" - }, - "mouthLine": { - "box": [ - 433, - 789, - 542, - 794 - ], - "hex": "#A5694F" - }, - "motifPetal": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "sun": { - "box": [ - 1230, - 10, - 1536, - 210 - ], - "n": 20570, - "hex": "#D3A661" - }, - "tulip": { - "box": [ - 1030, - 280, - 1170, - 560 - ], - "n": 4314, - "hex": "#CCA063" - } - }, - "hex": "#D0A362" - }, - "motifLeaf": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "cloudLeft": { - "box": [ - 20, - 20, - 380, - 190 - ], - "n": 21312, - "hex": "#636D76" - }, - "raincloud": { - "box": [ - 520, - 10, - 930, - 230 - ], - "n": 27607, - "hex": "#586B76" - }, - "wiltedFlowerLeaf": { - "box": [ - 520, - 280, - 650, - 580 - ], - "n": 5818, - "hex": "#495961" - }, - "closedBudLeaf": { - "box": [ - 10, - 290, - 110, - 570 - ], - "n": 6589, - "hex": "#495961" - } - }, - "hex": "#53626C" - } - }, - "publish": { - "fileSizes": { - "body": 142558, - "head": 102774, - "hair-front": 23854, - "face-detail": 49332, - "paper-grain": 1452 - }, - "encodeMeta": { - "quality": 80, - "scale": 1.0, - "totalBytes": 319970 - }, - "totalBytes": 319970 - }, - "grain": { - "href": "/avatar/v3/p5/paper-grain.webp", - "size": 256 - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 13726, - "lip-lower": 12934, - "lip-shadow": 12258 - }, - "totalBytes": 38918, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 34964, - "jaw-detail": 30114 - }, - "totalBytes": 65078, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.353, - "maxAbsDiffPremultipliedRgb": 40.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 2.178, - "maxAbsDiffPremultipliedRgb": 41.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - } - } - }, - "motifPublish": null, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 6584, - "brow-right": 5736 - }, - "totalBytes": 12320, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "rig": { - "schemaVersion": "vignette.avatar.v3.rig.v1", - "persona": "P5", - "canvas": { - "w": 1005, - "h": 1566 - }, - "layers": { - "body": { - "href": "/avatar/v3/p5/body.webp", - "x": 0, - "y": 696, - "w": 1005, - "h": 870 - }, - "head": { - "href": "/avatar/v3/p5/head.webp", - "x": 144, - "y": 118, - "w": 688, - "h": 846 - }, - "hairFront": { - "href": "/avatar/v3/p5/hair-front.webp", - "x": 231, - "y": 401, - "w": 513, - "h": 436 - }, - "faceDetail": { - "href": "/avatar/v3/p5/face-detail.webp", - "x": 262, - "y": 410, - "w": 455, - "h": 523 - } - }, - "grain": { - "href": "/avatar/v3/p5/paper-grain.webp", - "size": 256 - }, - "lipTexture": { - "upper": { - "href": "/avatar/v3/p5/lip-upper.webp", - "x": 423, - "y": 749, - "w": 124, - "h": 54 - }, - "lower": { - "href": "/avatar/v3/p5/lip-lower.webp", - "x": 423, - "y": 791, - "w": 136, - "h": 46 - }, - "shadow": { - "href": "/avatar/v3/p5/lip-shadow.webp", - "x": 433, - "y": 797, - "w": 116, - "h": 48 - } - }, - "jaw": { - "head": { - "href": "/avatar/v3/p5/jaw-head.webp", - "x": 236, - "y": 633, - "w": 512, - "h": 337 - }, - "detail": { - "href": "/avatar/v3/p5/jaw-detail.webp", - "x": 236, - "y": 633, - "w": 512, - "h": 337 - } - }, - "motif": null, - "pivots": { - "neck": [ - 488, - 1001 - ], - "body": [ - 502, - 1566 - ], - "face": [ - 490, - 659 - ] - }, - "crops": { - "portrait": [ - 0, - 0, - 1005, - 1566 - ], - "bust": [ - 0.0, - 40.0, - 1005.0, - 1005.0 - ], - "face": [ - 191.593571319131, - 370.8326851064444, - 601.2719892333985, - 601.2719892333985 - ] - }, - "faceOval": [ - [ - 496.1, - 403.0 - ], - [ - 564.0, - 405.0 - ], - [ - 621.1, - 414.5 - ], - [ - 672.8, - 434.4 - ], - [ - 705.1, - 464.2 - ], - [ - 723.6, - 501.7 - ], - [ - 732.1, - 541.0 - ], - [ - 732.9, - 589.2 - ], - [ - 728.9, - 634.4 - ], - [ - 723.7, - 681.0 - ], - [ - 714.5, - 730.1 - ], - [ - 698.1, - 782.2 - ], - [ - 675.7, - 823.6 - ], - [ - 650.1, - 853.0 - ], - [ - 619.0, - 879.9 - ], - [ - 592.9, - 900.0 - ], - [ - 565.2, - 919.2 - ], - [ - 531.9, - 934.8 - ], - [ - 488.2, - 939.9 - ], - [ - 444.8, - 934.7 - ], - [ - 411.7, - 918.9 - ], - [ - 384.1, - 899.3 - ], - [ - 358.4, - 879.0 - ], - [ - 328.1, - 851.8 - ], - [ - 303.5, - 822.2 - ], - [ - 281.3, - 781.3 - ], - [ - 265.8, - 729.0 - ], - [ - 257.4, - 680.0 - ], - [ - 253.9, - 634.0 - ], - [ - 251.6, - 589.2 - ], - [ - 255.3, - 541.7 - ], - [ - 265.7, - 502.0 - ], - [ - 286.1, - 464.7 - ], - [ - 319.6, - 435.0 - ], - [ - 371.3, - 414.7 - ], - [ - 428.2, - 405.3 - ] - ], - "landmarks": { - "eyeLeft": { - "inner": [ - 431.6, - 589.0 - ], - "outer": [ - 343.8, - 578.5 - ], - "upperLidTop": [ - 381.9, - 566.8 - ], - "lowerLidBottom": [ - 383.0, - 594.0 - ], - "iris": { - "center": [ - 387.5, - 574.5 - ], - "radius": 18.5 - }, - "lineScale": 2.5 - }, - "eyeRight": { - "inner": [ - 553.9, - 588.2 - ], - "outer": [ - 641.8, - 576.3 - ], - "upperLidTop": [ - 605.4, - 564.5 - ], - "lowerLidBottom": [ - 602.9, - 593.0 - ], - "iris": { - "center": [ - 594.0, - 573.0 - ], - "radius": 18.0 - }, - "lineScale": 2.5 - }, - "browLeft": { - "inner": [ - 454.2, - 526.5 - ], - "peak": [ - 355.0, - 512.3 - ], - "outer": [ - 303.8, - 521.4 - ], - "halfWidth": [ - 7.07, - 12.97, - 3.36 - ], - "sprite": { - "href": "/avatar/v3/p5/brow-left.webp", - "x": 299.0, - "y": 495.0, - "w": 161, - "h": 48 - } - }, - "browRight": { - "inner": [ - 524.9, - 526.2 - ], - "peak": [ - 625.3, - 510.7 - ], - "outer": [ - 677.1, - 518.6 - ], - "halfWidth": [ - 2.12, - 12.98, - 1.94 - ], - "sprite": { - "href": "/avatar/v3/p5/brow-right.webp", - "x": 520.0, - "y": 495.0, - "w": 162, - "h": 44 - } - }, - "noseTip": [ - 493.1, - 693.9 - ], - "mouthCornerLeft": [ - 418.9, - 801.4 - ], - "mouthCornerRight": [ - 557.3, - 801.8 - ], - "upperLipTop": [ - 490.9, - 759.4 - ], - "lowerLipBottom": [ - 489.5, - 828.3 - ], - "mouthCenter": [ - 490.7, - 791.0 - ], - "chinTip": [ - 488.3, - 929.7 - ] - }, - "palette": { - "ink": "#222221", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#B1845F", - "lipLower": "#CC926C", - "mouthLine": "#A5694F", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "backdrop": { - "cognitive": "#ECE3D1", - "positive": "#F1DEC2", - "negative": "#DCE0E2", - "defensive": "#E6DAD3", - "energy": "#E2E0D0" - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p5-linocut/preview/v2/ghost-check.jpg b/docs/avatar-art/p5-linocut/preview/v2/ghost-check.jpg deleted file mode 100644 index ffb0f8f..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/v2/ghost-check.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/v2/holes-texture.jpg b/docs/avatar-art/p5-linocut/preview/v2/holes-texture.jpg deleted file mode 100644 index 51e1100..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/v2/holes-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/v2/jaw-pieces.jpg b/docs/avatar-art/p5-linocut/preview/v2/jaw-pieces.jpg deleted file mode 100644 index a6fc626..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/v2/jaw-pieces.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/preview/v2/lip-texture.jpg b/docs/avatar-art/p5-linocut/preview/v2/lip-texture.jpg deleted file mode 100644 index 3ffa2a0..0000000 Binary files a/docs/avatar-art/p5-linocut/preview/v2/lip-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p5-linocut/prompts/base-faceless.txt b/docs/avatar-art/p5-linocut/prompts/base-faceless.txt deleted file mode 100644 index 17718c4..0000000 --- a/docs/avatar-art/p5-linocut/prompts/base-faceless.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as faceless.png: - -Redraw exactly the same boy, same frontal pose, same framing and same linocut relief-print style as the reference, but leave the face blank: remove the eyes, eyebrows and mouth completely and fill those areas with plain skin in the same flat skin color, keeping the nose, the face outline, the ears, the hair, the neck, the grey t-shirt, the black zip-up track jacket and the white wired earphones exactly as they are. Do not move, resize or redraw anything else. Plain flat cream background, nothing else, no text. diff --git a/docs/avatar-art/p5-linocut/prompts/body.txt b/docs/avatar-art/p5-linocut/prompts/body.txt deleted file mode 100644 index 70d20e0..0000000 --- a/docs/avatar-art/p5-linocut/prompts/body.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as body.png: - -Keep only the neck, the grey t-shirt, the black zip-up track jacket and the parts of the white earphone cords that lie on the jacket exactly as they are in the reference: same shapes, same position, same linocut relief-print style, same ink colors and texture. Remove the head, the face, the ears and all of the hair completely. Where the hair or the chin previously covered the neck or the clothes, continue them naturally in the same style so there are no holes. Place the result on a plain flat pure green background (#00ff00) with nothing else, no text. diff --git a/docs/avatar-art/p5-linocut/raw/body.png b/docs/avatar-art/p5-linocut/raw/body.png deleted file mode 100644 index 9f06840..0000000 Binary files a/docs/avatar-art/p5-linocut/raw/body.png and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/base/base-faceless.png b/docs/avatar-art/p6-linocut/base/base-faceless.png deleted file mode 100644 index 64039b0..0000000 Binary files a/docs/avatar-art/p6-linocut/base/base-faceless.png and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/base/base-front.png b/docs/avatar-art/p6-linocut/base/base-front.png deleted file mode 100644 index ab797d5..0000000 Binary files a/docs/avatar-art/p6-linocut/base/base-front.png and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/base/edits/aligned-body-1.json b/docs/avatar-art/p6-linocut/base/edits/aligned-body-1.json deleted file mode 100644 index 12ecd9c..0000000 --- a/docs/avatar-art/p6-linocut/base/edits/aligned-body-1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\body-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\aligned-body-1.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.78033, - "transform": { - "scaleSqrtDet": 1.00045, - "rotationDeg": -0.002, - "translateX": -0.468, - "translateY": -2.06, - "matrix": [ - [ - 0.999511, - 0.000548, - -0.468036 - ], - [ - -3.8e-05, - 1.001399, - -2.060432 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 478631, - "chamferInMask": { - "median": 0.0, - "p90": 1.414, - "nSamples": 80775 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p6-linocut/base/edits/aligned-body-2.json b/docs/avatar-art/p6-linocut/base/edits/aligned-body-2.json deleted file mode 100644 index 2a9d1da..0000000 --- a/docs/avatar-art/p6-linocut/base/edits/aligned-body-2.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\body-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\aligned-body-2.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.76917, - "transform": { - "scaleSqrtDet": 1.00098, - "rotationDeg": 0.002, - "translateX": -0.523, - "translateY": -3.41, - "matrix": [ - [ - 0.99945, - 0.000442, - -0.523264 - ], - [ - 3.3e-05, - 1.002504, - -3.409868 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 480048, - "chamferInMask": { - "median": 0.0, - "p90": 2.0, - "nSamples": 82384 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p6-linocut/base/edits/aligned-faceless-1.json b/docs/avatar-art/p6-linocut/base/edits/aligned-faceless-1.json deleted file mode 100644 index ab9a956..0000000 --- a/docs/avatar-art/p6-linocut/base/edits/aligned-faceless-1.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\faceless-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\aligned-faceless-1.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.99634, - "transform": { - "scaleSqrtDet": 0.99986, - "rotationDeg": 0.0, - "translateX": -0.3, - "translateY": -0.333, - "matrix": [ - [ - 0.999866, - -1.6e-05, - -0.299886 - ], - [ - 8e-06, - 0.999857, - -0.333271 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 884598, - "chamferInMask": { - "median": 0.0, - "p90": 1.0, - "nSamples": 148881 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 6403 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 390, - 488, - 535, - 574 - ], - "refInkRatio": 0.2302, - "editedInkRatio": 0.0 - }, - "eyeB": { - "box": [ - 606, - 478, - 750, - 564 - ], - "refInkRatio": 0.2649, - "editedInkRatio": 0.0232 - }, - "browA": { - "box": [ - 351, - 423, - 546, - 514 - ], - "refInkRatio": 0.3136, - "editedInkRatio": 0.1598 - }, - "browB": { - "box": [ - 587, - 412, - 783, - 506 - ], - "refInkRatio": 0.4015, - "editedInkRatio": 0.2383 - }, - "mouth": { - "box": [ - 476, - 709, - 680, - 823 - ], - "refInkRatio": 0.0804, - "editedInkRatio": 0.0 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 390, - 488, - 535, - 574 - ], - "eyeB": [ - 606, - 478, - 750, - 564 - ], - "browA": [ - 351, - 423, - 546, - 514 - ], - "browB": [ - 587, - 412, - 783, - 506 - ], - "mouth": [ - 476, - 709, - 680, - 823 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p6-linocut/base/edits/aligned-faceless-2.json b/docs/avatar-art/p6-linocut/base/edits/aligned-faceless-2.json deleted file mode 100644 index 382b1e4..0000000 --- a/docs/avatar-art/p6-linocut/base/edits/aligned-faceless-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\faceless-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\aligned-faceless-2.png", - "refSize": [ - 1145, - 1374 - ], - "editedOriginalSize": [ - 1145, - 1374 - ], - "eccCorrelation": 0.99628, - "transform": { - "scaleSqrtDet": 0.99981, - "rotationDeg": 0.001, - "translateX": -0.117, - "translateY": -0.47, - "matrix": [ - [ - 0.999689, - -2.3e-05, - -0.116897 - ], - [ - 2.1e-05, - 0.999929, - -0.469733 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 884598, - "chamferInMask": { - "median": 0.0, - "p90": 1.0, - "nSamples": 150035 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 6389 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 390, - 488, - 535, - 574 - ], - "refInkRatio": 0.2302, - "editedInkRatio": 0.0 - }, - "eyeB": { - "box": [ - 606, - 478, - 750, - 564 - ], - "refInkRatio": 0.2649, - "editedInkRatio": 0.0207 - }, - "browA": { - "box": [ - 351, - 423, - 546, - 514 - ], - "refInkRatio": 0.3136, - "editedInkRatio": 0.1554 - }, - "browB": { - "box": [ - 587, - 412, - 783, - 506 - ], - "refInkRatio": 0.4015, - "editedInkRatio": 0.2278 - }, - "mouth": { - "box": [ - 476, - 709, - 680, - 823 - ], - "refInkRatio": 0.0804, - "editedInkRatio": 0.0 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 390, - 488, - 535, - 574 - ], - "eyeB": [ - 606, - 478, - 750, - 564 - ], - "browA": [ - 351, - 423, - 546, - 514 - ], - "browB": [ - 587, - 412, - 783, - 506 - ], - "mouth": [ - 476, - 709, - 680, - 823 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p6-linocut/base/edits/sheet.jpg b/docs/avatar-art/p6-linocut/base/edits/sheet.jpg deleted file mode 100644 index 5b66dc1..0000000 Binary files a/docs/avatar-art/p6-linocut/base/edits/sheet.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/base/normalize.json b/docs/avatar-art/p6-linocut/base/normalize.json deleted file mode 100644 index 390c240..0000000 --- a/docs/avatar-art/p6-linocut/base/normalize.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "canvas": { - "w": 1005, - "h": 1566 - }, - "reference": { - "image": "docs\\avatar-art\\p1-linocut\\base\\base-front.png", - "eyesCenter": [ - 492.69, - 584.33 - ], - "chin": [ - 500.61, - 919.55 - ], - "eyeDistance": 215.427, - "eyeChinDistance": 335.308 - }, - "input": { - "front": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\source-front.png", - "faceless": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\aligned-faceless-2.png", - "body": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\base\\edits\\aligned-body-1.png", - "frontSize": [ - 1145, - 1374 - ], - "eyesCenter": [ - 570.22, - 532.03 - ], - "chin": [ - 583.57, - 891.58 - ], - "eyeDistance": 215.577, - "eyeChinDistance": 359.799 - }, - "transform": { - "sHead": 0.965618, - "sWidth": 0.877729, - "s": 0.965618, - "rotationDeg": 0.0, - "offsetXNatural": -57.922, - "offsetXClampRange": [ - -100.632, - 0.0 - ], - "offsetX": -57.922, - "offsetY": 70.594 - }, - "coveredRange": { - "x": [ - 0.0, - 1005 - ], - "y": [ - 70.6, - 1397.4 - ] - }, - "inputSha256": { - "front": "cd4ec9305f7d8b54ad9ed0b9e7bb6c3b9f2cc71d57783854dbd21727a0d8a542", - "faceless": "2cfeb265c33dfcb714c955f831d3c0f2a87cf0518d368f0b47e873640414157b", - "body": "5e544ce3daefa7781f7aadeb7a3886c3b54707a2f0e3a0211666b0a71ee6db88" - } -} \ No newline at end of file diff --git a/docs/avatar-art/p6-linocut/manifest.json b/docs/avatar-art/p6-linocut/manifest.json deleted file mode 100644 index e68d15a..0000000 --- a/docs/avatar-art/p6-linocut/manifest.json +++ /dev/null @@ -1,1346 +0,0 @@ -{ - "landmarks": { - "coordSystem": "screen (image pixel: x=0 좌측, y=0 상단; left=작은 x, right=큰 x; 인물 해부학적 좌우 아님)", - "eyeLeft": { - "innerCorner": [ - 432.37, - 593.74 - ], - "outerCorner": [ - 341.43, - 583.87 - ], - "upperLidTop": [ - 378.12, - 565.98 - ], - "lowerLidBottom": [ - 383.39, - 600.15 - ], - "iris": { - "center": [ - 388.77, - 581.09 - ], - "radius": 20.65 - } - }, - "eyeRight": { - "innerCorner": [ - 548.91, - 589.6 - ], - "outerCorner": [ - 639.76, - 570.83 - ], - "upperLidTop": [ - 601.39, - 555.92 - ], - "lowerLidBottom": [ - 598.38, - 591.82 - ], - "iris": { - "center": [ - 594.19, - 571.71 - ], - "radius": 20.75 - } - }, - "eyebrowLeft": { - "inner": [ - 451.92, - 530.26 - ], - "peak": [ - 367.6, - 517.35 - ], - "outer": [ - 324.08, - 521.47 - ] - }, - "eyebrowRight": { - "inner": [ - 518.29, - 525.41 - ], - "peak": [ - 606.95, - 510.29 - ], - "outer": [ - 652.71, - 515.57 - ] - }, - "noseTip": [ - 495.58, - 716.54 - ], - "mouthCornerLeft": [ - 423.83, - 811.05 - ], - "mouthCornerRight": [ - 575.09, - 805.14 - ], - "upperLipTopCenter": [ - 498.47, - 778.0 - ], - "lowerLipBottomCenter": [ - 501.51, - 841.17 - ], - "chinTip": [ - 506.07, - 930.63 - ], - "faceWidthAtEyeLevelLeft": [ - 274.49, - 635.04 - ], - "faceWidthAtEyeLevelRight": [ - 708.42, - 617.94 - ] - }, - "landmarkDetector": "mediapipe FaceLandmarker (tasks) 1.0.1, model=face_landmarker(float16, v1)", - "pipelineRun": { - "persona": "P6", - "timestamp": "2026-10-01T22:02:58", - "completed": true, - "stages": [ - { - "stage": "brow_sprite", - "ok": true, - "elapsedSec": 2.67 - }, - { - "stage": "paper_grain", - "ok": true, - "elapsedSec": 2.23 - }, - { - "stage": "lip_texture", - "ok": true, - "elapsedSec": 2.14 - }, - { - "stage": "jaw_pieces", - "ok": true, - "elapsedSec": 2.12 - }, - { - "stage": "export_rig", - "ok": true, - "elapsedSec": 15.98 - }, - { - "stage": "final_previews", - "ok": true, - "elapsedSec": 19.27 - } - ] - }, - "browCenterline": { - "method": "Otsu(front lum in bbox) AND diff(F-front)>=25, morphological closing to merge hatching, single global degree-2 polyfit on column-weighted centroid with one-pass outlier removal", - "diffThresh": 25.0, - "bboxPad": { - "x": 25.0, - "yUp": 42.0, - "yDown": 14.0 - }, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "outlierStdMult": 2.0, - "edgeInsetFrac": 0.03, - "peakWindow": [ - 0.25, - 0.6 - ], - "peakFallbackFrac": 0.35, - "peakFlatHeightPx": 3.0, - "peakFailEdgeFrac": 0.2, - "oldPoints": { - "browLeft": { - "inner": [ - 444.36, - 539.65 - ], - "peak": [ - 348.93, - 502.68 - ], - "outer": [ - 300.84, - 531.41 - ] - }, - "browRight": { - "inner": [ - 532.29, - 536.84 - ], - "peak": [ - 623.51, - 491.68 - ], - "outer": [ - 674.23, - 515.8 - ] - } - }, - "newPoints": { - "browLeft": { - "name": "browLeft", - "bbox": [ - 276, - 461, - 469, - 554 - ], - "otsuThresh": 110.0, - "colRange": [ - 320, - 456 - ], - "width": 136.0, - "nColumns": 137, - "nOutliersRemoved": 10, - "fitCoeffs": [ - 0.00193771, - -1.43485171, - 782.95866324 - ], - "inner": [ - 451.92, - 530.26 - ], - "peak": [ - 367.6, - 517.35 - ], - "outer": [ - 324.08, - 521.47 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.9854, - "checkA_pass": true, - "halfWidthInner": 15.731, - "halfWidthPeak": 10.999, - "halfWidthOuter": 7.875, - "bandX0": 276, - "bandY0": 461, - "colMin": 320, - "colMax": 456 - }, - "browRight": { - "name": "browRight", - "bbox": [ - 507, - 450, - 699, - 551 - ], - "otsuThresh": 107.0, - "colRange": [ - 514, - 657 - ], - "width": 143.0, - "nColumns": 144, - "nOutliersRemoved": 8, - "fitCoeffs": [ - 0.00212651, - -2.56332945, - 1282.72280877 - ], - "inner": [ - 518.29, - 525.41 - ], - "peak": [ - 606.95, - 510.29 - ], - "outer": [ - 652.71, - 515.57 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.9028, - "checkA_pass": true, - "halfWidthInner": 5.647, - "halfWidthPeak": 25.496, - "halfWidthOuter": 4.891, - "bandX0": 507, - "bandY0": 450, - "colMin": 514, - "colMax": 657 - } - }, - "halfWidthLeft": [ - 15.731, - 10.999, - 7.875 - ], - "halfWidthRight": [ - 5.647, - 25.496, - 4.891 - ], - "halfWidthNote": "원화 눈썹 잉크 띠 반두께[inner,peak,outer](px, 중심선 법선 방향). export_rig.py가 persona.json browHalfWidthFromArt!=false(기본)면 RigBrow.halfWidth로 내보낸다.", - "checkB_innerMinusPeakY": { - "browLeft": 12.91, - "browRight": 15.12 - }, - "checkD_symmetry": { - "innerYDiff": 4.85, - "peakYDiff": 7.06 - }, - "evidenceImage": "preview/v2/brow-centerline.jpg" - }, - "schemaVersion": "vignette.avatar.v3.layers.v1", - "persona": "P6", - "canvas": { - "w": 1005, - "h": 1566 - }, - "base": { - "front": "base/base-front.png", - "faceless": "base/base-faceless.png", - "facelessPadded": "base/base-faceless-padded.png", - "facelessSize": [ - 1005, - 1566 - ], - "padNote": "base-faceless.png 마지막 행을 0번 복제해 1005x1566(base-front.png 크기)으로 패딩한 것이 F다." - }, - "segmenter": { - "model": "selfie_multiclass_256x256.tflite (mediapipe ImageSegmenter, storage.googleapis.com)", - "categories": { - "0": "background", - "1": "hair", - "2": "bodySkin", - "3": "faceSkin", - "4": "clothes", - "5": "others" - }, - "categoryPixelCounts": { - "0": 492868, - "1": 349798, - "2": 71758, - "3": 188365, - "4": 471041 - }, - "hairEdgeRefine": { - "dilatePx": 6, - "lumThreshold": 110, - "addedPixels": 16042 - } - }, - "chinLine": { - "landmarkIndex": 152, - "xy": [ - 506.07, - 930.63 - ], - "marginPx": 8, - "cutY": 938.63 - }, - "faceOval": { - "landmarkLoop": [ - 10, - 338, - 297, - 332, - 284, - 251, - 389, - 356, - 454, - 323, - 361, - 288, - 397, - 365, - 379, - 378, - 400, - 377, - 152, - 148, - 176, - 149, - 150, - 136, - 172, - 58, - 132, - 93, - 234, - 127, - 162, - 21, - 54, - 103, - 67, - 109 - ], - "scale": 1.04, - "sourceImage": "base/base-front.png" - }, - "layers": [ - { - "id": "body", - "file": "layers/body.png", - "source": "base-faceless-masked+regenerated-fill", - "alphaBBox": [ - 0, - 844, - 1005, - 1566 - ], - "opaquePixels": 586434, - "greenResidue": 0, - "fillTargetPixels": 66153, - "fillSourceRaw": "raw/body.png (rejected 재생성이 아니라 수용된 body.png의 크로마키 결과; head/hairFront와 달리 body 재생성은 승인됨)" - }, - { - "id": "head", - "file": "layers/head.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 70, - 81, - 905, - 1146 - ], - "opaquePixels": 534350, - "greenResidue": 0 - }, - { - "id": "hairFront", - "file": "layers/hairFront.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 257, - 399, - 720, - 944 - ], - "opaquePixels": 26531, - "greenResidue": 0 - } - ], - "rejectedRawEdits": { - "body": { - "file": "raw/body.png", - "status": "accepted-as-fill-source", - "reason": "정렬 (0,0), 형태 변형 없음 — head_mask 채움 전용 소스로 재사용" - } - }, - "composite": { - "meanAbsDiff": { - "full": 1.40462036348695, - "face": 0.4177874589464975, - "hairOutline": 7.014684261853622 - }, - "target": { - "full": 3.0, - "pass": true - }, - "backgroundNote": "카테고리0(배경)은 종이결 텍스처 노이즈(표준편차 ~29/채널)를 포함해 평탄한 크림(#EEE5D3)과 자체적으로 평균절대차 ~7 차이가 난다." - }, - "layersV2": { - "chinBandFix": { - "targetRegionPixels": 14071, - "sourceBandHeight": 150, - "rowJitterRange": [ - -2.0, - 2.0 - ], - "rngSeed": 20260930 - }, - "hairEdgeFix": { - "bandOuterPx": 40, - "bandInnerPx": 6, - "lumSmoothstep": [ - 205.0, - 150.0 - ], - "minFragmentAreaPx": 60, - "head": { - "bandOuterPixels": 105949, - "bandInnerPixels": 58333, - "componentsBefore": 173, - "removed": 171, - "removedPixels": 951 - }, - "hairFront": { - "bandOuterPixels": 0, - "bandInnerPixels": 16912, - "componentsBefore": 149, - "removed": 147, - "removedPixels": 307 - } - }, - "hairPaperGapFix": { - "regionDilatePx": 6, - "paperColorDistThreshold": 40.0, - "paperLumThreshold": 195.0, - "paperSatThreshold": 40.0, - "opaqueAlphaThreshold": 0.95, - "regionPixels": 380353, - "head": { - "targetPixels": 1237, - "componentsBefore": 3, - "removed": 1, - "removedPixels": 1, - "faceSkinAlphaDiff": 0.0 - }, - "hairFront": { - "targetPixels": 1, - "componentsBefore": 2, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - } - }, - "jawInkFix": { - "mouthCenterY": 803.47, - "distThresholdPx": 14.0, - "mergePx": 3, - "aspectThreshold": 1.0, - "removedGroups": [ - { - "pixels": 7827, - "bbox": [ - 311, - 804, - 681, - 945 - ], - "width": 370, - "height": 141, - "aspect": 2.624 - } - ], - "keptGroups": [], - "removedPixels": 7827, - "strayFragmentCleanup": { - "componentsBefore": 5, - "removed": 3, - "removedPixels": 3 - } - }, - "metrics": { - "body": { - "haloPct": 3.2908268202385846 - }, - "head": { - "haloPct": 0.3532219570405728, - "bgLeakAlphaMean": 0.0, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - }, - "hairFront": { - "haloPct": 0.0, - "bgLeakAlphaMean": 0.0, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - } - }, - "layers": [ - { - "id": "body", - "bbox": [ - 0, - 844, - 1005, - 1566 - ], - "opaque": 586357, - "greenResidue": 0 - }, - { - "id": "head", - "bbox": [ - 37, - 83, - 924, - 1146 - ], - "opaque": 547008, - "greenResidue": 0 - }, - { - "id": "hairFront", - "bbox": [ - 257, - 399, - 720, - 928 - ], - "opaque": 27620, - "greenResidue": 0 - } - ], - "faceDetail": { - "browLandmarksSource": "manifest.landmarks.eyebrowLeft/Right", - "region": "face", - "faceInsetPx": 24.0, - "matchFacelessColor": true, - "matchFacelessColorDeltaRgb": { - "eyeLeft": [ - -17.96, - -18.35, - -19.46 - ], - "eyeRight": [ - -21.14, - -21.78, - -21.82 - ], - "browLeft": [ - -1.83, - -1.9, - -4.89 - ], - "browRight": [ - -2.88, - -3.74, - -7.0 - ], - "mouth": [ - -1.85, - -0.97, - -2.98 - ] - }, - "regions": { - "eyeLeft": [ - 311.4, - 507.4, - 457.4, - 670.1 - ], - "eyeRight": [ - 523.9, - 500.3, - 669.8, - 661.8 - ], - "mouth": [ - 378.8, - 741.5, - 620.1, - 915.6 - ], - "moles": [] - }, - "exclusionPixels": { - "eyeLeft": 12236, - "eyeRight": 12954, - "browLeft": 11741, - "browRight": 13108, - "mouth": 19919, - "union": 57733 - }, - "hairMaskPixels": 370233, - "bbox": [ - 272, - 404, - 711, - 933 - ], - "textureCloneStamp": { - "cloneTargetThreshold": 65.0, - "avoidThreshold": 110.0, - "lineOpeningIterations": 3, - "featherPx": 4.0, - "badTexturePixels": 66, - "preservedShadowPixels": 0, - "components": [ - { - "componentId": 1, - "pixels": 29, - "offset": [ - 60, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 2, - "pixels": 1, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 3, - "pixels": 1, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 4, - "pixels": 8, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 5, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 6, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 7, - "pixels": 2, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 8, - "pixels": 1, - "offset": [ - -40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 9, - "pixels": 1, - "offset": [ - 0, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 10, - "pixels": 9, - "offset": [ - 0, - -80 - ], - "method": "clone-stamp" - }, - { - "componentId": 11, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 12, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 13, - "pixels": 2, - "offset": [ - 40, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 14, - "pixels": 2, - "offset": [ - 0, - -80 - ], - "method": "clone-stamp" - }, - { - "componentId": 15, - "pixels": 2, - "offset": [ - 0, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 16, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 17, - "pixels": 1, - "offset": [ - 0, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 18, - "pixels": 1, - "offset": [ - -40, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 19, - "pixels": 1, - "offset": [ - -40, - -40 - ], - "method": "clone-stamp" - } - ] - }, - "genericPatches": { - "forehead": [ - 443, - 410, - 491, - 458 - ], - "cheekLeft": [ - 331, - 648, - 379, - 696 - ], - "cheekRight": [ - 515, - 639, - 563, - 687 - ] - }, - "genericPatchBaseline": { - "highFreqEnergy": 9.2469051470674, - "lineEnergy": 4.048168758136364 - }, - "checkA_meanAbsDiff": 0.4896796010315382, - "checkB_seamLumDiff": 0.5417158200004906, - "checkC_holeDarkResiduePct": 0.15155003069367712, - "checkC_violationPixels": 79, - "checkD_texturePreservation": { - "eyeLeft": { - "insideEnergy": 9.229265393541892, - "baselineEnergy": 9.2469051470674, - "ratio": 0.9980923613636178, - "pass": true - }, - "eyeRight": { - "insideEnergy": 10.786136096647196, - "baselineEnergy": 9.2469051470674, - "ratio": 1.166459039548811, - "pass": true - }, - "browLeft": { - "insideEnergy": 8.548916746600598, - "baselineEnergy": 9.2469051470674, - "ratio": 0.9245165393863519, - "pass": true - }, - "browRight": { - "insideEnergy": 9.979914063921097, - "baselineEnergy": 9.2469051470674, - "ratio": 1.0792707295246957, - "pass": true - }, - "mouth": { - "insideEnergy": 8.022835014361053, - "baselineEnergy": 9.2469051470674, - "ratio": 0.867623803506349, - "pass": true - } - }, - "checkD_allPass": true, - "checkE_ghostOutline": { - "eyeLeft": { - "bandLineEnergy": 7.355716132534533, - "baselineLineEnergy": 4.048168758136364, - "ratio": 1.8170477991438403, - "pass": false - }, - "eyeRight": { - "bandLineEnergy": 7.604336496690048, - "baselineLineEnergy": 4.048168758136364, - "ratio": 1.8784633129254276, - "pass": false - }, - "browLeft": { - "bandLineEnergy": 3.446691194533974, - "baselineLineEnergy": 4.048168758136364, - "ratio": 0.8514198395525168, - "pass": true - }, - "browRight": { - "bandLineEnergy": 3.9400153277490557, - "baselineLineEnergy": 4.048168758136364, - "ratio": 0.9732833691357525, - "pass": true - }, - "mouth": { - "bandLineEnergy": 3.3578240037031386, - "baselineLineEnergy": 4.048168758136364, - "ratio": 0.8294673973149687, - "pass": true - } - }, - "checkE_allPass": false, - "checkDarkCircle": { - "eyeLeft": { - "compositeMean": 133.09809523809525, - "frontMean": 132.64361904761904, - "diff": 0.454476190476214, - "pass": true - }, - "eyeRight": { - "compositeMean": 135.7567619047619, - "frontMean": 135.64457142857142, - "diff": 0.11219047619047728, - "pass": true - } - }, - "checkDarkCircle_allPass": true - }, - "paperGrain": { - "sourceImage": "base/base-front.png", - "sourceOrigin": [ - 739, - 10 - ], - "tileSize": 256, - "personMarginPx": 20, - "cleanFraction": 0.98858642578125, - "contaminatedPixelsFilled": 748, - "chromaKeep": 0.3, - "targetLumRange": [ - 245.0, - 250.0 - ], - "lumShift": 22.521382649739564, - "finalMeanLum": 247.44620143538134, - "finalLumStd": 3.3603736380015956, - "seamContinuity": { - "leftRightDiff": 0.1671720490391048, - "topBottomDiff": 0.34696069563338483 - } - } - }, - "lipTexture": { - "designVersion": "A5(아랫입술 그늘 띠를 lip-shadow 조각으로 분리)", - "landmarks": { - "mouthCornerLeft": [ - 423.83, - 811.05 - ], - "mouthCornerRight": [ - 575.09, - 805.14 - ], - "upperLipTop": [ - 498.47, - 778.0 - ], - "lowerLipBottom": [ - 501.51, - 841.17 - ], - "mouthCenter": [ - 499.7782923281193, - 803.4675067663193 - ] - }, - "capBBox": [ - 418.46, - 774.59, - 580.46, - 844.59 - ], - "otsuThreshLabA": 139.0, - "diffThresh": 20.0, - "dilatePx": 10.0, - "alphaRampPx": 4.0, - "splitOverlapPx": 2.0, - "shadowBandPx": 18.0, - "shadowTopOverlapPx": 6.0, - "shadowSideRampPx": 6.0, - "shadowTopRampPx": 4.0, - "checks": { - "lip-upper": { - "regionPixelCount": 5306, - "fullOpacityPixelCount": 4597, - "rampPixelCount": 709, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 42, - 30, - 158, - 49 - ] - }, - "lip-lower": { - "regionPixelCount": 6041, - "fullOpacityPixelCount": 5354, - "rampPixelCount": 687, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 41, - 69, - 160, - 48 - ] - }, - "lip-shadow": { - "regionPixelCount": 3360, - "fullOpacityPixelCount": 1880, - "rampPixelCount": 1480, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 51, - 72, - 140, - 53 - ] - }, - "splitOverlapPixelCount": 316, - "splitOverlapBandOk": true, - "splitOverlapAlphaMaxDiff": 0, - "splitOverlapConsistentOk": true, - "splitCoverageMismatchPixelCount": 0, - "splitCheckOk": true, - "shadowLowerOverlapPixelCount": 2445, - "shadowLowerOverlapMaxDiff": 21.91372549019607, - "shadowLowerOverlapDiffGt1PixelCount": 116, - "shadowLowerOverlapOk": false - }, - "check3NeutralComposite": { - "fullOpacityMaxDiff": 0.0, - "rampMeanAbsDiff": 10.831, - "mouthBBoxMeanAbsDiff": 2.13 - }, - "pngFileSizes": { - "lip-upper": 21193, - "lip-lower": 21453, - "lip-shadow": 20989 - }, - "canvasOrigin": { - "upper": [ - 420, - 765 - ], - "lower": [ - 419, - 804 - ], - "shadow": [ - 429, - 807 - ] - }, - "evidenceImage": "preview/v2/lip-texture.jpg", - "check4WebpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jaw": { - "designVersion": "A5(결정문 §8.4 하관 띠 변형 — 턱 조각 원본)", - "noseTip": [ - 495.58, - 716.54 - ], - "chinTip": [ - 506.07, - 930.63 - ], - "yFormula": "noseTip.y - 50 - 10 .. chinTip.y + 40", - "noseToTopPx": 60.0, - "chinToBottomPx": 40.0, - "ovalOutsetPx": 18.0, - "faceOvalSource": "export_rig.detect_face_landmarks + compute_face_oval(base-front.png, 결정적)", - "bboxCanvas": [ - 251, - 656, - 733, - 971 - ], - "naiveXRangeForComparison": [ - 251, - 733 - ], - "checks": { - "head": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 333372 - }, - "detail": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 366349 - } - }, - "evidenceImage": "preview/v2/jaw-pieces.jpg", - "check1VsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 3.533, - "maxAbsDiffPremultipliedRgb": 52.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.784, - "maxAbsDiffPremultipliedRgb": 40.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 14714, - "lip-lower": 15166, - "lip-shadow": 15138 - }, - "totalBytes": 45018, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 31190, - "jaw-detail": 24094 - }, - "totalBytes": 55284, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 3.533, - "maxAbsDiffPremultipliedRgb": 52.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.784, - "maxAbsDiffPremultipliedRgb": 40.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "browSprite": { - "enabled": true, - "bandExtraHalfHeightPx": 8.0, - "bandEndExtendPx": 6.0, - "diffThresh": 25.0, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "hairSource": "layers/v2/hairFront.png alpha(게시되는 레이어 그 자체, 분할 카테고리 아님)", - "hairAlphaThresh": 128.0, - "hairDilateIterations": 1, - "rampPx": 1.5, - "inkLumMethod": "median(ink-classified pixels in band, region after hair removal)", - "minAlphaForUnmix": 0.05, - "hairMaskPixelCount": 30780, - "left": { - "bboxCanvas": [ - 323, - 504, - 134, - 49 - ], - "halfHeightPx": 23.731, - "otsuThresh": 111.0, - "lSkin": 184.333, - "lInk": 42.667, - "regionPixelCount": 2898, - "hairOverlapPixelCount": 43, - "neutralCompositeMeanAbsDiffAlphaRegion": 5.492, - "pngFileSizeBytes": 9675 - }, - "right": { - "bboxCanvas": [ - 514, - 492, - 137, - 57 - ], - "halfHeightPx": 33.496, - "otsuThresh": 107.0, - "lSkin": 183.0, - "lInk": 42.333, - "regionPixelCount": 2888, - "hairOverlapPixelCount": 77, - "neutralCompositeMeanAbsDiffAlphaRegion": 4.641, - "pngFileSizeBytes": 10134 - }, - "canvasOrigin": { - "left": [ - 323.0, - 504.0 - ], - "right": [ - 514.0, - 492.0 - ] - }, - "evidenceImage": "preview/v2/brow-sprite.jpg", - "checkWebpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "eyeLineScale": { - "enabled": true, - "method": "눈 폭 가운데 60% 열마다 upperLidTop.y-12 ~ 홍채중심y 사이 첫 연속 어두운(lum<80) 구간 길이의 중앙값", - "windowAbovePx": 12.0, - "darkLumThresh": 80.0, - "columnMiddleFrac": 0.6, - "p1BaselinePx": 2.0, - "lineScaleClampRange": [ - 1.0, - 2.5 - ], - "left": { - "measuredPx": 2.0, - "nColumns": 55, - "ratio": 1.0, - "lineScale": 1.0 - }, - "right": { - "measuredPx": 2.0, - "nColumns": 56, - "ratio": 1.0, - "lineScale": 1.0 - } - }, - "iris": { - "method": "가로: 중심 y 부근 상하 각 4행에서 중심 x를 포함하는 연속 어두운(lum<100) 구간의 중앙값으로 반지름·중심x. 세로: 중심 x 부근 좌우 각 4열에서 중심 y를 포함하는 연속 어두운 구간의 아래쪽 끝 중앙값에서 가로 반지름을 빼 중심y(세로축은 윗눈꺼풀 잉크와 홍채가 같은 톤으로 이어져 위쪽 경계를 가를 수 없다).", - "darkLumThresh": 100.0, - "scanBandPx": 4, - "left": { - "measuredCenter": [ - 386.5, - 577.5 - ], - "measuredRadius": 20.5, - "rigCenter": [ - 388.77, - 581.09 - ], - "rigRadius": 20.65, - "nRowsHorizontal": 9, - "nColsVertical": 9 - }, - "right": { - "measuredCenter": [ - 595.0, - 568.0 - ], - "measuredRadius": 20.0, - "rigCenter": [ - 594.19, - 571.71 - ], - "rigRadius": 20.75, - "nRowsHorizontal": 9, - "nColsVertical": 9 - } - }, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 6056, - "brow-right": 6426 - }, - "totalBytes": 12482, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p6-linocut/persona.json b/docs/avatar-art/p6-linocut/persona.json deleted file mode 100644 index 9a06bb2..0000000 --- a/docs/avatar-art/p6-linocut/persona.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "code": "P6", - "publicSlug": "p6", - "rigFileName": "p6Rig.ts", - "rigExportName": "P6_LINOCUT_RIG", - "styleFrame": "../art-direction-v3/p1/r2-b-linocut.png", - "paletteSamples": { - "ink": { - "box": [300, 150, 650, 300], - "lumThreshold": 55 - }, - "motifPetalBoxes": [ - { "label": "sun", "kind": "ochre", "box": [1230, 10, 1536, 210] }, - { "label": "tulip", "kind": "ochre", "box": [1030, 280, 1170, 560] } - ], - "motifLeafBoxes": [ - { "label": "cloudLeft", "kind": "blue", "box": [20, 20, 380, 190] }, - { "label": "raincloud", "kind": "blue", "box": [520, 10, 930, 230] }, - { "label": "wiltedFlowerLeaf", "kind": "blue", "box": [520, 280, 650, 580] }, - { "label": "closedBudLeaf", "kind": "blue", "box": [10, 290, 110, 570] } - ] - }, - "faceDetail": { - "moles": [], - "region": "face", - "faceInsetPx": 24, - "matchFacelessColor": true - }, - "pivots": { - "neck": [506, 1001], - "body": [502, 1566], - "face": [488, 661] - }, - "crops": { - "bustYOffset": 40 - } -} diff --git a/docs/avatar-art/p6-linocut/preview/qa/face-art-vs-render.jpg b/docs/avatar-art/p6-linocut/preview/qa/face-art-vs-render.jpg deleted file mode 100644 index aa01c4c..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/qa/face-art-vs-render.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/qa/face-zoom.jpg b/docs/avatar-art/p6-linocut/preview/qa/face-zoom.jpg deleted file mode 100644 index b9ee5d1..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/qa/face-zoom.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/qa/neutral-vs-art.jpg b/docs/avatar-art/p6-linocut/preview/qa/neutral-vs-art.jpg deleted file mode 100644 index 0dc9dea..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/qa/neutral-vs-art.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/qa/normalized.jpg b/docs/avatar-art/p6-linocut/preview/qa/normalized.jpg deleted file mode 100644 index 63c124a..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/qa/normalized.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/qa/sheet-expr-bust.jpg b/docs/avatar-art/p6-linocut/preview/qa/sheet-expr-bust.jpg deleted file mode 100644 index 7a53085..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/qa/sheet-expr-bust.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/qa/sheet-expr-face.jpg b/docs/avatar-art/p6-linocut/preview/qa/sheet-expr-face.jpg deleted file mode 100644 index 36d083e..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/qa/sheet-expr-face.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/qa/sheet-viseme.jpg b/docs/avatar-art/p6-linocut/preview/qa/sheet-viseme.jpg deleted file mode 100644 index bac742d..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/qa/sheet-viseme.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/qa/viseme-timeline.json b/docs/avatar-art/p6-linocut/preview/qa/viseme-timeline.json deleted file mode 100644 index 16698bd..0000000 --- a/docs/avatar-art/p6-linocut/preview/qa/viseme-timeline.json +++ /dev/null @@ -1,287 +0,0 @@ -[ - { - "t": 0, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00000.png" - }, - { - "t": 50, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00050.png" - }, - { - "t": 100, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00100.png" - }, - { - "t": 150, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00150.png" - }, - { - "t": 200, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00200.png" - }, - { - "t": 250, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00250.png" - }, - { - "t": 300, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00300.png" - }, - { - "t": 350, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00350.png" - }, - { - "t": 400, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00400.png" - }, - { - "t": 450, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00450.png" - }, - { - "t": 500, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00500.png" - }, - { - "t": 550, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00550.png" - }, - { - "t": 600, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00600.png" - }, - { - "t": 650, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00650.png" - }, - { - "t": 700, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00700.png" - }, - { - "t": 750, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00750.png" - }, - { - "t": 800, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00800.png" - }, - { - "t": 850, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00850.png" - }, - { - "t": 900, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00900.png" - }, - { - "t": 950, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t00950.png" - }, - { - "t": 1000, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01000.png" - }, - { - "t": 1050, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01050.png" - }, - { - "t": 1100, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01100.png" - }, - { - "t": 1150, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01150.png" - }, - { - "t": 1200, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01200.png" - }, - { - "t": 1250, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01250.png" - }, - { - "t": 1300, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01300.png" - }, - { - "t": 1350, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01350.png" - }, - { - "t": 1400, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01400.png" - }, - { - "t": 1450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01450.png" - }, - { - "t": 1500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01500.png" - }, - { - "t": 1550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01550.png" - }, - { - "t": 1600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01600.png" - }, - { - "t": 1650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01650.png" - }, - { - "t": 1700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01700.png" - }, - { - "t": 1750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01750.png" - }, - { - "t": 1800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01800.png" - }, - { - "t": 1850, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01850.png" - }, - { - "t": 1900, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01900.png" - }, - { - "t": 1950, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t01950.png" - }, - { - "t": 2000, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02000.png" - }, - { - "t": 2050, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02050.png" - }, - { - "t": 2100, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02100.png" - }, - { - "t": 2150, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02150.png" - }, - { - "t": 2200, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02200.png" - }, - { - "t": 2250, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02250.png" - }, - { - "t": 2300, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02300.png" - }, - { - "t": 2350, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02350.png" - }, - { - "t": 2400, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02400.png" - }, - { - "t": 2450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02450.png" - }, - { - "t": 2500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02500.png" - }, - { - "t": 2550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02550.png" - }, - { - "t": 2600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02600.png" - }, - { - "t": 2650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02650.png" - }, - { - "t": 2700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02700.png" - }, - { - "t": 2750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02750.png" - }, - { - "t": 2800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p6-linocut\\preview\\qa\\frames\\viseme-scan-t02800.png" - } -] \ No newline at end of file diff --git a/docs/avatar-art/p6-linocut/preview/v2/brow-centerline.jpg b/docs/avatar-art/p6-linocut/preview/v2/brow-centerline.jpg deleted file mode 100644 index a8d026a..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/v2/brow-centerline.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/v2/brow-sprite.jpg b/docs/avatar-art/p6-linocut/preview/v2/brow-sprite.jpg deleted file mode 100644 index dc8a786..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/v2/brow-sprite.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/v2/export-rig-report.json b/docs/avatar-art/p6-linocut/preview/v2/export-rig-report.json deleted file mode 100644 index 697e888..0000000 --- a/docs/avatar-art/p6-linocut/preview/v2/export-rig-report.json +++ /dev/null @@ -1,676 +0,0 @@ -{ - "faceOvalBBox": [ - 260.0, - 397.6, - 717.9, - 941.0 - ], - "cropsFace": [ - 184.6, - 365.0, - 608.6, - 608.6 - ], - "mouthCenter": [ - 499.8, - 803.5 - ], - "palette": { - "ink": "#242322", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#A27659", - "lipLower": "#A47459", - "mouthLine": "#2C2625", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "paletteSamples": { - "eyeLeft": { - "sclera": "#B28C67", - "iris": "#312B25", - "irisRing": "#544234", - "n": { - "sclera": 745, - "iris": 193, - "irisRing": 386 - } - }, - "eyeRight": { - "sclera": "#BA926C", - "iris": "#342D26", - "irisRing": "#6B523F", - "n": { - "sclera": 807, - "iris": 195, - "irisRing": 398 - } - }, - "scleraIrisIrisRingFixedOverride": { - "reason": "persona.json paletteSamples.eyeOverride(고정 설계값)", - "measured": { - "sclera": "#B68F6A", - "iris": "#322C26", - "irisRing": "#5F4A39" - }, - "fixed": { - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F" - } - }, - "ink": { - "box": [ - 300, - 150, - 650, - 300 - ], - "lumThreshold": 55.0, - "n": 40816, - "hex": "#242322" - }, - "lipUpper": { - "box": [ - 418, - 775, - 580, - 801 - ], - "hex": "#A27659" - }, - "lipLower": { - "box": [ - 418, - 805, - 580, - 844 - ], - "hex": "#A47459" - }, - "mouthLine": { - "box": [ - 438, - 801, - 560, - 806 - ], - "hex": "#2C2625" - }, - "motifPetal": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "sun": { - "box": [ - 1230, - 10, - 1536, - 210 - ], - "n": 20570, - "hex": "#D3A661" - }, - "tulip": { - "box": [ - 1030, - 280, - 1170, - 560 - ], - "n": 4314, - "hex": "#CCA063" - } - }, - "hex": "#D0A362" - }, - "motifLeaf": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "cloudLeft": { - "box": [ - 20, - 20, - 380, - 190 - ], - "n": 21312, - "hex": "#636D76" - }, - "raincloud": { - "box": [ - 520, - 10, - 930, - 230 - ], - "n": 27607, - "hex": "#586B76" - }, - "wiltedFlowerLeaf": { - "box": [ - 520, - 280, - 650, - 580 - ], - "n": 5818, - "hex": "#495961" - }, - "closedBudLeaf": { - "box": [ - 10, - 290, - 110, - 570 - ], - "n": 6589, - "hex": "#495961" - } - }, - "hex": "#53626C" - } - }, - "publish": { - "fileSizes": { - "body": 171266, - "head": 170650, - "hair-front": 20596, - "face-detail": 45114, - "paper-grain": 1816 - }, - "encodeMeta": { - "quality": 80, - "scale": 1.0, - "totalBytes": 409442 - }, - "totalBytes": 409442 - }, - "grain": { - "href": "/avatar/v3/p6/paper-grain.webp", - "size": 256 - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 14714, - "lip-lower": 15166, - "lip-shadow": 15138 - }, - "totalBytes": 45018, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 31190, - "jaw-detail": 24094 - }, - "totalBytes": 55284, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 3.533, - "maxAbsDiffPremultipliedRgb": 52.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.784, - "maxAbsDiffPremultipliedRgb": 40.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 6056, - "brow-right": 6426 - }, - "totalBytes": 12482, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "rig": { - "schemaVersion": "vignette.avatar.v3.rig.v1", - "persona": "P6", - "canvas": { - "w": 1005, - "h": 1566 - }, - "layers": { - "body": { - "href": "/avatar/v3/p6/body.webp", - "x": 0, - "y": 844, - "w": 1005, - "h": 722 - }, - "head": { - "href": "/avatar/v3/p6/head.webp", - "x": 37, - "y": 83, - "w": 887, - "h": 1063 - }, - "hairFront": { - "href": "/avatar/v3/p6/hair-front.webp", - "x": 257, - "y": 399, - "w": 463, - "h": 529 - }, - "faceDetail": { - "href": "/avatar/v3/p6/face-detail.webp", - "x": 272, - "y": 404, - "w": 439, - "h": 529 - } - }, - "grain": { - "href": "/avatar/v3/p6/paper-grain.webp", - "size": 256 - }, - "lipTexture": { - "upper": { - "href": "/avatar/v3/p6/lip-upper.webp", - "x": 420, - "y": 765, - "w": 158, - "h": 49 - }, - "lower": { - "href": "/avatar/v3/p6/lip-lower.webp", - "x": 419, - "y": 804, - "w": 160, - "h": 48 - }, - "shadow": { - "href": "/avatar/v3/p6/lip-shadow.webp", - "x": 429, - "y": 807, - "w": 140, - "h": 53 - } - }, - "jaw": { - "head": { - "href": "/avatar/v3/p6/jaw-head.webp", - "x": 251, - "y": 656, - "w": 482, - "h": 315 - }, - "detail": { - "href": "/avatar/v3/p6/jaw-detail.webp", - "x": 251, - "y": 656, - "w": 482, - "h": 315 - } - }, - "motif": null, - "pivots": { - "neck": [ - 506, - 1001 - ], - "body": [ - 502, - 1566 - ], - "face": [ - 488, - 661 - ] - }, - "crops": { - "portrait": [ - 0, - 0, - 1005, - 1566 - ], - "bust": [ - 0.0, - 40.0, - 1005.0, - 1005.0 - ], - "face": [ - 184.62152092794975, - 365.04152293910977, - 608.5970685722352, - 608.5970685722352 - ] - }, - "faceOval": [ - [ - 480.8, - 398.4 - ], - [ - 546.8, - 397.6 - ], - [ - 602.0, - 404.4 - ], - [ - 651.9, - 421.0 - ], - [ - 683.6, - 447.7 - ], - [ - 702.8, - 483.5 - ], - [ - 713.5, - 522.5 - ], - [ - 717.9, - 570.3 - ], - [ - 717.0, - 615.8 - ], - [ - 714.2, - 663.3 - ], - [ - 706.6, - 713.5 - ], - [ - 692.8, - 766.8 - ], - [ - 673.8, - 811.4 - ], - [ - 651.7, - 845.0 - ], - [ - 623.5, - 875.2 - ], - [ - 600.1, - 897.0 - ], - [ - 575.1, - 917.5 - ], - [ - 545.4, - 934.4 - ], - [ - 506.6, - 941.0 - ], - [ - 467.6, - 937.9 - ], - [ - 437.3, - 924.0 - ], - [ - 410.8, - 905.8 - ], - [ - 385.5, - 886.6 - ], - [ - 354.0, - 859.0 - ], - [ - 328.2, - 826.9 - ], - [ - 304.3, - 783.8 - ], - [ - 285.2, - 730.7 - ], - [ - 272.8, - 680.6 - ], - [ - 265.7, - 633.6 - ], - [ - 260.0, - 588.5 - ], - [ - 260.0, - 540.9 - ], - [ - 267.4, - 500.8 - ], - [ - 283.6, - 463.8 - ], - [ - 312.3, - 434.4 - ], - [ - 359.6, - 413.5 - ], - [ - 414.2, - 402.7 - ] - ], - "landmarks": { - "eyeLeft": { - "inner": [ - 432.4, - 593.7 - ], - "outer": [ - 341.4, - 583.9 - ], - "upperLidTop": [ - 378.1, - 566.0 - ], - "lowerLidBottom": [ - 383.4, - 600.1 - ], - "iris": { - "center": [ - 386.5, - 577.5 - ], - "radius": 20.5 - }, - "lineScale": 1.0 - }, - "eyeRight": { - "inner": [ - 548.9, - 589.6 - ], - "outer": [ - 639.8, - 570.8 - ], - "upperLidTop": [ - 601.4, - 555.9 - ], - "lowerLidBottom": [ - 598.4, - 591.8 - ], - "iris": { - "center": [ - 595.0, - 568.0 - ], - "radius": 20.0 - }, - "lineScale": 1.0 - }, - "browLeft": { - "inner": [ - 451.9, - 530.3 - ], - "peak": [ - 367.6, - 517.4 - ], - "outer": [ - 324.1, - 521.5 - ], - "halfWidth": [ - 15.73, - 11.0, - 7.88 - ], - "sprite": { - "href": "/avatar/v3/p6/brow-left.webp", - "x": 323.0, - "y": 504.0, - "w": 134, - "h": 49 - } - }, - "browRight": { - "inner": [ - 518.3, - 525.4 - ], - "peak": [ - 607.0, - 510.3 - ], - "outer": [ - 652.7, - 515.6 - ], - "halfWidth": [ - 5.65, - 25.5, - 4.89 - ], - "sprite": { - "href": "/avatar/v3/p6/brow-right.webp", - "x": 514.0, - "y": 492.0, - "w": 137, - "h": 57 - } - }, - "noseTip": [ - 495.6, - 716.5 - ], - "mouthCornerLeft": [ - 423.8, - 811.0 - ], - "mouthCornerRight": [ - 575.1, - 805.1 - ], - "upperLipTop": [ - 498.5, - 778.0 - ], - "lowerLipBottom": [ - 501.5, - 841.2 - ], - "mouthCenter": [ - 499.8, - 803.5 - ], - "chinTip": [ - 506.1, - 930.6 - ] - }, - "palette": { - "ink": "#242322", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#A27659", - "lipLower": "#A47459", - "mouthLine": "#2C2625", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "backdrop": { - "cognitive": "#ECE3D1", - "positive": "#F1DEC2", - "negative": "#DCE0E2", - "defensive": "#E6DAD3", - "energy": "#E2E0D0" - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p6-linocut/preview/v2/ghost-check.jpg b/docs/avatar-art/p6-linocut/preview/v2/ghost-check.jpg deleted file mode 100644 index 6b289df..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/v2/ghost-check.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/v2/holes-texture.jpg b/docs/avatar-art/p6-linocut/preview/v2/holes-texture.jpg deleted file mode 100644 index a98dee3..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/v2/holes-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/v2/jaw-pieces.jpg b/docs/avatar-art/p6-linocut/preview/v2/jaw-pieces.jpg deleted file mode 100644 index 0bb36a3..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/v2/jaw-pieces.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/preview/v2/lip-texture.jpg b/docs/avatar-art/p6-linocut/preview/v2/lip-texture.jpg deleted file mode 100644 index 4e85525..0000000 Binary files a/docs/avatar-art/p6-linocut/preview/v2/lip-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p6-linocut/prompts/base-faceless.txt b/docs/avatar-art/p6-linocut/prompts/base-faceless.txt deleted file mode 100644 index 1f5627f..0000000 --- a/docs/avatar-art/p6-linocut/prompts/base-faceless.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as faceless.png: - -Redraw exactly the same girl, same frontal pose, same framing and same linocut relief-print style as the reference, but leave the face blank: remove the eyes, eyebrows and mouth completely and fill those areas with plain skin in the same flat skin color, keeping the nose, the face outline, the ears, the hair, the neck, the claw clip in her hair, the striped shirt and the off-white cable-knit cardigan with its small charcoal smudge exactly as they are. Do not move, resize or redraw anything else. Plain flat cream background, nothing else, no text. diff --git a/docs/avatar-art/p6-linocut/prompts/body.txt b/docs/avatar-art/p6-linocut/prompts/body.txt deleted file mode 100644 index 1558232..0000000 --- a/docs/avatar-art/p6-linocut/prompts/body.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as body.png: - -Keep only the neck, the striped shirt and the off-white cable-knit cardigan, including the small charcoal smudge exactly as they are in the reference: same shapes, same position, same linocut relief-print style, same ink colors and texture. Remove the head, the face, the ears and all of the hair completely. Where the hair or the chin previously covered the neck or the clothes, continue them naturally in the same style so there are no holes. Place the result on a plain flat pure green background (#00ff00) with nothing else, no text. diff --git a/docs/avatar-art/p6-linocut/raw/body.png b/docs/avatar-art/p6-linocut/raw/body.png deleted file mode 100644 index f852dd9..0000000 Binary files a/docs/avatar-art/p6-linocut/raw/body.png and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/base/base-faceless.png b/docs/avatar-art/p7-linocut/base/base-faceless.png deleted file mode 100644 index 695d472..0000000 Binary files a/docs/avatar-art/p7-linocut/base/base-faceless.png and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/base/base-front.png b/docs/avatar-art/p7-linocut/base/base-front.png deleted file mode 100644 index 62b1d26..0000000 Binary files a/docs/avatar-art/p7-linocut/base/base-front.png and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/base/edits/aligned-body-1.json b/docs/avatar-art/p7-linocut/base/edits/aligned-body-1.json deleted file mode 100644 index 11f5a2f..0000000 --- a/docs/avatar-art/p7-linocut/base/edits/aligned-body-1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\body-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\aligned-body-1.png", - "refSize": [ - 1142, - 1377 - ], - "editedOriginalSize": [ - 1142, - 1377 - ], - "eccCorrelation": 0.99627, - "transform": { - "scaleSqrtDet": 0.99995, - "rotationDeg": 0.002, - "translateX": -0.097, - "translateY": -0.462, - "matrix": [ - [ - 0.999783, - 4.7e-05, - -0.097108 - ], - [ - 3.6e-05, - 1.000121, - -0.46182 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 531858, - "chamferInMask": { - "median": 0.0, - "p90": 1.414, - "nSamples": 84981 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p7-linocut/base/edits/aligned-body-2.json b/docs/avatar-art/p7-linocut/base/edits/aligned-body-2.json deleted file mode 100644 index 0919306..0000000 --- a/docs/avatar-art/p7-linocut/base/edits/aligned-body-2.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mode": "body", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\body-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\aligned-body-2.png", - "refSize": [ - 1142, - 1377 - ], - "editedOriginalSize": [ - 1142, - 1377 - ], - "eccCorrelation": 0.99308, - "transform": { - "scaleSqrtDet": 0.99996, - "rotationDeg": 0.003, - "translateX": -0.473, - "translateY": -0.775, - "matrix": [ - [ - 0.9999, - 0.000127, - -0.472568 - ], - [ - 5.4e-05, - 1.000025, - -0.775239 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 532046, - "chamferInMask": { - "median": 0.0, - "p90": 1.414, - "nSamples": 86334 - } -} \ No newline at end of file diff --git a/docs/avatar-art/p7-linocut/base/edits/aligned-faceless-1.json b/docs/avatar-art/p7-linocut/base/edits/aligned-faceless-1.json deleted file mode 100644 index facd2df..0000000 --- a/docs/avatar-art/p7-linocut/base/edits/aligned-faceless-1.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\faceless-1.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\aligned-faceless-1.png", - "refSize": [ - 1142, - 1377 - ], - "editedOriginalSize": [ - 1142, - 1377 - ], - "eccCorrelation": 0.99353, - "transform": { - "scaleSqrtDet": 0.99995, - "rotationDeg": 0.005, - "translateX": -0.285, - "translateY": -0.603, - "matrix": [ - [ - 0.999806, - -7.3e-05, - -0.284832 - ], - [ - 8.4e-05, - 1.000089, - -0.603197 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 772884, - "chamferInMask": { - "median": 0.0, - "p90": 1.0, - "nSamples": 114122 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 5875 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 396, - 410, - 536, - 489 - ], - "refInkRatio": 0.1821, - "editedInkRatio": 0.0059 - }, - "eyeB": { - "box": [ - 607, - 408, - 748, - 488 - ], - "refInkRatio": 0.1543, - "editedInkRatio": 0.0025 - }, - "browA": { - "box": [ - 354, - 351, - 553, - 438 - ], - "refInkRatio": 0.4338, - "editedInkRatio": 0.303 - }, - "browB": { - "box": [ - 590, - 350, - 787, - 437 - ], - "refInkRatio": 0.2923, - "editedInkRatio": 0.1698 - }, - "mouth": { - "box": [ - 472, - 623, - 667, - 740 - ], - "refInkRatio": 0.064, - "editedInkRatio": 0.0 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 396, - 410, - 536, - 489 - ], - "eyeB": [ - 607, - 408, - 748, - 488 - ], - "browA": [ - 354, - 351, - 553, - 438 - ], - "browB": [ - 590, - 350, - 787, - 437 - ], - "mouth": [ - 472, - 623, - 667, - 740 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p7-linocut/base/edits/aligned-faceless-2.json b/docs/avatar-art/p7-linocut/base/edits/aligned-faceless-2.json deleted file mode 100644 index 8993351..0000000 --- a/docs/avatar-art/p7-linocut/base/edits/aligned-faceless-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "mode": "faceless", - "ref": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\source-front.png", - "edited": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\faceless-2.png", - "out": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\aligned-faceless-2.png", - "refSize": [ - 1142, - 1377 - ], - "editedOriginalSize": [ - 1142, - 1377 - ], - "eccCorrelation": 0.99416, - "transform": { - "scaleSqrtDet": 1.00019, - "rotationDeg": 0.001, - "translateX": -0.214, - "translateY": -0.564, - "matrix": [ - [ - 1.000019, - -2.9e-05, - -0.21414 - ], - [ - 2.2e-05, - 1.000368, - -0.563871 - ] - ], - "direction": "ref(template) 좌표 -> edited(resized, input) 좌표. 정렬된 출력은 이 행렬의 역변환(WARP_INVERSE_MAP)으로 만든다." - }, - "eccIncludeMaskPixels": 772884, - "chamferInMask": { - "median": 0.0, - "p90": 1.0, - "nSamples": 117889 - }, - "faceOvalBandChamfer": { - "median": 0.0, - "p90": 1.0, - "nSamples": 5975 - }, - "inkRatioByBox": { - "eyeA": { - "box": [ - 396, - 410, - 536, - 489 - ], - "refInkRatio": 0.1821, - "editedInkRatio": 0.0078 - }, - "eyeB": { - "box": [ - 607, - 408, - 748, - 488 - ], - "refInkRatio": 0.1543, - "editedInkRatio": 0.0032 - }, - "browA": { - "box": [ - 354, - 351, - 553, - 438 - ], - "refInkRatio": 0.4338, - "editedInkRatio": 0.3066 - }, - "browB": { - "box": [ - 590, - 350, - 787, - 437 - ], - "refInkRatio": 0.2923, - "editedInkRatio": 0.1743 - }, - "mouth": { - "box": [ - 472, - 623, - 667, - 740 - ], - "refInkRatio": 0.064, - "editedInkRatio": 0.0 - } - }, - "eccExcludeBoxes": { - "eyeA": [ - 396, - 410, - 536, - 489 - ], - "eyeB": [ - 607, - 408, - 748, - 488 - ], - "browA": [ - 354, - 351, - 553, - 438 - ], - "browB": [ - 590, - 350, - 787, - 437 - ], - "mouth": [ - 472, - 623, - 667, - 740 - ] - } -} \ No newline at end of file diff --git a/docs/avatar-art/p7-linocut/base/edits/sheet.jpg b/docs/avatar-art/p7-linocut/base/edits/sheet.jpg deleted file mode 100644 index d3b7cd2..0000000 Binary files a/docs/avatar-art/p7-linocut/base/edits/sheet.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/base/normalize.json b/docs/avatar-art/p7-linocut/base/normalize.json deleted file mode 100644 index 660f604..0000000 --- a/docs/avatar-art/p7-linocut/base/normalize.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "canvas": { - "w": 1005, - "h": 1566 - }, - "reference": { - "image": "docs\\avatar-art\\p1-linocut\\base\\base-front.png", - "eyesCenter": [ - 492.69, - 584.33 - ], - "chin": [ - 500.61, - 919.55 - ], - "eyeDistance": 215.427, - "eyeChinDistance": 335.308 - }, - "input": { - "front": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\source-front.png", - "faceless": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\aligned-faceless-1.png", - "body": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\base\\edits\\aligned-body-1.png", - "frontSize": [ - 1142, - 1377 - ], - "eyesCenter": [ - 571.8, - 453.19 - ], - "chin": [ - 569.6, - 816.89 - ], - "eyeDistance": 211.724, - "eyeChinDistance": 363.704 - }, - "transform": { - "sHead": 0.969707, - "sWidth": 0.880035, - "s": 0.969707, - "rotationDeg": 0.0, - "offsetXNatural": -61.793, - "offsetXClampRange": [ - -102.406, - 0.0 - ], - "offsetX": -61.793, - "offsetY": 144.868 - }, - "coveredRange": { - "x": [ - 0.0, - 1005 - ], - "y": [ - 144.9, - 1480.2 - ] - }, - "inputSha256": { - "front": "e92955a4a5bbb0dc7a0b133368344c5d4acf2cd424b62f675c9206054973e124", - "faceless": "213a6566071f8c7e80cf539625d23eb709ed1d5e88a9ef177f2e223ff1164b13", - "body": "cb2091bf50aaead7b1a9569c4922aa06cfab7c101a0a0a0701982c4433f36e36" - } -} \ No newline at end of file diff --git a/docs/avatar-art/p7-linocut/manifest.json b/docs/avatar-art/p7-linocut/manifest.json deleted file mode 100644 index 9ef8b7e..0000000 --- a/docs/avatar-art/p7-linocut/manifest.json +++ /dev/null @@ -1,1920 +0,0 @@ -{ - "landmarks": { - "coordSystem": "screen (image pixel: x=0 좌측, y=0 상단; left=작은 x, right=큰 x; 인물 해부학적 좌우 아님)", - "eyeLeft": { - "innerCorner": [ - 433.39, - 588.66 - ], - "outerCorner": [ - 345.62, - 578.8 - ], - "upperLidTop": [ - 381.33, - 566.28 - ], - "lowerLidBottom": [ - 387.83, - 592.8 - ], - "iris": { - "center": [ - 387.31, - 576.96 - ], - "radius": 19.17 - } - }, - "eyeRight": { - "innerCorner": [ - 550.71, - 589.88 - ], - "outerCorner": [ - 637.67, - 578.54 - ], - "upperLidTop": [ - 601.55, - 566.13 - ], - "lowerLidBottom": [ - 596.27, - 593.19 - ], - "iris": { - "center": [ - 595.07, - 576.86 - ], - "radius": 19.41 - } - }, - "eyebrowLeft": { - "inner": [ - 455.8, - 526.95 - ], - "peak": [ - 369.0, - 521.19 - ], - "outer": [ - 324.2, - 527.5 - ] - }, - "eyebrowRight": { - "inner": [ - 526.5, - 526.84 - ], - "peak": [ - 619.5, - 519.78 - ], - "outer": [ - 667.5, - 526.96 - ] - }, - "noseTip": [ - 493.52, - 712.47 - ], - "mouthCornerLeft": [ - 422.65, - 806.53 - ], - "mouthCornerRight": [ - 558.88, - 808.4 - ], - "upperLipTopCenter": [ - 493.82, - 774.04 - ], - "lowerLipBottomCenter": [ - 492.31, - 834.98 - ], - "chinTip": [ - 491.82, - 932.41 - ], - "faceWidthAtEyeLevelLeft": [ - 273.76, - 630.72 - ], - "faceWidthAtEyeLevelRight": [ - 704.05, - 629.99 - ] - }, - "landmarkDetector": "mediapipe FaceLandmarker (tasks) 1.0.1, model=face_landmarker(float16, v1)", - "browCenterline": { - "method": "Otsu(front lum in bbox) AND diff(F-front)>=25, morphological closing to merge hatching, single global degree-2 polyfit on column-weighted centroid with one-pass outlier removal", - "diffThresh": 25.0, - "bboxPad": { - "x": 25.0, - "yUp": 42.0, - "yDown": 14.0 - }, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "outlierStdMult": 2.0, - "edgeInsetFrac": 0.03, - "peakWindow": [ - 0.25, - 0.6 - ], - "peakFallbackFrac": 0.35, - "peakFlatHeightPx": 3.0, - "peakFailEdgeFrac": 0.2, - "oldPoints": { - "browLeft": { - "inner": [ - 449.0, - 543.74 - ], - "peak": [ - 352.93, - 508.38 - ], - "outer": [ - 304.12, - 533.83 - ] - }, - "browRight": { - "inner": [ - 534.98, - 542.72 - ], - "peak": [ - 628.43, - 506.85 - ], - "outer": [ - 676.51, - 532.35 - ] - } - }, - "newPoints": { - "browLeft": { - "name": "browLeft", - "bbox": [ - 279, - 466, - 474, - 558 - ], - "otsuThresh": 99.0, - "colRange": [ - 320, - 460 - ], - "width": 140.0, - "nColumns": 129, - "nOutliersRemoved": 7, - "fitCoeffs": [ - 0.00157525, - -1.23283377, - 761.61565189 - ], - "inner": [ - 455.8, - 526.95 - ], - "peak": [ - 369.0, - 521.19 - ], - "outer": [ - 324.2, - 527.5 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.8992, - "checkA_pass": true, - "halfWidthInner": 5.39, - "halfWidthPeak": 7.98, - "halfWidthOuter": 3.424, - "bandX0": 279, - "bandY0": 466, - "colMin": 320, - "colMax": 460 - }, - "browRight": { - "name": "browRight", - "bbox": [ - 510, - 465, - 702, - 557 - ], - "otsuThresh": 108.0, - "colRange": [ - 522, - 672 - ], - "width": 150.0, - "nColumns": 147, - "nOutliersRemoved": 4, - "fitCoeffs": [ - 0.00160062, - -1.91032372, - 1088.9327895 - ], - "inner": [ - 526.5, - 526.84 - ], - "peak": [ - 619.5, - 519.78 - ], - "outer": [ - 667.5, - 526.96 - ], - "peakMethod": "fallback35", - "peakFracFromOuterEdge": 0.35, - "peakFail": false, - "checkA_fracColumnsFitWithinMask": 0.966, - "checkA_pass": true, - "halfWidthInner": 6.342, - "halfWidthPeak": 7.979, - "halfWidthOuter": 2.926, - "bandX0": 510, - "bandY0": 465, - "colMin": 522, - "colMax": 672 - } - }, - "halfWidthLeft": [ - 5.39, - 7.98, - 3.424 - ], - "halfWidthRight": [ - 6.342, - 7.979, - 2.926 - ], - "halfWidthNote": "원화 눈썹 잉크 띠 반두께[inner,peak,outer](px, 중심선 법선 방향). export_rig.py가 persona.json browHalfWidthFromArt!=false(기본)면 RigBrow.halfWidth로 내보낸다.", - "checkB_innerMinusPeakY": { - "browLeft": 5.76, - "browRight": 7.06 - }, - "checkD_symmetry": { - "innerYDiff": 0.11, - "peakYDiff": 1.41 - }, - "evidenceImage": "preview/v2/brow-centerline.jpg" - }, - "schemaVersion": "vignette.avatar.v3.layers.v1", - "persona": "P7", - "canvas": { - "w": 1005, - "h": 1566 - }, - "base": { - "front": "base/base-front.png", - "faceless": "base/base-faceless.png", - "facelessPadded": "base/base-faceless-padded.png", - "facelessSize": [ - 1005, - 1566 - ], - "padNote": "base-faceless.png 마지막 행을 0번 복제해 1005x1566(base-front.png 크기)으로 패딩한 것이 F다." - }, - "segmenter": { - "model": "selfie_multiclass_256x256.tflite (mediapipe ImageSegmenter, storage.googleapis.com)", - "categories": { - "0": "background", - "1": "hair", - "2": "bodySkin", - "3": "faceSkin", - "4": "clothes", - "5": "others" - }, - "categoryPixelCounts": { - "0": 659577, - "1": 157948, - "2": 76012, - "3": 183814, - "4": 494322, - "5": 2157 - }, - "hairEdgeRefine": { - "dilatePx": 6, - "lumThreshold": 110, - "addedPixels": 7398 - } - }, - "chinLine": { - "landmarkIndex": 152, - "xy": [ - 491.82, - 932.41 - ], - "marginPx": 8, - "cutY": 940.41 - }, - "faceOval": { - "landmarkLoop": [ - 10, - 338, - 297, - 332, - 284, - 251, - 389, - 356, - 454, - 323, - 361, - 288, - 397, - 365, - 379, - 378, - 400, - 377, - 152, - 148, - 176, - 149, - 150, - 136, - 172, - 58, - 132, - 93, - 234, - 127, - 162, - 21, - 54, - 103, - 67, - 109 - ], - "scale": 1.04, - "sourceImage": "base/base-front.png" - }, - "layers": [ - { - "id": "body", - "file": "layers/body.png", - "source": "base-faceless-masked+regenerated-fill", - "alphaBBox": [ - 0, - 725, - 1005, - 1566 - ], - "opaquePixels": 574074, - "greenResidue": 0, - "fillTargetPixels": 17280, - "fillSourceRaw": "raw/body.png (rejected 재생성이 아니라 수용된 body.png의 크로마키 결과; head/hairFront와 달리 body 재생성은 승인됨)" - }, - { - "id": "head", - "file": "layers/head.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 184, - 184, - 806, - 1043 - ], - "opaquePixels": 345799, - "greenResidue": 0 - }, - { - "id": "hairFront", - "file": "layers/hairFront.png", - "source": "base-faceless-masked", - "alphaBBox": [ - 258, - 413, - 719, - 802 - ], - "opaquePixels": 28325, - "greenResidue": 0 - } - ], - "rejectedRawEdits": { - "body": { - "file": "raw/body.png", - "status": "accepted-as-fill-source", - "reason": "정렬 (0,0), 형태 변형 없음 — head_mask 채움 전용 소스로 재사용" - } - }, - "composite": { - "meanAbsDiff": { - "full": 2.085797280095902, - "face": 0.3415553669119034, - "hairOutline": 7.633911850659033 - }, - "target": { - "full": 3.0, - "pass": true - }, - "backgroundNote": "카테고리0(배경)은 종이결 텍스처 노이즈(표준편차 ~29/채널)를 포함해 평탄한 크림(#EEE5D3)과 자체적으로 평균절대차 ~7 차이가 난다." - }, - "layersV2": { - "chinBandFix": { - "targetRegionPixels": 13086, - "sourceBandHeight": 150, - "rowJitterRange": [ - -2.0, - 2.0 - ], - "rngSeed": 20260930 - }, - "hairEdgeFix": { - "bandOuterPx": 40, - "bandInnerPx": 6, - "lumSmoothstep": [ - 205.0, - 150.0 - ], - "minFragmentAreaPx": 60, - "head": { - "bandOuterPixels": 72201, - "bandInnerPixels": 26037, - "componentsBefore": 80, - "removed": 77, - "removedPixels": 684 - }, - "hairFront": { - "bandOuterPixels": 6035, - "bandInnerPixels": 15130, - "componentsBefore": 129, - "removed": 125, - "removedPixels": 353 - } - }, - "hairPaperGapFix": { - "regionDilatePx": 6, - "paperColorDistThreshold": 40.0, - "paperLumThreshold": 195.0, - "paperSatThreshold": 40.0, - "opaqueAlphaThreshold": 0.95, - "regionPixels": 169288, - "head": { - "targetPixels": 1261, - "componentsBefore": 3, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - }, - "hairFront": { - "targetPixels": 11, - "componentsBefore": 4, - "removed": 0, - "removedPixels": 0, - "faceSkinAlphaDiff": 0.0 - } - }, - "jawInkFix": { - "mouthCenterY": 800.29, - "distThresholdPx": 14.0, - "mergePx": 3, - "aspectThreshold": 1.0, - "removedGroups": [], - "keptGroups": [ - { - "pixels": 42, - "bbox": [ - 299, - 801, - 304, - 814 - ], - "width": 5, - "height": 13, - "aspect": 0.385 - }, - { - "pixels": 34, - "bbox": [ - 662, - 801, - 680, - 836 - ], - "width": 18, - "height": 35, - "aspect": 0.514 - }, - { - "pixels": 2, - "bbox": [ - 310, - 825, - 312, - 828 - ], - "width": 2, - "height": 3, - "aspect": 0.667 - } - ], - "removedPixels": 0, - "strayFragmentCleanup": { - "componentsBefore": 4, - "removed": 0, - "removedPixels": 0 - } - }, - "metrics": { - "body": { - "haloPct": 0.502293077091068 - }, - "head": { - "haloPct": 0.8726695755652519, - "bgLeakAlphaMean": 4.8724556565942635e-05, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - }, - "hairFront": { - "haloPct": 0.0, - "bgLeakAlphaMean": 0.0, - "hairPaperOpaquePct": 0.0, - "disconnectedFragmentsUnder60px": 0 - } - }, - "layers": [ - { - "id": "body", - "bbox": [ - 0, - 725, - 1005, - 1566 - ], - "opaque": 558776, - "greenResidue": 1 - }, - { - "id": "head", - "bbox": [ - 174, - 151, - 822, - 1043 - ], - "opaque": 348927, - "greenResidue": 0 - }, - { - "id": "hairFront", - "bbox": [ - 220, - 413, - 755, - 836 - ], - "opaque": 33451, - "greenResidue": 0 - } - ], - "faceDetail": { - "browLandmarksSource": "manifest.landmarks.eyebrowLeft/Right", - "region": "face", - "faceInsetPx": 24.0, - "matchFacelessColor": true, - "matchFacelessColorDeltaRgb": { - "eyeLeft": [ - -16.69, - -16.87, - -17.95 - ], - "eyeRight": [ - -13.76, - -14.61, - -16.21 - ], - "browLeft": [ - 1.81, - 0.25, - -4.16 - ], - "browRight": [ - 0.58, - -0.04, - -3.37 - ], - "mouth": [ - -0.72, - -0.94, - -4.42 - ] - }, - "regions": { - "eyeLeft": [ - 315.6, - 511.2, - 458.4, - 662.8 - ], - "eyeRight": [ - 525.7, - 509.8, - 667.7, - 663.2 - ], - "mouth": [ - 377.6, - 737.5, - 603.9, - 917.4 - ], - "moles": [] - }, - "exclusionPixels": { - "eyeLeft": 11350, - "eyeRight": 10802, - "browLeft": 12463, - "browRight": 11270, - "mouth": 16631, - "union": 54127 - }, - "hairMaskPixels": 177726, - "bbox": [ - 276, - 423, - 701, - 936 - ], - "textureCloneStamp": { - "cloneTargetThreshold": 65.0, - "avoidThreshold": 110.0, - "lineOpeningIterations": 3, - "featherPx": 4.0, - "badTexturePixels": 217, - "preservedShadowPixels": 0, - "components": [ - { - "componentId": 1, - "pixels": 3, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 2, - "pixels": 2, - "offset": [ - 0, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 3, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 4, - "pixels": 1, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 5, - "pixels": 1, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 6, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 7, - "pixels": 1, - "offset": [ - -60, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 8, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 9, - "pixels": 4, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 10, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 11, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 12, - "pixels": 2, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 13, - "pixels": 26, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 14, - "pixels": 1, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 15, - "pixels": 1, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 16, - "pixels": 1, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 17, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 18, - "pixels": 1, - "offset": [ - -80, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 19, - "pixels": 1, - "offset": [ - -40, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 20, - "pixels": 4, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 21, - "pixels": 1, - "offset": [ - -100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 22, - "pixels": 1, - "offset": [ - -40, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 23, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 24, - "pixels": 1, - "offset": [ - -60, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 25, - "pixels": 1, - "offset": [ - -40, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 26, - "pixels": 2, - "offset": [ - 40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 27, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 28, - "pixels": 5, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 29, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 30, - "pixels": 1, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 31, - "pixels": 7, - "offset": [ - 100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 32, - "pixels": 1, - "offset": [ - -100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 33, - "pixels": 1, - "offset": [ - 100, - 0 - ], - "method": "clone-stamp" - }, - { - "componentId": 34, - "pixels": 1, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 35, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 36, - "pixels": 1, - "offset": [ - 0, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 37, - "pixels": 2, - "offset": [ - 0, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 38, - "pixels": 1, - "offset": [ - -40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 39, - "pixels": 15, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 40, - "pixels": 2, - "offset": [ - 0, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 41, - "pixels": 2, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 42, - "pixels": 1, - "offset": [ - 0, - 60 - ], - "method": "clone-stamp" - }, - { - "componentId": 43, - "pixels": 8, - "offset": [ - 60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 44, - "pixels": 7, - "offset": [ - -40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 45, - "pixels": 8, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 46, - "pixels": 4, - "offset": [ - 40, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 47, - "pixels": 1, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 48, - "pixels": 2, - "offset": [ - -40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 49, - "pixels": 10, - "offset": [ - -120, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 50, - "pixels": 1, - "offset": [ - -40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 51, - "pixels": 2, - "offset": [ - 120, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 52, - "pixels": 4, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 53, - "pixels": 2, - "offset": [ - 120, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 54, - "pixels": 2, - "offset": [ - 0, - -80 - ], - "method": "clone-stamp" - }, - { - "componentId": 55, - "pixels": 1, - "offset": [ - 0, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 56, - "pixels": 4, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 57, - "pixels": 3, - "offset": [ - -40, - 40 - ], - "method": "clone-stamp" - }, - { - "componentId": 58, - "pixels": 1, - "offset": [ - 0, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 59, - "pixels": 4, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 60, - "pixels": 1, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 61, - "pixels": 1, - "offset": [ - 120, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 62, - "pixels": 1, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 63, - "pixels": 3, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 64, - "pixels": 1, - "offset": [ - -120, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 65, - "pixels": 1, - "offset": [ - 0, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 66, - "pixels": 1, - "offset": [ - 0, - -180 - ], - "method": "clone-stamp" - }, - { - "componentId": 67, - "pixels": 1, - "offset": [ - 0, - 60 - ], - "method": "clone-stamp" - }, - { - "componentId": 68, - "pixels": 1, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 69, - "pixels": 6, - "offset": [ - 0, - -80 - ], - "method": "clone-stamp" - }, - { - "componentId": 70, - "pixels": 1, - "offset": [ - 0, - 60 - ], - "method": "clone-stamp" - }, - { - "componentId": 71, - "pixels": 3, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 72, - "pixels": 2, - "offset": [ - 0, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 73, - "pixels": 1, - "offset": [ - 0, - -80 - ], - "method": "clone-stamp" - }, - { - "componentId": 74, - "pixels": 9, - "offset": [ - 0, - -150 - ], - "method": "clone-stamp" - }, - { - "componentId": 75, - "pixels": 2, - "offset": [ - 0, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 76, - "pixels": 5, - "offset": [ - -60, - -100 - ], - "method": "clone-stamp" - }, - { - "componentId": 77, - "pixels": 1, - "offset": [ - -40, - -40 - ], - "method": "clone-stamp" - }, - { - "componentId": 78, - "pixels": 2, - "offset": [ - 0, - -120 - ], - "method": "clone-stamp" - }, - { - "componentId": 79, - "pixels": 2, - "offset": [ - 0, - -60 - ], - "method": "clone-stamp" - }, - { - "componentId": 80, - "pixels": 3, - "offset": [ - 120, - -60 - ], - "method": "clone-stamp" - } - ] - }, - "genericPatches": { - "forehead": [ - 530, - 419, - 578, - 467 - ], - "cheekLeft": [ - 338, - 640, - 386, - 688 - ], - "cheekRight": [ - 513, - 641, - 561, - 689 - ] - }, - "genericPatchBaseline": { - "highFreqEnergy": 9.080476197978074, - "lineEnergy": 4.482899101781223 - }, - "checkA_meanAbsDiff": 0.551403778535253, - "checkB_seamLumDiff": 4.8619307927367, - "checkC_holeDarkResiduePct": 0.33517397699597307, - "checkC_violationPixels": 139, - "checkD_texturePreservation": { - "eyeLeft": { - "insideEnergy": 11.728178068836447, - "baselineEnergy": 9.080476197978074, - "ratio": 1.2915818304163311, - "pass": true - }, - "eyeRight": { - "insideEnergy": 9.141842269631741, - "baselineEnergy": 9.080476197978074, - "ratio": 1.0067580235128342, - "pass": true - }, - "browLeft": { - "insideEnergy": 17.537053096939957, - "baselineEnergy": 9.080476197978074, - "ratio": 1.9312922268156913, - "pass": true - }, - "browRight": { - "insideEnergy": 13.608813222720961, - "baselineEnergy": 9.080476197978074, - "ratio": 1.498689377738934, - "pass": true - }, - "mouth": { - "insideEnergy": 6.004906204634735, - "baselineEnergy": 9.080476197978074, - "ratio": 0.6612986008345941, - "pass": false - } - }, - "checkD_allPass": false, - "checkE_ghostOutline": { - "eyeLeft": { - "bandLineEnergy": 10.536511919699906, - "baselineLineEnergy": 4.482899101781223, - "ratio": 2.350379002622066, - "pass": false - }, - "eyeRight": { - "bandLineEnergy": 8.918524608958212, - "baselineLineEnergy": 4.482899101781223, - "ratio": 1.9894546824430086, - "pass": false - }, - "browLeft": { - "bandLineEnergy": 14.994533403406946, - "baselineLineEnergy": 4.482899101781223, - "ratio": 3.3448295540377115, - "pass": false - }, - "browRight": { - "bandLineEnergy": 4.481198714945496, - "baselineLineEnergy": 4.482899101781223, - "ratio": 0.999620694823345, - "pass": true - }, - "mouth": { - "bandLineEnergy": 4.588176972223662, - "baselineLineEnergy": 4.482899101781223, - "ratio": 1.023484327452431, - "pass": true - } - }, - "checkE_allPass": false, - "checkDarkCircle": { - "eyeLeft": { - "compositeMean": 142.89866666666666, - "frontMean": 142.80361904761904, - "diff": 0.09504761904761949, - "pass": true - }, - "eyeRight": { - "compositeMean": 141.6638095238095, - "frontMean": 141.38838095238094, - "diff": 0.2754285714285629, - "pass": true - } - }, - "checkDarkCircle_allPass": true - }, - "paperGrain": { - "sourceImage": "base/base-front.png", - "sourceOrigin": [ - 10, - 10 - ], - "tileSize": 256, - "personMarginPx": 20, - "cleanFraction": 0.9999847412109375, - "contaminatedPixelsFilled": 1, - "chromaKeep": 0.3, - "targetLumRange": [ - 245.0, - 250.0 - ], - "lumShift": 23.471405029296875, - "finalMeanLum": 247.49236309765936, - "finalLumStd": 2.192897744732887, - "seamContinuity": { - "leftRightDiff": 0.15942372250082334, - "topBottomDiff": 0.01046539273550214 - } - } - }, - "lipTexture": { - "designVersion": "A5(아랫입술 그늘 띠를 lip-shadow 조각으로 분리)", - "landmarks": { - "mouthCornerLeft": [ - 422.65, - 806.53 - ], - "mouthCornerRight": [ - 558.88, - 808.4 - ], - "upperLipTop": [ - 493.82, - 774.04 - ], - "lowerLipBottom": [ - 492.31, - 834.98 - ], - "mouthCenter": [ - 493.23816396296024, - 800.2905101180077 - ] - }, - "capBBox": [ - 409.76, - 769.51, - 571.76, - 839.51 - ], - "otsuThreshLabA": 135.0, - "diffThresh": 20.0, - "dilatePx": 10.0, - "alphaRampPx": 4.0, - "splitOverlapPx": 2.0, - "shadowBandPx": 18.0, - "shadowTopOverlapPx": 6.0, - "shadowSideRampPx": 6.0, - "shadowTopRampPx": 4.0, - "checks": { - "lip-upper": { - "regionPixelCount": 5345, - "fullOpacityPixelCount": 4579, - "rampPixelCount": 766, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 36, - 33, - 170, - 50 - ] - }, - "lip-lower": { - "regionPixelCount": 5690, - "fullOpacityPixelCount": 4980, - "rampPixelCount": 710, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 38, - 71, - 164, - 49 - ] - }, - "lip-shadow": { - "regionPixelCount": 3024, - "fullOpacityPixelCount": 1512, - "rampPixelCount": 1512, - "preserveMaxDiffFullOpacity": 0, - "check1Preserve": true, - "spriteBBoxWorkingLocal": [ - 58, - 77, - 126, - 51 - ] - }, - "splitOverlapPixelCount": 327, - "splitOverlapBandOk": true, - "splitOverlapAlphaMaxDiff": 0, - "splitOverlapConsistentOk": true, - "splitCoverageMismatchPixelCount": 0, - "splitCheckOk": true, - "shadowLowerOverlapPixelCount": 2256, - "shadowLowerOverlapMaxDiff": 25.3765628604383, - "shadowLowerOverlapDiffGt1PixelCount": 106, - "shadowLowerOverlapOk": false - }, - "check3NeutralComposite": { - "fullOpacityMaxDiff": 0.0, - "rampMeanAbsDiff": 3.033, - "mouthBBoxMeanAbsDiff": 1.48 - }, - "pngFileSizes": { - "lip-upper": 23136, - "lip-lower": 21665, - "lip-shadow": 18046 - }, - "canvasOrigin": { - "upper": [ - 406, - 763 - ], - "lower": [ - 408, - 801 - ], - "shadow": [ - 428, - 807 - ] - }, - "evidenceImage": "preview/v2/lip-texture.jpg", - "check4WebpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jaw": { - "designVersion": "A5(결정문 §8.4 하관 띠 변형 — 턱 조각 원본)", - "noseTip": [ - 493.52, - 712.47 - ], - "chinTip": [ - 491.82, - 932.41 - ], - "yFormula": "noseTip.y - 50 - 10 .. chinTip.y + 40", - "noseToTopPx": 60.0, - "chinToBottomPx": 40.0, - "ovalOutsetPx": 18.0, - "faceOvalSource": "export_rig.detect_face_landmarks + compute_face_oval(base-front.png, 결정적)", - "bboxCanvas": [ - 251, - 652, - 729, - 973 - ], - "naiveXRangeForComparison": [ - 251, - 729 - ], - "checks": { - "head": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 308210 - }, - "detail": { - "pngRoundTripMaxDiff": 0, - "pngRoundTripOk": true, - "pngFileSize": 356378 - } - }, - "evidenceImage": "preview/v2/jaw-pieces.jpg", - "check1VsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.673, - "maxAbsDiffPremultipliedRgb": 34.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.819, - "maxAbsDiffPremultipliedRgb": 38.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "pipelineRun": { - "persona": "P7", - "timestamp": "2026-10-01T21:42:40", - "completed": true, - "stages": [ - { - "stage": "brow_sprite", - "ok": true, - "elapsedSec": 2.84 - }, - { - "stage": "paper_grain", - "ok": true, - "elapsedSec": 2.47 - }, - { - "stage": "lip_texture", - "ok": true, - "elapsedSec": 2.27 - }, - { - "stage": "jaw_pieces", - "ok": true, - "elapsedSec": 2.2 - }, - { - "stage": "export_rig", - "ok": true, - "elapsedSec": 17.28 - }, - { - "stage": "final_previews", - "ok": true, - "elapsedSec": 19.7 - } - ] - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 15948, - "lip-lower": 15146, - "lip-shadow": 13252 - }, - "totalBytes": 44346, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 24328, - "jaw-detail": 23986 - }, - "totalBytes": 48314, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.673, - "maxAbsDiffPremultipliedRgb": 34.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.819, - "maxAbsDiffPremultipliedRgb": 38.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "browSprite": { - "enabled": true, - "bandExtraHalfHeightPx": 8.0, - "bandEndExtendPx": 6.0, - "diffThresh": 25.0, - "closingKernel": [ - 7, - 11 - ], - "minComponentAreaPx": 30.0, - "hairSource": "layers/v2/hairFront.png alpha(게시되는 레이어 그 자체, 분할 카테고리 아님)", - "hairAlphaThresh": 128.0, - "hairDilateIterations": 1, - "rampPx": 1.5, - "inkLumMethod": "median(ink-classified pixels in band, region after hair removal)", - "minAlphaForUnmix": 0.05, - "hairMaskPixelCount": 37836, - "left": { - "bboxCanvas": [ - 324, - 509, - 137, - 29 - ], - "halfHeightPx": 15.98, - "otsuThresh": 102.0, - "lSkin": 171.333, - "lInk": 39.667, - "regionPixelCount": 1362, - "hairOverlapPixelCount": 402, - "neutralCompositeMeanAbsDiffAlphaRegion": 7.827, - "pngFileSizeBytes": 5345 - }, - "right": { - "bboxCanvas": [ - 521, - 509, - 150, - 28 - ], - "halfHeightPx": 15.979, - "otsuThresh": 109.0, - "lSkin": 179.0, - "lInk": 44.667, - "regionPixelCount": 2054, - "hairOverlapPixelCount": 67, - "neutralCompositeMeanAbsDiffAlphaRegion": 4.989, - "pngFileSizeBytes": 7329 - }, - "canvasOrigin": { - "left": [ - 324.0, - 509.0 - ], - "right": [ - 521.0, - 509.0 - ] - }, - "evidenceImage": "preview/v2/brow-sprite.jpg", - "checkWebpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "eyeLineScale": { - "enabled": true, - "method": "눈 폭 가운데 60% 열마다 upperLidTop.y-12 ~ 홍채중심y 사이 첫 연속 어두운(lum<80) 구간 길이의 중앙값", - "windowAbovePx": 12.0, - "darkLumThresh": 80.0, - "columnMiddleFrac": 0.6, - "p1BaselinePx": 2.0, - "lineScaleClampRange": [ - 1.0, - 2.5 - ], - "left": { - "measuredPx": 10.0, - "nColumns": 54, - "ratio": 5.0, - "lineScale": 2.5 - }, - "right": { - "measuredPx": 11.0, - "nColumns": 53, - "ratio": 5.5, - "lineScale": 2.5 - } - }, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 3510, - "brow-right": 4848 - }, - "totalBytes": 8358, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "iris": { - "method": "가로: 중심 y 부근 상하 각 4행에서 중심 x를 포함하는 연속 어두운(lum<100) 구간의 중앙값으로 반지름·중심x. 세로: 중심 x 부근 좌우 각 4열에서 중심 y를 포함하는 연속 어두운 구간의 아래쪽 끝 중앙값에서 가로 반지름을 빼 중심y(세로축은 윗눈꺼풀 잉크와 홍채가 같은 톤으로 이어져 위쪽 경계를 가를 수 없다).", - "darkLumThresh": 100.0, - "scanBandPx": 4, - "left": { - "measuredCenter": [ - 387.5, - 574.5 - ], - "measuredRadius": 16.5, - "rigCenter": [ - 387.31, - 576.96 - ], - "rigRadius": 19.17, - "nRowsHorizontal": 9, - "nColsVertical": 9 - }, - "right": { - "measuredCenter": [ - 593.5, - 572.5 - ], - "measuredRadius": 16.5, - "rigCenter": [ - 595.07, - 576.86 - ], - "rigRadius": 19.41, - "nRowsHorizontal": 9, - "nColsVertical": 9 - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p7-linocut/persona.json b/docs/avatar-art/p7-linocut/persona.json deleted file mode 100644 index db2974c..0000000 --- a/docs/avatar-art/p7-linocut/persona.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "code": "P7", - "publicSlug": "p7", - "rigFileName": "p7Rig.ts", - "rigExportName": "P7_LINOCUT_RIG", - "styleFrame": "../art-direction-v3/p1/r2-b-linocut.png", - "paletteSamples": { - "ink": { - "box": [347, 244, 507, 404], - "lumThreshold": 55 - }, - "motifPetalBoxes": [ - { "label": "sun", "kind": "ochre", "box": [1230, 10, 1536, 210] }, - { "label": "tulip", "kind": "ochre", "box": [1030, 280, 1170, 560] } - ], - "motifLeafBoxes": [ - { "label": "cloudLeft", "kind": "blue", "box": [20, 20, 380, 190] }, - { "label": "raincloud", "kind": "blue", "box": [520, 10, 930, 230] }, - { "label": "wiltedFlowerLeaf", "kind": "blue", "box": [520, 280, 650, 580] }, - { "label": "closedBudLeaf", "kind": "blue", "box": [10, 290, 110, 570] } - ] - }, - "faceDetail": { - "moles": [], - "region": "face", - "faceInsetPx": 24, - "matchFacelessColor": true - }, - "pivots": { - "neck": [492, 1002], - "body": [502, 1566], - "face": [489, 660] - }, - "crops": { - "bustYOffset": 40 - } -} diff --git a/docs/avatar-art/p7-linocut/preview/qa-brow/face-zoom.jpg b/docs/avatar-art/p7-linocut/preview/qa-brow/face-zoom.jpg deleted file mode 100644 index a884096..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa-brow/face-zoom.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa-brow/neutral-vs-art.jpg b/docs/avatar-art/p7-linocut/preview/qa-brow/neutral-vs-art.jpg deleted file mode 100644 index 69a7397..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa-brow/neutral-vs-art.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa-brow/sheet-expr-bust.jpg b/docs/avatar-art/p7-linocut/preview/qa-brow/sheet-expr-bust.jpg deleted file mode 100644 index b60a098..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa-brow/sheet-expr-bust.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa-brow/sheet-expr-face.jpg b/docs/avatar-art/p7-linocut/preview/qa-brow/sheet-expr-face.jpg deleted file mode 100644 index 0f95101..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa-brow/sheet-expr-face.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa-brow/sheet-viseme.jpg b/docs/avatar-art/p7-linocut/preview/qa-brow/sheet-viseme.jpg deleted file mode 100644 index 681ba02..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa-brow/sheet-viseme.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa-brow/viseme-timeline.json b/docs/avatar-art/p7-linocut/preview/qa-brow/viseme-timeline.json deleted file mode 100644 index b962dc1..0000000 --- a/docs/avatar-art/p7-linocut/preview/qa-brow/viseme-timeline.json +++ /dev/null @@ -1,287 +0,0 @@ -[ - { - "t": 0, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00000.png" - }, - { - "t": 50, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00050.png" - }, - { - "t": 100, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00100.png" - }, - { - "t": 150, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00150.png" - }, - { - "t": 200, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00200.png" - }, - { - "t": 250, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00250.png" - }, - { - "t": 300, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00300.png" - }, - { - "t": 350, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00350.png" - }, - { - "t": 400, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00400.png" - }, - { - "t": 450, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00450.png" - }, - { - "t": 500, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00500.png" - }, - { - "t": 550, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00550.png" - }, - { - "t": 600, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00600.png" - }, - { - "t": 650, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00650.png" - }, - { - "t": 700, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00700.png" - }, - { - "t": 750, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00750.png" - }, - { - "t": 800, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00800.png" - }, - { - "t": 850, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00850.png" - }, - { - "t": 900, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00900.png" - }, - { - "t": 950, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t00950.png" - }, - { - "t": 1000, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01000.png" - }, - { - "t": 1050, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01050.png" - }, - { - "t": 1100, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01100.png" - }, - { - "t": 1150, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01150.png" - }, - { - "t": 1200, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01200.png" - }, - { - "t": 1250, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01250.png" - }, - { - "t": 1300, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01300.png" - }, - { - "t": 1350, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01350.png" - }, - { - "t": 1400, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01400.png" - }, - { - "t": 1450, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01450.png" - }, - { - "t": 1500, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01500.png" - }, - { - "t": 1550, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01550.png" - }, - { - "t": 1600, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01600.png" - }, - { - "t": 1650, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01650.png" - }, - { - "t": 1700, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01700.png" - }, - { - "t": 1750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01750.png" - }, - { - "t": 1800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01800.png" - }, - { - "t": 1850, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01850.png" - }, - { - "t": 1900, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01900.png" - }, - { - "t": 1950, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t01950.png" - }, - { - "t": 2000, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02000.png" - }, - { - "t": 2050, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02050.png" - }, - { - "t": 2100, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02100.png" - }, - { - "t": 2150, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02150.png" - }, - { - "t": 2200, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02200.png" - }, - { - "t": 2250, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02250.png" - }, - { - "t": 2300, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02300.png" - }, - { - "t": 2350, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02350.png" - }, - { - "t": 2400, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02400.png" - }, - { - "t": 2450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02450.png" - }, - { - "t": 2500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02500.png" - }, - { - "t": 2550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02550.png" - }, - { - "t": 2600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02600.png" - }, - { - "t": 2650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02650.png" - }, - { - "t": 2700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02700.png" - }, - { - "t": 2750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02750.png" - }, - { - "t": 2800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa-brow\\frames\\viseme-scan-t02800.png" - } -] \ No newline at end of file diff --git a/docs/avatar-art/p7-linocut/preview/qa/face-zoom.jpg b/docs/avatar-art/p7-linocut/preview/qa/face-zoom.jpg deleted file mode 100644 index 05cf174..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa/face-zoom.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa/neutral-vs-art.jpg b/docs/avatar-art/p7-linocut/preview/qa/neutral-vs-art.jpg deleted file mode 100644 index 3420819..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa/neutral-vs-art.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa/sheet-expr-bust.jpg b/docs/avatar-art/p7-linocut/preview/qa/sheet-expr-bust.jpg deleted file mode 100644 index 953771f..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa/sheet-expr-bust.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa/sheet-expr-face.jpg b/docs/avatar-art/p7-linocut/preview/qa/sheet-expr-face.jpg deleted file mode 100644 index 9fd0afc..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa/sheet-expr-face.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa/sheet-viseme.jpg b/docs/avatar-art/p7-linocut/preview/qa/sheet-viseme.jpg deleted file mode 100644 index 50be32b..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/qa/sheet-viseme.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/qa/viseme-timeline.json b/docs/avatar-art/p7-linocut/preview/qa/viseme-timeline.json deleted file mode 100644 index 27a9d23..0000000 --- a/docs/avatar-art/p7-linocut/preview/qa/viseme-timeline.json +++ /dev/null @@ -1,287 +0,0 @@ -[ - { - "t": 0, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00000.png" - }, - { - "t": 50, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00050.png" - }, - { - "t": 100, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00100.png" - }, - { - "t": 150, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00150.png" - }, - { - "t": 200, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00200.png" - }, - { - "t": 250, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00250.png" - }, - { - "t": 300, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00300.png" - }, - { - "t": 350, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00350.png" - }, - { - "t": 400, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00400.png" - }, - { - "t": 450, - "viseme": "A", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00450.png" - }, - { - "t": 500, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00500.png" - }, - { - "t": 550, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00550.png" - }, - { - "t": 600, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00600.png" - }, - { - "t": 650, - "viseme": "EO", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00650.png" - }, - { - "t": 700, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00700.png" - }, - { - "t": 750, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00750.png" - }, - { - "t": 800, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00800.png" - }, - { - "t": 850, - "viseme": "O", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00850.png" - }, - { - "t": 900, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00900.png" - }, - { - "t": 950, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t00950.png" - }, - { - "t": 1000, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01000.png" - }, - { - "t": 1050, - "viseme": "U", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01050.png" - }, - { - "t": 1100, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01100.png" - }, - { - "t": 1150, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01150.png" - }, - { - "t": 1200, - "viseme": "EU", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01200.png" - }, - { - "t": 1250, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01250.png" - }, - { - "t": 1300, - "viseme": "M", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01300.png" - }, - { - "t": 1350, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01350.png" - }, - { - "t": 1400, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01400.png" - }, - { - "t": 1450, - "viseme": "I", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01450.png" - }, - { - "t": 1500, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01500.png" - }, - { - "t": 1550, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01550.png" - }, - { - "t": 1600, - "viseme": "E", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01600.png" - }, - { - "t": 1650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01650.png" - }, - { - "t": 1700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01700.png" - }, - { - "t": 1750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01750.png" - }, - { - "t": 1800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01800.png" - }, - { - "t": 1850, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01850.png" - }, - { - "t": 1900, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01900.png" - }, - { - "t": 1950, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t01950.png" - }, - { - "t": 2000, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02000.png" - }, - { - "t": 2050, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02050.png" - }, - { - "t": 2100, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02100.png" - }, - { - "t": 2150, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02150.png" - }, - { - "t": 2200, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02200.png" - }, - { - "t": 2250, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02250.png" - }, - { - "t": 2300, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02300.png" - }, - { - "t": 2350, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02350.png" - }, - { - "t": 2400, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02400.png" - }, - { - "t": 2450, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02450.png" - }, - { - "t": 2500, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02500.png" - }, - { - "t": 2550, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02550.png" - }, - { - "t": 2600, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02600.png" - }, - { - "t": 2650, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02650.png" - }, - { - "t": 2700, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02700.png" - }, - { - "t": 2750, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02750.png" - }, - { - "t": 2800, - "viseme": "X", - "shotPath": "D:\\workspace\\vignette\\docs\\avatar-art\\p7-linocut\\preview\\qa\\frames\\viseme-scan-t02800.png" - } -] \ No newline at end of file diff --git a/docs/avatar-art/p7-linocut/preview/v2/brow-centerline.jpg b/docs/avatar-art/p7-linocut/preview/v2/brow-centerline.jpg deleted file mode 100644 index 7ab17b2..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/v2/brow-centerline.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/v2/brow-sprite.jpg b/docs/avatar-art/p7-linocut/preview/v2/brow-sprite.jpg deleted file mode 100644 index 05ea9cb..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/v2/brow-sprite.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/v2/export-rig-report.json b/docs/avatar-art/p7-linocut/preview/v2/export-rig-report.json deleted file mode 100644 index 69698b4..0000000 --- a/docs/avatar-art/p7-linocut/preview/v2/export-rig-report.json +++ /dev/null @@ -1,676 +0,0 @@ -{ - "faceOvalBBox": [ - 260.4, - 415.8, - 716.2, - 942.6 - ], - "cropsFace": [ - 193.3, - 384.2, - 590.0, - 590.0 - ], - "mouthCenter": [ - 493.2, - 800.3 - ], - "palette": { - "ink": "#202122", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#BA9069", - "lipLower": "#C4936D", - "mouthLine": "#322F2B", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "paletteSamples": { - "eyeLeft": { - "sclera": "#D5AB81", - "iris": "#31302F", - "irisRing": "#D5AA81", - "n": { - "sclera": 1104, - "iris": 167, - "irisRing": 340 - } - }, - "eyeRight": { - "sclera": "#D5AA7F", - "iris": "#393632", - "irisRing": "#D0A67E", - "n": { - "sclera": 1130, - "iris": 171, - "irisRing": 348 - } - }, - "scleraIrisIrisRingFixedOverride": { - "reason": "persona.json paletteSamples.eyeOverride(고정 설계값)", - "measured": { - "sclera": "#D5AA80", - "iris": "#353330", - "irisRing": "#D3A880" - }, - "fixed": { - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F" - } - }, - "ink": { - "box": [ - 347, - 244, - 507, - 404 - ], - "lumThreshold": 55.0, - "n": 20938, - "hex": "#202122" - }, - "lipUpper": { - "box": [ - 417, - 771, - 563, - 798 - ], - "hex": "#BA9069" - }, - "lipLower": { - "box": [ - 417, - 802, - 563, - 837 - ], - "hex": "#C4936D" - }, - "mouthLine": { - "box": [ - 437, - 798, - 543, - 803 - ], - "hex": "#322F2B" - }, - "motifPetal": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "sun": { - "box": [ - 1230, - 10, - 1536, - 210 - ], - "n": 20570, - "hex": "#D3A661" - }, - "tulip": { - "box": [ - 1030, - 280, - 1170, - 560 - ], - "n": 4314, - "hex": "#CCA063" - } - }, - "hex": "#D0A362" - }, - "motifLeaf": { - "sourceImage": "docs\\avatar-art\\art-direction-v3\\p1\\r2-b-linocut.png", - "samples": { - "cloudLeft": { - "box": [ - 20, - 20, - 380, - 190 - ], - "n": 21312, - "hex": "#636D76" - }, - "raincloud": { - "box": [ - 520, - 10, - 930, - 230 - ], - "n": 27607, - "hex": "#586B76" - }, - "wiltedFlowerLeaf": { - "box": [ - 520, - 280, - 650, - 580 - ], - "n": 5818, - "hex": "#495961" - }, - "closedBudLeaf": { - "box": [ - 10, - 290, - 110, - 570 - ], - "n": 6589, - "hex": "#495961" - } - }, - "hex": "#53626C" - } - }, - "publish": { - "fileSizes": { - "body": 143004, - "head": 92712, - "hair-front": 25592, - "face-detail": 41908, - "paper-grain": 980 - }, - "encodeMeta": { - "quality": 80, - "scale": 1.0, - "totalBytes": 304196 - }, - "totalBytes": 304196 - }, - "grain": { - "href": "/avatar/v3/p7/paper-grain.webp", - "size": 256 - }, - "lipTexturePublish": { - "quality": "lossless", - "fileSizes": { - "lip-upper": 15948, - "lip-lower": 15146, - "lip-shadow": 13252 - }, - "totalBytes": 44346, - "budgetBytes": 81920, - "webpVsPngMeanAbsDiff": { - "upper": 0.0, - "lower": 0.0, - "shadow": 0.0 - } - }, - "jawPublish": { - "quality": 80, - "scale": 1.0, - "fileSizes": { - "jaw-head": 24328, - "jaw-detail": 23986 - }, - "totalBytes": 48314, - "budgetBytes": 204800, - "vsPublishedLayerAbsDiff": { - "head": { - "comparedAgainstPublishedLayer": "head.webp", - "meanAbsDiffPremultipliedRgb": 2.673, - "maxAbsDiffPremultipliedRgb": 34.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": false - }, - "detail": { - "comparedAgainstPublishedLayer": "face-detail.webp", - "meanAbsDiffPremultipliedRgb": 1.819, - "maxAbsDiffPremultipliedRgb": 38.0, - "meanAbsDiffAlpha": 0.0, - "maxAbsDiffAlpha": 0.0, - "meetsTarget": true - } - } - }, - "motifPublish": null, - "browSpritePublish": { - "quality": "lossless", - "fileSizes": { - "brow-left": 3510, - "brow-right": 4848 - }, - "totalBytes": 8358, - "budgetBytes": 61440, - "webpVsPngMeanAbsDiff": { - "left": 0.0, - "right": 0.0 - } - }, - "rig": { - "schemaVersion": "vignette.avatar.v3.rig.v1", - "persona": "P7", - "canvas": { - "w": 1005, - "h": 1566 - }, - "layers": { - "body": { - "href": "/avatar/v3/p7/body.webp", - "x": 0, - "y": 725, - "w": 1005, - "h": 841 - }, - "head": { - "href": "/avatar/v3/p7/head.webp", - "x": 174, - "y": 151, - "w": 648, - "h": 892 - }, - "hairFront": { - "href": "/avatar/v3/p7/hair-front.webp", - "x": 220, - "y": 413, - "w": 535, - "h": 423 - }, - "faceDetail": { - "href": "/avatar/v3/p7/face-detail.webp", - "x": 276, - "y": 423, - "w": 425, - "h": 513 - } - }, - "grain": { - "href": "/avatar/v3/p7/paper-grain.webp", - "size": 256 - }, - "lipTexture": { - "upper": { - "href": "/avatar/v3/p7/lip-upper.webp", - "x": 406, - "y": 763, - "w": 170, - "h": 50 - }, - "lower": { - "href": "/avatar/v3/p7/lip-lower.webp", - "x": 408, - "y": 801, - "w": 164, - "h": 49 - }, - "shadow": { - "href": "/avatar/v3/p7/lip-shadow.webp", - "x": 428, - "y": 807, - "w": 126, - "h": 51 - } - }, - "jaw": { - "head": { - "href": "/avatar/v3/p7/jaw-head.webp", - "x": 251, - "y": 652, - "w": 478, - "h": 321 - }, - "detail": { - "href": "/avatar/v3/p7/jaw-detail.webp", - "x": 251, - "y": 652, - "w": 478, - "h": 321 - } - }, - "motif": null, - "pivots": { - "neck": [ - 492, - 1002 - ], - "body": [ - 502, - 1566 - ], - "face": [ - 489, - 660 - ] - }, - "crops": { - "portrait": [ - 0, - 0, - 1005, - 1566 - ], - "bust": [ - 0.0, - 40.0, - 1005.0, - 1005.0 - ], - "face": [ - 193.25256299848155, - 384.20437934582236, - 590.0381387924195, - 590.0381387924195 - ] - }, - "faceOval": [ - [ - 490.5, - 415.8 - ], - [ - 555.3, - 417.3 - ], - [ - 609.5, - 425.4 - ], - [ - 658.6, - 442.5 - ], - [ - 688.8, - 469.0 - ], - [ - 706.0, - 502.9 - ], - [ - 714.3, - 539.4 - ], - [ - 716.2, - 584.5 - ], - [ - 712.6, - 628.1 - ], - [ - 706.8, - 673.7 - ], - [ - 696.8, - 722.2 - ], - [ - 681.3, - 774.6 - ], - [ - 662.0, - 818.1 - ], - [ - 641.5, - 850.8 - ], - [ - 615.8, - 880.4 - ], - [ - 592.8, - 902.0 - ], - [ - 566.9, - 921.9 - ], - [ - 534.8, - 937.4 - ], - [ - 491.9, - 942.6 - ], - [ - 449.0, - 938.5 - ], - [ - 416.4, - 923.9 - ], - [ - 389.5, - 904.8 - ], - [ - 365.4, - 883.9 - ], - [ - 338.9, - 854.2 - ], - [ - 317.7, - 821.0 - ], - [ - 298.0, - 777.0 - ], - [ - 282.2, - 723.6 - ], - [ - 271.5, - 674.4 - ], - [ - 265.1, - 628.9 - ], - [ - 260.4, - 585.3 - ], - [ - 262.1, - 540.3 - ], - [ - 270.9, - 503.5 - ], - [ - 289.1, - 469.4 - ], - [ - 320.3, - 442.7 - ], - [ - 369.9, - 425.3 - ], - [ - 425.0, - 417.4 - ] - ], - "landmarks": { - "eyeLeft": { - "inner": [ - 433.4, - 588.7 - ], - "outer": [ - 345.6, - 578.8 - ], - "upperLidTop": [ - 381.3, - 566.3 - ], - "lowerLidBottom": [ - 387.8, - 592.8 - ], - "iris": { - "center": [ - 387.5, - 574.5 - ], - "radius": 16.5 - }, - "lineScale": 2.5 - }, - "eyeRight": { - "inner": [ - 550.7, - 589.9 - ], - "outer": [ - 637.7, - 578.5 - ], - "upperLidTop": [ - 601.5, - 566.1 - ], - "lowerLidBottom": [ - 596.3, - 593.2 - ], - "iris": { - "center": [ - 593.5, - 572.5 - ], - "radius": 16.5 - }, - "lineScale": 2.5 - }, - "browLeft": { - "inner": [ - 455.8, - 527.0 - ], - "peak": [ - 369.0, - 521.2 - ], - "outer": [ - 324.2, - 527.5 - ], - "halfWidth": [ - 5.39, - 7.98, - 3.42 - ], - "sprite": { - "href": "/avatar/v3/p7/brow-left.webp", - "x": 324.0, - "y": 509.0, - "w": 137, - "h": 29 - } - }, - "browRight": { - "inner": [ - 526.5, - 526.8 - ], - "peak": [ - 619.5, - 519.8 - ], - "outer": [ - 667.5, - 527.0 - ], - "halfWidth": [ - 6.34, - 7.98, - 2.93 - ], - "sprite": { - "href": "/avatar/v3/p7/brow-right.webp", - "x": 521.0, - "y": 509.0, - "w": 150, - "h": 28 - } - }, - "noseTip": [ - 493.5, - 712.5 - ], - "mouthCornerLeft": [ - 422.6, - 806.5 - ], - "mouthCornerRight": [ - 558.9, - 808.4 - ], - "upperLipTop": [ - 493.8, - 774.0 - ], - "lowerLipBottom": [ - 492.3, - 835.0 - ], - "mouthCenter": [ - 493.2, - 800.3 - ], - "chinTip": [ - 491.8, - 932.4 - ] - }, - "palette": { - "ink": "#202122", - "sclera": "#D8CEBD", - "iris": "#4F3B2C", - "irisRing": "#1E1F1F", - "lipUpper": "#BA9069", - "lipLower": "#C4936D", - "mouthLine": "#322F2B", - "mouthInner": "#3B201B", - "teeth": "#E9E0CF", - "blush": "#C0624A", - "tear": "#EEE5D3", - "pallor": "#9AA3A6", - "paper": "#EEE5D3", - "motifPetal": "#D0A362", - "motifLeaf": "#53626C" - }, - "backdrop": { - "cognitive": "#ECE3D1", - "positive": "#F1DEC2", - "negative": "#DCE0E2", - "defensive": "#E6DAD3", - "energy": "#E2E0D0" - } - } -} \ No newline at end of file diff --git a/docs/avatar-art/p7-linocut/preview/v2/ghost-check.jpg b/docs/avatar-art/p7-linocut/preview/v2/ghost-check.jpg deleted file mode 100644 index d72099f..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/v2/ghost-check.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/v2/holes-texture.jpg b/docs/avatar-art/p7-linocut/preview/v2/holes-texture.jpg deleted file mode 100644 index 47db1e9..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/v2/holes-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/v2/jaw-pieces.jpg b/docs/avatar-art/p7-linocut/preview/v2/jaw-pieces.jpg deleted file mode 100644 index 8ebd6b7..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/v2/jaw-pieces.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/preview/v2/lip-texture.jpg b/docs/avatar-art/p7-linocut/preview/v2/lip-texture.jpg deleted file mode 100644 index 2a3eb7e..0000000 Binary files a/docs/avatar-art/p7-linocut/preview/v2/lip-texture.jpg and /dev/null differ diff --git a/docs/avatar-art/p7-linocut/prompts/base-faceless.txt b/docs/avatar-art/p7-linocut/prompts/base-faceless.txt deleted file mode 100644 index 6f1dca4..0000000 --- a/docs/avatar-art/p7-linocut/prompts/base-faceless.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as faceless.png: - -Redraw exactly the same boy, same frontal pose, same framing and same linocut relief-print style as the reference, but leave the face blank: remove the eyes, eyebrows and mouth completely and fill those areas with plain skin in the same flat skin color, keeping the nose, the face outline, the ears, the hair, the neck, the white shirt collar and the charcoal sweatshirt exactly as they are. Do not move, resize or redraw anything else. Plain flat cream background, nothing else, no text. diff --git a/docs/avatar-art/p7-linocut/prompts/body.txt b/docs/avatar-art/p7-linocut/prompts/body.txt deleted file mode 100644 index b9f6117..0000000 --- a/docs/avatar-art/p7-linocut/prompts/body.txt +++ /dev/null @@ -1,3 +0,0 @@ -Call the built-in image_gen tool immediately, using the attached image as the reference to edit. Do not read any files, do not use any skill, do not ask questions. Invoke the image_gen tool once in high quality, keeping the output image the same size and aspect ratio as the reference image, using exactly this prompt, then save the resulting image into the current directory as body.png: - -Keep only the neck, the white shirt collar and the charcoal sweatshirt exactly as they are in the reference: same shapes, same position, same linocut relief-print style, same ink colors and texture. Remove the head, the face, the ears and all of the hair completely. Where the hair or the chin previously covered the neck or the clothes, continue them naturally in the same style so there are no holes. Place the result on a plain flat pure green background (#00ff00) with nothing else, no text. diff --git a/docs/avatar-art/p7-linocut/raw/body.png b/docs/avatar-art/p7-linocut/raw/body.png deleted file mode 100644 index 974b644..0000000 Binary files a/docs/avatar-art/p7-linocut/raw/body.png and /dev/null differ diff --git a/docs/decisions/avatar-expression-engine-v3.md b/docs/decisions/avatar-expression-engine-v3.md index 898b9bb..f05252b 100644 --- a/docs/decisions/avatar-expression-engine-v3.md +++ b/docs/decisions/avatar-expression-engine-v3.md @@ -461,67 +461,6 @@ type Performance = { - **로드 실패**: 레이어 하나라도 실패하면 `data-load-state="error"`와 콜백으로 알린다. 세션(2B-2)은 이때 기존 SVG 리그로 되돌아간다. 이 환경에서 SVG ``의 load·error 이벤트가 발동하지 않아(원인 미확인, 2단계-B-1b 관찰) 같은 href를 `HTMLImageElement`로 미리 불러 로드 상태를 집계한다. - 새 공연(`playPerformance`)은 이전 공연의 **미발동 cue를 모두 취소**한다. 이미 재생 중인 클립은 자연 종료한다. -### 8.4.1 캐스트 확장 (P2~P7, 2026-10-01 설계) - -- **원화 선택**: 소유자 지시("모든 사람 병렬로, 지금 아트웍 스타일을 따라서, 다양한 표정과")에 따라 오케스트레이터 추천안을 쓴다. 소유자가 바꾸면 해당 페르소나만 다시 돈다. - - | 페르소나 | 후보 파일 | - |---|---| - | P2 | front-b | - | P3 | front-v2b | - | P4 | front-v2b | - | P5 | front-v2a | - | P6 | front-v2b | - | P7 | front-v2b | - -- **편집본을 원본 구도에서 만든다**: 얼굴 없는 기본형·몸 원본은 정규화 전 원화를 참조로 codex 편집한다. 페르소나마다 2장씩 만든다. 프롬프트는 `pN-linocut/prompts/`에 있고, P1 문구에서 사람·소지품만 바꿨다. - - 패딩된 가장자리를 모델이 "고치는" 일을 막기 위해서다. - - 편집본은 `register_edit.py`(ECC 정렬)로 원화 좌표에 맞춘다. - - 머리카락·옷·얼굴 윤곽의 에지 챔퍼 거리로 수용한다. - - faceless: 중앙값 ≤1.0px, p90 ≤2.5px, 윤곽 띠 p90 ≤2.0px - - body: 중앙값 ≤1.5px, p90 ≤3.0px -- **정규화**: `normalize_base.py`가 원화·얼굴 없는 기본형·몸 원본을 한 변환으로 P1 캔버스 1005×1566에 옮긴다. - - 머리 배율은 P1과 같게 한다. 눈-턱·눈 사이 거리 비의 평균이다. - - 폭은 반드시 덮는다(배율은 s = max(s_head, 1005/원화 폭)). 두 눈 중점은 P1에 맞춘다. - - 위아래 빈 곳은 반사 패딩한다. - - 렌더러 기하 상수(px)가 P1 머리 크기 기준이라, 머리 크기를 맞추는 것이 조건이다. 측정한 s_head는 0.91~0.97이다. - - 아래쪽 판화 끝선이 캔버스 바닥보다 위에서 끊길 수 있다. 이는 portrait 크롭에서만 보이고, 세션의 bust·face 크롭에는 들어오지 않는다. -- **눈썹 굵기를 원화에서 잰다**: 리그 계약 `RigBrow.halfWidth?: [안쪽, 꼭짓점, 바깥]`을 추가했다. - - 렌더러 반두께가 P1 설계값 [4.5, 3.2, 1.0]으로 고정이면, 굵은 눈썹(남성·성인) 원화가 가는 벡터 눈썹으로 바뀌어 "원화를 따른다"는 원칙이 깨진다. - - brow_centerline이 잉크 띠 반두께를 재고 export_rig이 쓴다. - - P1은 `browHalfWidthFromArt: false`로 기존 리그를 유지한다(소유자 검수본). -- **세션 반영 단위**: 파이프라인이 만든 리그는 Lab(`import.meta.glob`)에서 바로 보인다. 세션 레지스트리(`linocutRigFor`)에는 오케스트레이터가 표정 시트를 판정한 페르소나만 넣는다. -- **표정 검수**: 페르소나마다 4가지를 본다. - - 16표정 bust·face 시트 - - 비짐 9종 - - neutral 렌더와 원화 대조 - - P1과 같은 판정 기준: 중립 렌더 = 원화, 얼굴이 원화 인물 그대로, 표정 구분 -- 모티프 스프라이트(시계·촛불·연·참새·고치/나비·달팽이)는 세션에 아직 배치하지 않아(§8.5) 이번 범위 밖이다. -- **제작 중 확정한 추가 규칙(2026-10-01 P7 시범 판정)** - - **얼굴 해칭은 원화 픽셀로 지킨다** - - codex 얼굴 지우기 편집은 눈·입뿐 아니라 얼굴 전체 해칭(다크서클·볼 빗금·팔자·수염)까지 지운다. - - persona.json `faceDetail.region: "face"`이면 faceDetail이 얼굴 윤곽 안쪽 전체(윤곽에서 `faceInsetPx` 24px 안쪽, 윤곽 잉크선은 빼서 턱선이 두 겹이 되지 않게 함)를 원화 픽셀로 덮는다. - - 눈·눈썹·입 제외 영역만 faceless로 채운다. - - `matchFacelessColor`는 그 채움을 주변 원화 피부색에 상수 평행이동으로 맞춘다(보간·블러 금지). - - 머리카락 알파 0 마스크는 원화·faceless 분할의 합집합이다. - - P1은 `features`(기존)를 유지한다. - - **눈썹은 원화 스프라이트** - - 벡터 눈썹(반투명 띠 + 털 획)은 P1의 가늘고 옅은 눈썹에 맞춘 것이라, 굵은 판화 눈썹이 "갈색 막대"가 됐다. - - `RigBrow.sprite` - - `brow_sprite.py`가 원화 눈썹 잉크를 뜬다. - - 알파는 잉크 농도이고, L_ink = 잉크 픽셀 휘도 중앙값, 경계 1.5px다. 색은 피부 오염을 역산한다. - - 실제로 위에 그려지는 hairFront 레이어 알파 아래 픽셀은 뺀다. 분할 category로 빼면 굵은 눈썹이 머리카락으로 오분류돼 82%가 사라졌다. - - 렌더러는 이를 세로 16띠로 나눠, 벡터 눈썹과 같은 계수의 inner·peak·outer 변위를 2차 보간으로 옮긴다. - - P7 중립 합성 평균절대차는 7.8/5.0(알파>0 영역)이다. - - **눈꺼풀 선 굵기·홍채는 원화에서 잰다** - - `RigEye.lineScale` - - 원화 윗눈꺼풀 잉크 두께 ÷ P1 2.0px이고, [1, 2.5]로 제한한다. - - 늘어나는 분량은 눈꺼풀 선 **바깥쪽**으로만 더한다. 안쪽으로 두꺼워지면 홍채가 가려져 눈이 졸려 보였다. - - 홍채 중심·반지름은 원화 실측을 쓴다(`irisFromArt`). MediaPipe 홍채는 P7에서 14~15% 컸다. - - P1은 세 필드 모두 false로 기존 리그를 유지한다. - - **정규화 위쪽 빈 곳은 종이색으로 채운다.** 반사 패딩은 머리카락이 원화 위 끝 가까이 오는 후보(P3·P5·P6·P7)에서 머리 위에 뒤집힌 머리카락을 만들었다. - - **QA 시트는 깜빡임을 피해 찍는다.** 3연사 중 눈 윤곽이 가장 큰 장을 고른다. - ### 8.5 세션 연결 (2단계-B-2, 2026-10-01 설계) - 연결 지점 지도(탐색 보고) diff --git a/docs/dev_dashboard.html b/docs/dev_dashboard.html index b965712..72518d8 100644 --- a/docs/dev_dashboard.html +++ b/docs/dev_dashboard.html @@ -1043,7 +1043,7 @@
GATE
한신대 공문/데이터 거버넌스 게이트 정리

SSO 클레임, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보 처리방침을 P1 진입 전 외부 의존성으로 명확히 둔다. 로컬 문서 골격은 준비됐지만 한신대/데이터 steward의 written evidence는 아직 필요하다.

산출물

docs/ops/hanshin-data-governance-gate.md, 공문 질의 항목, 동의 범위 체크리스트, SSO claim mapping 표

검증

문서 artifact 작성 완료; IRB 게이트가 아니라 데이터/SSO 게이트로 외부 증거 필요 상태 유지

JEV-001
Jev 감정 판단 실증과 기존 내담자 경로 반복 비교

수용: 신규 artifact 보존 갱신, API legacy baseline 1191 passed/1 skipped/0 failed, engine 82 passed, web typecheck/build/API types 통과, NAS API 배포·공개 JS/CORS preflight, 인증 브라우저 3왕복·6발화와 DB 9축 감정 영속화. 보류: 성능 우위·정확도·품질 승격과 사용자 체감 지연 개선.

산출물

runner/probe·배포 artifact는 Jev 결정문과 운영 증거에서 관리한다.

검증

새로고침 뒤 6발화 복원, 종료 review의 실제 6발화 요약·deep-loop 완료와 DB 종료 상태를 확인했다. USER-LATENCY는 text display·voice onset·완료 준비의 실제 경로 분리 측정과 안전 검사 유지 최적화가 남는다. 일시 502 관측 때문에 무중단을 주장하지 않는다.

JEV-002
Jev 내담자 평가·표현 v2와 속마음 공개

수용: P0 가드레일 수단어 오탐("고독"·"다른 방법은") 제거, P1 20문항 질문 세트·반응/기분 분리·생성 지시 v2(1차 반려: noul 응답 필드), P2 migration 24·같은 트랜잭션 저장·세 경로 노출·리뷰(1차 반려: 교수자 cohort 누락·readiness 미등록), P3a 회기·리뷰 속마음 UI(2차 반려: 모바일 스위치 깨짐·컨트롤 바 높이 회귀), P3b 관리자 v2 표시. 보류: 실제 생성 대사 관측, 한국어 판정 정확도·전문가 보정, 운영 배포.

산출물

v2 프로토콜, infra/db/init/24_client_inner_reaction.sql, scratch/jev/v2-appraisal-live.json, scratch/jev/inner-reaction-verify/

검증

HEAD bda7ebb9 대조로 full-sweep-session :644·:878·:974와 layout-visual-gate :970을 사전 실패로 분리했다. 보정 관찰: 공감 반영을 a_understood가 "비껴갔다"로 판정한 1건, a_sore_spot 고확률 선택 2/3건.

-
AVATAR-V3
가상 내담자 아바타 v3 — 표현 엔진·반응 모션·리노컷 리그

소유자 결정(2026-09-30): 렌더는 벡터 리그 고도화를 혼합으로 구체화(덩어리는 판화 질감 이미지 레이어, 표정 부위는 코드 벡터), 강도 비례 하이브리드, P1 서연 파일럿, 아트는 리노컷 판화 + 작은 모티프·배경 색 변화. 수용: 0단계 아트 디렉션(1차 브리프 미충족 → 2차 수용), 트레이싱·참조 편집 spike, 1단계 엔진 코어(채널 33·표정 28·반응 클립 26·지문 파서·보조 cue)와 /dev/avatar-lab, 2단계-A P1 리노컷 레이어(1차 반려 → 분할 마스크 재작업 수용, 정지 합성 평균 절대차 1.22). 2단계-B-1b 렌더러는 기능 계약·E2E 수용, 모티프 스프라이트(2단계-B-1c) 수용. 진행: 2단계-B-1d 표정 판독성 개정 — 3차 캡처 판정에서 입(단색 렌즈 → 원화 입술 결 래스터 + 벡터 모양), 중립 눈썹 사선(원인: 눈썹 랜드마크가 잉크 띠 중심선이 아닌 모서리), 이마 주름 평행선, bust 200px 약한 표정 구분 불가를 반려하고 결정문 §8.4를 개정해 자산·렌더러 워커 병렬 재작업. 소유자 관찰(비탄 눈썹이 너무 위 → 프리셋을 AU1+AU4로, 말하는 입이 인위적 → 립싱크 재설계)에 따라 발화층 §5.4(한글 자모 비짐 9종 + TTS 오디오 선분석 정렬, mouthRound 채널, 페르소나 조음 폭)를 구현·검증(check:avatar-lipsync, avatar-lab E2E 16 passed)했고, 소유자 관찰(입술만 움직여 기괴함)로 발화 동반층 §5.5(억양·강세에 맞춘 고개 표류·끄덕임·들숨·눈썹 박·시선 회피·깜빡임)와 하관 띠 변형(턱·코 들썩, 볼 불룩, §8.4)을 구현했다. 입은 원화 픽셀 입술 스프라이트(24띠 변형, 중립 = 원화)로 바꿨고, 턱 두 줄 결함(투명 음영 비침·정적 clip·head 원본·hairFront 자산 오분류 턱선)과 볼 가장자리 틈을 규명·수정해 2단계-B-1d를 수용했다(out11 직접 확인, avatar-lab E2E 19 passed, 발화 중 rAF p95 6.2ms·long task 0). 소유자가 Lab에서 확인(2026-10-01 "괜찮네")하고 작업 브랜치 YunChan/avatar-v3-linocut-p1-20261001를 forgejo에 푸시했다(master 미병합·미배포). 운영 배포는 세션 연결(2단계-B-2) 뒤에 함께 한다(소유자 결정). P2~P7 리노컷 캐스트 리그 수용(2026-10-01, 결정문 §8.4.1: 추천 원화로 병렬 제작 — codex 얼굴 없는 기본형·몸 편집 → ECC 정렬 → P1 머리 크기 정규화 → 공통 파이프라인. 얼굴 해칭은 원화 픽셀(faceDetail region face), 눈썹은 원화 스프라이트 16띠, 눈꺼풀 굵기·홍채는 원화 실측. P1 재실행 바이트 동일. 세션 레지스트리 P1~P7 전원 활성, 표정 E2E v3 기대로 갱신 — 아바타·세션 E2E 79 passed/1 skipped, build·typecheck·presets·lipsync·dead-code 통과), 2단계-B-2 세션 연결 수용(결정문 §8.5·§11: P1 세션·시작 전·썸네일 v3, 오디오 시계 립싱크, TTS 실패 시 텍스트 타이밍, VITE_AVATAR_V3 플래그. 세션 레이아웃·발화·표정 E2E 11 passed, avatar-lab 19 passed) · master 613bcb60·공개 웹(Pages dd96c2d5) 반영 완료(2026-10-01, 운영 묶음·P1 자산 200 확인). 남음: 운영 화면 소유자 시각 확인, 3단계 API avatar_cue·지문 어휘 가이드, 4단계 7명 외형 SSOT. 운영 모델은 괄호 지문을 거의 쓰지 않는다(6턴 0개).

산출물

아바타 v3 결정문, apps/web/src/components/avatar/engine/, docs/avatar-art/art-direction-v3/, docs/avatar-art/p1-linocut/

검증

npm run check:avatar-presets 통과(표정 378쌍 구분, 최소 L1 0.95), e2e/avatar-lab.spec.ts 6 passed(오케스트레이터 재실행), typecheck·build·check:dead-code 통과(워커 보고)

+
AVATAR-V3
가상 내담자 아바타 v3 — 표현 엔진·반응 모션·리노컷 리그

소유자 결정(2026-09-30): 렌더는 벡터 리그 고도화를 혼합으로 구체화(덩어리는 판화 질감 이미지 레이어, 표정 부위는 코드 벡터), 강도 비례 하이브리드, P1 서연 파일럿, 아트는 리노컷 판화 + 작은 모티프·배경 색 변화. 수용: 0단계 아트 디렉션(1차 브리프 미충족 → 2차 수용), 트레이싱·참조 편집 spike, 1단계 엔진 코어(채널 33·표정 28·반응 클립 26·지문 파서·보조 cue)와 /dev/avatar-lab, 2단계-A P1 리노컷 레이어(1차 반려 → 분할 마스크 재작업 수용, 정지 합성 평균 절대차 1.22). 2단계-B-1b 렌더러는 기능 계약·E2E 수용, 모티프 스프라이트(2단계-B-1c) 수용. 진행: 2단계-B-1d 표정 판독성 개정 — 3차 캡처 판정에서 입(단색 렌즈 → 원화 입술 결 래스터 + 벡터 모양), 중립 눈썹 사선(원인: 눈썹 랜드마크가 잉크 띠 중심선이 아닌 모서리), 이마 주름 평행선, bust 200px 약한 표정 구분 불가를 반려하고 결정문 §8.4를 개정해 자산·렌더러 워커 병렬 재작업. 소유자 관찰(비탄 눈썹이 너무 위 → 프리셋을 AU1+AU4로, 말하는 입이 인위적 → 립싱크 재설계)에 따라 발화층 §5.4(한글 자모 비짐 9종 + TTS 오디오 선분석 정렬, mouthRound 채널, 페르소나 조음 폭)를 구현·검증(check:avatar-lipsync, avatar-lab E2E 16 passed)했고, 소유자 관찰(입술만 움직여 기괴함)로 발화 동반층 §5.5(억양·강세에 맞춘 고개 표류·끄덕임·들숨·눈썹 박·시선 회피·깜빡임)와 하관 띠 변형(턱·코 들썩, 볼 불룩, §8.4)을 구현했다. 입은 원화 픽셀 입술 스프라이트(24띠 변형, 중립 = 원화)로 바꿨고, 턱 두 줄 결함(투명 음영 비침·정적 clip·head 원본·hairFront 자산 오분류 턱선)과 볼 가장자리 틈을 규명·수정해 2단계-B-1d를 수용했다(out11 직접 확인, avatar-lab E2E 19 passed, 발화 중 rAF p95 6.2ms·long task 0). 소유자가 Lab에서 확인(2026-10-01 "괜찮네")하고 작업 브랜치 YunChan/avatar-v3-linocut-p1-20261001를 forgejo에 푸시했다(master 미병합·미배포). 운영 배포는 세션 연결(2단계-B-2) 뒤에 함께 한다(소유자 결정). 진행: P2~P7 리노컷 캐스트(외형·상징 설계 docs/avatar-art/linocut-cast.md, 원화 후보 v2 생성·소유자 선택 대기, 자산 파이프라인 공통화 docs/avatar-art/linocut-pipeline/ — P1 재실행 바이트 동일), 2단계-B-2 세션 연결 수용(결정문 §8.5·§11: P1 세션·시작 전·썸네일 v3, 오디오 시계 립싱크, TTS 실패 시 텍스트 타이밍, VITE_AVATAR_V3 플래그. 세션 레이아웃·발화·표정 E2E 11 passed, avatar-lab 19 passed) · master 613bcb60·공개 웹(Pages dd96c2d5) 반영 완료(2026-10-01, 운영 묶음·P1 자산 200 확인). 남음: 운영 화면 소유자 시각 확인, 3단계 API avatar_cue·지문 어휘 가이드, 4단계 7명 외형 SSOT. 운영 모델은 괄호 지문을 거의 쓰지 않는다(6턴 0개).

산출물

아바타 v3 결정문, apps/web/src/components/avatar/engine/, docs/avatar-art/art-direction-v3/, docs/avatar-art/p1-linocut/

검증

npm run check:avatar-presets 통과(표정 378쌍 구분, 최소 L1 0.95), e2e/avatar-lab.spec.ts 6 passed(오케스트레이터 재실행), typecheck·build·check:dead-code 통과(워커 보고)

ADMIN-OPENROUTER-001
관리자 OpenRouter 복구의 웹 검증과 새 OAuth 재시도

수용: AmbiguousColumnError의 기존 refresh column 참조와 EXCLUDED.extra의 text 캐스트 제거(JSONB 비교)를 수정했고, 실제 PostgreSQL rollback·API 교체·보존 census를 확인했다. 웹 focused 선택 16개와 정적 검증, NAS web·Pages 배포, public entry/health 200과 관리자 UI의 ready·복구 화면 없음은 수용했다. 진행 중: 새 사용자 OAuth finish 성공 관측.

검증

API 1173 passed·1 skipped·0 failed, focused 16 passed, health ok·DB/engine true. API 교체 직후 census에서 133개 테이블 중 130개 동일이며 활동 메타데이터 3개는 변했다. OpenRouter UI 미연결과 Jev live_verified=false는 live 검증이 아니며, OAuth finish 성공은 아직 주장하지 않는다.

DONE
ADMIN-AFFECT-001 관리자 감정 관측 trace·그래프 운영 수용

수용: 완료 내담자 턴 trace의 공개 payload 비노출, API, 격리 PostgreSQL RLS·cascade, 성공·rollback 원자성, canonical artifact 기술 6/6, migration 23 운영 적용(RLS 2개 정책·grants·기존 행 0), API legacy baseline 1191 passed/1 skipped/0 failed와 gateway 82 passed, API 운영 health와 Pages 공개 index/AdminAffect JS/CSS·기존 사용자 bundle 200, 인증 CUA의 trace 1개·9축·5단계 분포와 새로고침 복원, NAS web build·컨테이너 교체 exit 0 및 API·engine·DB 컨테이너 유지.

산출물

Jev 결정문, 운영 영수증, /admin/emotions, /admin/affect/sessions

검증 경계

web generate/check API types, typecheck, build, design SSOT와 focused 16개를 통과했고 dark/mobile/추이/관계 캡처 및 실제 리뷰 평가도 수용했다. 실제 1턴 관계 표시는 데이터 부족이며 fixture의 5개 이상 관계 계산 검증과 구분한다. watchdog은 한 차례 반려 뒤 root marker 보완으로 고정 수용했다. 전체 layout-visual-gate는 15개 중 1 passed·1 failed·13 not run이므로 전체 GREEN을 주장하지 않는다.

B1-LAYOUT-720-001
720px prestart 시작 버튼 ancestor clipping

prestart 회기 시작의 clip(left=25, right=695)이 baseline d22cd988과 현재 작업트리에서 동일하다. 감정 관측 기능 원인으로 단정하지 않고 cosmetic backlog로 추적한다.

다음 행동

원인을 분리한 뒤 focused browser 재현·수정 검증을 수행한다.

검증 경계

현재 전체 layout-visual-gate는 15개 중 1 passed·1 failed·13 not run이다. 관리자 3개와 session-layout desktop/mobile 각 4개 통과가 전체 gate 통과를 대체하지 않는다.