diff options
Diffstat (limited to 'public/bluebey-studio/src/exporter.js')
| -rw-r--r-- | public/bluebey-studio/src/exporter.js | 51 |
1 files changed, 49 insertions, 2 deletions
diff --git a/public/bluebey-studio/src/exporter.js b/public/bluebey-studio/src/exporter.js index 147af1c..b34b6f6 100644 --- a/public/bluebey-studio/src/exporter.js +++ b/public/bluebey-studio/src/exporter.js @@ -1,7 +1,7 @@ import { traceAlphaContours, contoursToPathData } from './trace.js'; import { roughenContours } from './handDrawn.js'; import { captionToSvg } from './caption.js'; -import { EYE_LAYOUT, MOUTH_LAYOUT } from './faceArt.js'; +import { EYE_LAYOUT, MOUTH_LAYOUT, HAIR_WRAP_LAYOUT } from './faceArt.js'; /** * Export helpers. @@ -275,7 +275,54 @@ export function buildFaceMap(face, kind) { return canvas; } -/** One guide segment, kept out of `buildFaceMap` so the placements stay readable. */ +/** + * The 髪の下地 (a base to draw hair on). + * + * The hair plate is a cylinder unrolled: `x` is the angle round the head, with + * the face at the middle of the artwork window and the seam at the back at the + * two edges, and `y` is the height, top of the head first. The current hair is + * copied in as a faint reference, and guides mark the window, the front centre + * and the height the default まえがみ hairline reaches. + * + * @param {import('./face.js').Face} face + * @returns {HTMLCanvasElement|null} + */ +export function buildHairMap(face) { + const layout = face.hairLayout; + const plate = face.hairCanvas; + if (!layout || !plate) return null; + // The plate's UVs were rewritten to fill 0..1, so the whole canvas is the + // texture (unlike the eye/mouth maps, which keep a window inside a wider + // canvas). Every guide is therefore measured straight against the canvas. + const canvas = document.createElement('canvas'); + canvas.width = plate.width; + canvas.height = plate.height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(plate, 0, 0); + + // White paper under the copy, so a redraw starts from an opaque base. + ctx.globalCompositeOperation = 'destination-over'; + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.globalCompositeOperation = 'source-over'; + + ctx.strokeStyle = FACE_MAP_GUIDE; + ctx.fillStyle = FACE_MAP_GUIDE; + ctx.lineWidth = 3; + + // The front of the head sits at the middle of the artwork window, which is + // where `drawHeadCoverWrap` puts the face, so this vertical line is the + // nose-to-backbone plane. The canvas's own left/right edges are the seam at + // the back of the head. + const fx = (layout.offsetX + layout.window.width * 0.5) * (canvas.width / layout.width); + guideLine(ctx, fx, 0, fx, canvas.height); + // The default hairline (大きさ 1) runs across the whole wrap at this height. + guideLine(ctx, 0, HAIR_WRAP_LAYOUT.base * canvas.height, canvas.width, HAIR_WRAP_LAYOUT.base * canvas.height); + + return canvas; +} + +/** One guide segment, kept out of the map builders so the placements stay readable. */ function guideLine(ctx, x0, y0, x1, y1) { ctx.beginPath(); ctx.moveTo(x0, y0); |
