aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/faceArt.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/bluebey-studio/src/faceArt.js')
-rw-r--r--public/bluebey-studio/src/faceArt.js57
1 files changed, 46 insertions, 11 deletions
diff --git a/public/bluebey-studio/src/faceArt.js b/public/bluebey-studio/src/faceArt.js
index a14f53a..cf61c68 100644
--- a/public/bluebey-studio/src/faceArt.js
+++ b/public/bluebey-studio/src/faceArt.js
@@ -871,6 +871,39 @@ function drawFaceExtras(ctx, style, lineOnly) {
drawBeard(ctx, style.beard, lineOnly, style.beards);
}
+const BEARD_TINT_IDS = new WeakMap();
+const BEARD_TINT_CACHE = new Map();
+let beardTintSeq = 0;
+
+/**
+ * A copy of a beard drawing in the chosen colour.
+ *
+ * The supplied files are drawn in near-black, so repainting the ink with the
+ * beard colour (`source-in` keeps the anti-aliased alpha) is what lets the colour
+ * picker work on a drawing. Cached per (sprite, colour).
+ */
+function tintedSprite(image, color) {
+ let id = BEARD_TINT_IDS.get(image);
+ if (!id) {
+ beardTintSeq += 1;
+ id = beardTintSeq;
+ BEARD_TINT_IDS.set(image, id);
+ }
+ const key = `${id}|${color}`;
+ const cached = BEARD_TINT_CACHE.get(key);
+ if (cached) return cached;
+ const canvas = document.createElement('canvas');
+ canvas.width = image.width;
+ canvas.height = image.height;
+ const ctx = canvas.getContext('2d');
+ ctx.drawImage(image, 0, 0);
+ ctx.globalCompositeOperation = 'source-in';
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ BEARD_TINT_CACHE.set(key, canvas);
+ return canvas;
+}
+
/**
* ひげ: a mustache or beard under the nose. Each kind is a small shape built on
* the spot; `scotch` and `cat` are drawn with round-capped strokes, `kaiser` is a
@@ -884,38 +917,40 @@ function drawBeard(ctx, spec, lineOnly = false, textures = null) {
const cx = BEARD_LAYOUT.cx;
const cy = BEARD_LAYOUT.cy + clamp(spec.offsetY ?? 0, -600, 600);
const w = BEARD_LAYOUT.width * size;
+ const color = spec.color ?? '#3a2a4a';
// A supplied drawing for this kind (assets/beards/<shape>.png) replaces the
// built-in strokes. The drawings are trimmed to their ink, so `w` is the
- // beard's real width; it is centred on the nose and scaled by `size`.
+ // beard's real width; it is centred on the nose and scaled by `size`, and it is
+ // tinted with the beard colour (the files are drawn in near-black).
const entry = textures?.[shape];
if (entry?.image) {
- const { image, half } = entry;
+ const sprite = tintedSprite(entry.image, color);
// Slightly wider than the built-in base: a moustache drawn to the same width
// as a beard's stroke sat mostly behind the nose.
const drawW = w * 1.4;
- if (half) {
+ if (entry.half) {
// One side, mirrored to make the pair. The drawing is the model's own right
- // side, which sits in the artwork's left half; `spacing` opens the middle.
+ // side, which sits in the artwork's left half; `spacing` opens the middle
+ // (a negative value brings the two halves together).
const halfW = drawW / 2;
- const dh = (halfW * (image.height || 1)) / (image.width || 1);
- const gap = clamp(spec.spacing ?? 0, 0, 800);
+ const dh = (halfW * (sprite.height || 1)) / (sprite.width || 1);
+ const gap = clamp(spec.spacing ?? 0, -400, 800);
const y = cy - dh / 2;
- ctx.drawImage(image, cx - gap - halfW, y, halfW, dh);
+ ctx.drawImage(sprite, cx - gap - halfW, y, halfW, dh);
ctx.save();
ctx.translate(cx + gap, cy);
ctx.scale(-1, 1);
// Draw into [-halfW, 0] so that, mirrored, it lands in [cx+gap, cx+gap+halfW].
- ctx.drawImage(image, -halfW, -dh / 2, halfW, dh);
+ ctx.drawImage(sprite, -halfW, -dh / 2, halfW, dh);
ctx.restore();
return;
}
- const dh = (drawW * (image.height || 1)) / (image.width || 1);
- ctx.drawImage(image, cx - drawW / 2, cy - dh / 2, drawW, dh);
+ const dh = (drawW * (sprite.height || 1)) / (sprite.width || 1);
+ ctx.drawImage(sprite, cx - drawW / 2, cy - dh / 2, drawW, dh);
return;
}
- const color = spec.color ?? '#3a2a4a';
const line = spec.line ?? '#55386e';
const lw = Math.max(2, 9 * size);
const stroke = (points, width) => strokePolyline(ctx, points, {