refinement
This commit is contained in:
@@ -237,6 +237,104 @@ export function renderCanvasPhoto(ctx, w, h, scene, params, opts = {}) {
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.globalAlpha = 1;
|
||||
|
||||
/* ---------- Pass 10: media & hand (réseau, splice, film furniture, grease) ---------- */
|
||||
if (scene.media) drawMedia(ctx, scene.media, w, h, tx, ty, scale, u, Pf, light);
|
||||
}
|
||||
|
||||
/* ---- media & hand layer ---- */
|
||||
function drawMedia(ctx, media, w, h, tx, ty, scale, u, P, light) {
|
||||
const [fr, fg, fb] = P.ink;
|
||||
const feat = (a) => `rgba(${fr},${fg},${fb},${a})`;
|
||||
|
||||
// réseau crosses (imaged grid)
|
||||
if (media.reseau) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = feat(media.reseau.opacity);
|
||||
ctx.lineWidth = 1 * u;
|
||||
const s = media.reseau.size * scale;
|
||||
for (const m of media.reseau.marks) {
|
||||
const x = tx(m.x), y = ty(m.y);
|
||||
ctx.beginPath(); ctx.moveTo(x - s, y); ctx.lineTo(x + s, y); ctx.moveTo(x, y - s); ctx.lineTo(x, y + s); ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// tape splice
|
||||
if (media.splice) {
|
||||
const sp = media.splice, yc = ty(sp.y), hh = sp.h * scale;
|
||||
ctx.save();
|
||||
ctx.translate(w / 2, yc); ctx.rotate(sp.tilt);
|
||||
ctx.globalCompositeOperation = light ? 'multiply' : 'screen';
|
||||
ctx.fillStyle = feat(sp.opacity);
|
||||
ctx.fillRect(-w, -hh, w * 2, hh * 2);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.strokeStyle = feat(Math.min(0.6, sp.opacity * 2)); ctx.lineWidth = 1 * u;
|
||||
ctx.beginPath(); ctx.moveTo(-w, -hh); ctx.lineTo(w, -hh); ctx.moveTo(-w, hh); ctx.lineTo(w, hh); ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// film furniture
|
||||
if (media.film) {
|
||||
const f = media.film;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = feat(0.5); ctx.lineWidth = 2 * u;
|
||||
const m = 0.965;
|
||||
ctx.strokeRect(tx(-m), ty(-m), tx(m) - tx(-m), ty(m) - ty(-m));
|
||||
const sw = 0.028 * scale, sh = 0.05 * scale, rr = 4 * u;
|
||||
for (const y of f.sprockets) {
|
||||
for (const sx of [tx(-0.985), tx(0.985)]) {
|
||||
ctx.beginPath();
|
||||
if (ctx.roundRect) ctx.roundRect(sx - sw / 2, ty(y) - sh / 2, sw, sh, rr);
|
||||
else ctx.rect(sx - sw / 2, ty(y) - sh / 2, sw, sh);
|
||||
ctx.fillStyle = light ? 'rgba(250,248,240,0.7)' : 'rgba(18,16,13,0.85)';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = feat(0.4); ctx.lineWidth = 1 * u; ctx.stroke();
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = feat(0.45);
|
||||
ctx.font = `${7 * u}px 'JetBrains Mono', monospace`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.save(); ctx.translate(tx(-0.935), ty(0)); ctx.rotate(-Math.PI / 2); ctx.fillText(f.edgeText, 0, 0); ctx.restore();
|
||||
ctx.textAlign = 'left';
|
||||
ctx.font = `${9 * u}px 'JetBrains Mono', monospace`;
|
||||
f.dataBox.forEach((line, i) => ctx.fillText(line, tx(-0.9), ty(0.8) + i * 12 * u));
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// grease pencil (chinagraph) on top
|
||||
if (media.grease && media.grease.length) {
|
||||
const c = light ? [156, 30, 30] : [240, 226, 150];
|
||||
const stroke = (a) => `rgba(${c[0]},${c[1]},${c[2]},${a})`;
|
||||
ctx.save();
|
||||
ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
||||
ctx.strokeStyle = stroke(0.85); ctx.fillStyle = stroke(0.9);
|
||||
for (const g of media.grease) {
|
||||
if (g.kind === 'ring' || g.kind === 'arrow') {
|
||||
const pts = g.pts || g.shaft;
|
||||
ctx.lineWidth = g.width * u;
|
||||
ctx.beginPath(); ctx.moveTo(tx(pts[0].x), ty(pts[0].y));
|
||||
for (let i = 1; i < pts.length; i++) ctx.lineTo(tx(pts[i].x), ty(pts[i].y));
|
||||
ctx.stroke();
|
||||
if (g.kind === 'arrow') {
|
||||
const { x, y, ang } = g.tip, hl = 0.03 * scale;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(tx(x), ty(y)); ctx.lineTo(tx(x) - Math.cos(ang - 0.4) * hl, ty(y) - Math.sin(ang - 0.4) * hl);
|
||||
ctx.moveTo(tx(x), ty(y)); ctx.lineTo(tx(x) - Math.cos(ang + 0.4) * hl, ty(y) - Math.sin(ang + 0.4) * hl);
|
||||
ctx.stroke();
|
||||
}
|
||||
} else if (g.kind === 'arc') {
|
||||
ctx.lineWidth = g.width * u; ctx.beginPath(); ctx.arc(tx(g.x), ty(g.y), g.r * scale, g.a0, g.a1); ctx.stroke();
|
||||
} else if (g.kind === 'tick') {
|
||||
ctx.lineWidth = g.width * u; ctx.beginPath(); ctx.moveTo(tx(g.x1), ty(g.y1)); ctx.lineTo(tx(g.x2), ty(g.y2)); ctx.stroke();
|
||||
} else if (g.kind === 'text') {
|
||||
ctx.save(); ctx.translate(tx(g.x), ty(g.y)); ctx.rotate(g.rot || 0);
|
||||
ctx.font = `${(g.size * scale).toFixed(1)}px 'Bradley Hand','Segoe Script','Comic Sans MS',cursive`;
|
||||
ctx.fillText(g.text, 0, 0); ctx.restore();
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- shock disk ---- */
|
||||
|
||||
@@ -220,6 +220,69 @@ export function renderSVG(scene, params, sizePx = 4800) {
|
||||
+ `</g>`;
|
||||
}
|
||||
|
||||
/* ---------- Media & hand (réseau, splice, film furniture, grease pencil) ---------- */
|
||||
let media = '';
|
||||
if (scene.media) {
|
||||
const M = scene.media;
|
||||
const nx = (x) => (cx + x * scale), ny = (y) => (cy + y * scale);
|
||||
const esc = (t) => String(t).replace(/[<&]/g, c => (c === '<' ? '<' : '&'));
|
||||
if (M.reseau) {
|
||||
let g = `<g stroke="${ink}" stroke-opacity="${M.reseau.opacity.toFixed(3)}" stroke-width="${1 * u}">`;
|
||||
const ss = M.reseau.size * scale;
|
||||
for (const m of M.reseau.marks) {
|
||||
const x = nx(m.x), y = ny(m.y);
|
||||
g += `<line x1="${(x - ss).toFixed(1)}" y1="${y.toFixed(1)}" x2="${(x + ss).toFixed(1)}" y2="${y.toFixed(1)}"/><line x1="${x.toFixed(1)}" y1="${(y - ss).toFixed(1)}" x2="${x.toFixed(1)}" y2="${(y + ss).toFixed(1)}"/>`;
|
||||
}
|
||||
media += g + `</g>`;
|
||||
}
|
||||
if (M.splice) {
|
||||
const yc = ny(M.splice.y), hh = M.splice.h * scale, deg = M.splice.tilt * 57.3;
|
||||
media += `<g transform="rotate(${deg.toFixed(2)} ${(w / 2).toFixed(1)} ${yc.toFixed(1)})">`
|
||||
+ `<rect x="0" y="${(yc - hh).toFixed(1)}" width="${w}" height="${(hh * 2).toFixed(1)}" fill="${ink}" fill-opacity="${M.splice.opacity.toFixed(3)}"/>`
|
||||
+ `<line x1="0" y1="${(yc - hh).toFixed(1)}" x2="${w}" y2="${(yc - hh).toFixed(1)}" stroke="${ink}" stroke-opacity="${Math.min(0.6, M.splice.opacity * 2).toFixed(3)}" stroke-width="${u}"/>`
|
||||
+ `<line x1="0" y1="${(yc + hh).toFixed(1)}" x2="${w}" y2="${(yc + hh).toFixed(1)}" stroke="${ink}" stroke-opacity="${Math.min(0.6, M.splice.opacity * 2).toFixed(3)}" stroke-width="${u}"/></g>`;
|
||||
}
|
||||
if (M.film) {
|
||||
const f = M.film, m = 0.965;
|
||||
media += `<rect x="${nx(-m).toFixed(1)}" y="${ny(-m).toFixed(1)}" width="${((nx(m) - nx(-m))).toFixed(1)}" height="${((ny(m) - ny(-m))).toFixed(1)}" fill="none" stroke="${ink}" stroke-opacity="0.5" stroke-width="${2 * u}"/>`;
|
||||
const sw = 0.028 * scale, sh = 0.05 * scale, rx = 4 * u, holeFill = inv ? '#faf8f0' : '#12100d';
|
||||
let g = '';
|
||||
for (const y of f.sprockets) for (const sx of [nx(-0.985), nx(0.985)]) {
|
||||
g += `<rect x="${(sx - sw / 2).toFixed(1)}" y="${(ny(y) - sh / 2).toFixed(1)}" width="${sw.toFixed(1)}" height="${sh.toFixed(1)}" rx="${rx.toFixed(1)}" fill="${holeFill}" fill-opacity="0.7" stroke="${ink}" stroke-opacity="0.4" stroke-width="${u}"/>`;
|
||||
}
|
||||
media += g;
|
||||
media += `<g fill="${ink}" font-family="'JetBrains Mono', monospace">`
|
||||
+ `<text x="${nx(-0.935).toFixed(1)}" y="${ny(0).toFixed(1)}" font-size="${(7 * u).toFixed(0)}" fill-opacity="0.45" text-anchor="middle" transform="rotate(-90 ${nx(-0.935).toFixed(1)} ${ny(0).toFixed(1)})">${esc(f.edgeText)}</text>`;
|
||||
f.dataBox.forEach((line, i) => { media += `<text x="${nx(-0.9).toFixed(1)}" y="${(ny(0.8) + i * 12 * u).toFixed(1)}" font-size="${(9 * u).toFixed(0)}" fill-opacity="0.5">${esc(line)}</text>`; });
|
||||
media += `</g>`;
|
||||
}
|
||||
if (M.grease && M.grease.length) {
|
||||
const ch = inv ? '#9c1e1e' : '#f0e296';
|
||||
let g = `<g stroke="${ch}" fill="${ch}" stroke-linecap="round" stroke-linejoin="round">`;
|
||||
for (const gm of M.grease) {
|
||||
if (gm.kind === 'ring' || gm.kind === 'arrow') {
|
||||
const pts = gm.pts || gm.shaft;
|
||||
let d = `M ${nx(pts[0].x).toFixed(1)} ${ny(pts[0].y).toFixed(1)}`;
|
||||
for (let i = 1; i < pts.length; i++) d += ` L ${nx(pts[i].x).toFixed(1)} ${ny(pts[i].y).toFixed(1)}`;
|
||||
g += `<path d="${d}" fill="none" stroke-opacity="0.85" stroke-width="${(gm.width * u).toFixed(2)}"/>`;
|
||||
if (gm.kind === 'arrow') {
|
||||
const { x, y, ang } = gm.tip, hl = 0.03 * scale;
|
||||
g += `<path d="M ${nx(x).toFixed(1)} ${ny(y).toFixed(1)} L ${(nx(x) - Math.cos(ang - 0.4) * hl).toFixed(1)} ${(ny(y) - Math.sin(ang - 0.4) * hl).toFixed(1)} M ${nx(x).toFixed(1)} ${ny(y).toFixed(1)} L ${(nx(x) - Math.cos(ang + 0.4) * hl).toFixed(1)} ${(ny(y) - Math.sin(ang + 0.4) * hl).toFixed(1)}" fill="none" stroke-opacity="0.85" stroke-width="${(gm.width * u).toFixed(2)}"/>`;
|
||||
}
|
||||
} else if (gm.kind === 'arc') {
|
||||
const x0 = nx(gm.x) + Math.cos(gm.a0) * gm.r * scale, y0 = ny(gm.y) + Math.sin(gm.a0) * gm.r * scale;
|
||||
const x1 = nx(gm.x) + Math.cos(gm.a1) * gm.r * scale, y1 = ny(gm.y) + Math.sin(gm.a1) * gm.r * scale;
|
||||
g += `<path d="M ${x0.toFixed(1)} ${y0.toFixed(1)} A ${(gm.r * scale).toFixed(1)} ${(gm.r * scale).toFixed(1)} 0 0 1 ${x1.toFixed(1)} ${y1.toFixed(1)}" fill="none" stroke-opacity="0.85" stroke-width="${(gm.width * u).toFixed(2)}"/>`;
|
||||
} else if (gm.kind === 'tick') {
|
||||
g += `<line x1="${nx(gm.x1).toFixed(1)}" y1="${ny(gm.y1).toFixed(1)}" x2="${nx(gm.x2).toFixed(1)}" y2="${ny(gm.y2).toFixed(1)}" stroke-opacity="0.85" stroke-width="${(gm.width * u).toFixed(2)}"/>`;
|
||||
} else if (gm.kind === 'text') {
|
||||
g += `<text x="${nx(gm.x).toFixed(1)}" y="${ny(gm.y).toFixed(1)}" font-size="${(gm.size * scale).toFixed(0)}" font-family="'Bradley Hand','Segoe Script','Comic Sans MS',cursive" fill-opacity="0.9" transform="rotate(${((gm.rot || 0) * 57.3).toFixed(1)} ${nx(gm.x).toFixed(1)} ${ny(gm.y).toFixed(1)})">${esc(gm.text)}</text>`;
|
||||
}
|
||||
}
|
||||
media += g + `</g>`;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Assemble ---------- */
|
||||
let s = `<?xml version="1.0" encoding="UTF-8"?>\n`;
|
||||
s += `<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">\n`;
|
||||
@@ -233,6 +296,7 @@ export function renderSVG(scene, params, sizePx = 4800) {
|
||||
s += layer('fiducials', 'Fiducials', fids);
|
||||
s += layer('vignette', 'Vignette', vign);
|
||||
s += layer('header', 'Archival header', header);
|
||||
s += layer('media', 'Media & hand', media);
|
||||
s += `</svg>\n`;
|
||||
return s;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user