aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/exporter.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/bluebey-studio/src/exporter.js')
-rw-r--r--public/bluebey-studio/src/exporter.js51
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);