diff options
Diffstat (limited to 'public/bluebey-studio/src/faceArt.js')
| -rw-r--r-- | public/bluebey-studio/src/faceArt.js | 57 |
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, { |
