idk
This commit is contained in:
parent
b68deb838a
commit
082d8561d2
2 changed files with 71 additions and 17 deletions
77
js/app.js
77
js/app.js
|
|
@ -586,23 +586,31 @@ function compositeRender(canvas, f, zoom) {
|
|||
mode === 'photo-dim' ? 0.34 : 1);
|
||||
}
|
||||
|
||||
const r = renderFrame(f, mode === 'oval+photo' ? 'oval' : (showPhoto ? 'off' : mode));
|
||||
const img2 = r.toImageData(PALETTE.map((p) => p.hex), zoom);
|
||||
if (showPhoto) {
|
||||
// Keep the photo visible wherever the indexed layer is background.
|
||||
const bg = PALETTE[IDX.bg].hex.replace('#', '');
|
||||
const br = parseInt(bg.slice(0, 2), 16), bgn = parseInt(bg.slice(2, 4), 16), bb = parseInt(bg.slice(4, 6), 16);
|
||||
const d = img2.data;
|
||||
for (let i = 0; i < d.length; i += 4) {
|
||||
if (d[i] === br && d[i + 1] === bgn && d[i + 2] === bb) d[i + 3] = 0;
|
||||
}
|
||||
const tmp = document.createElement('canvas');
|
||||
tmp.width = img2.width; tmp.height = img2.height;
|
||||
tmp.getContext('2d').putImageData(img2, 0, 0);
|
||||
g.drawImage(tmp, 0, 0);
|
||||
} else {
|
||||
g.putImageData(img2, 0, 0);
|
||||
blitIndexed(g, renderFrame(f, mode === 'oval+photo' ? 'oval' : (showPhoto ? 'off' : mode)),
|
||||
zoom, showPhoto);
|
||||
}
|
||||
|
||||
// Put an indexed raster onto a 2D context. With `keyBg`, background pixels go
|
||||
// transparent instead of opaque, so whatever was painted underneath - a
|
||||
// registered photograph, usually - stays visible through them.
|
||||
//
|
||||
// Split out of compositeRender so the paint canvas can draw the same pixels at
|
||||
// a reduced globalAlpha. Nothing else is different about that path, which is
|
||||
// what keeps the editing view honest: you are dimming the render, not looking
|
||||
// at a second renderer that might disagree with it.
|
||||
function blitIndexed(g, raster, zoom, keyBg) {
|
||||
const img = raster.toImageData(PALETTE.map((p) => p.hex), zoom);
|
||||
if (!keyBg) { g.putImageData(img, 0, 0); return; }
|
||||
const bg = PALETTE[IDX.bg].hex.replace('#', '');
|
||||
const br = parseInt(bg.slice(0, 2), 16), bgn = parseInt(bg.slice(2, 4), 16), bb = parseInt(bg.slice(4, 6), 16);
|
||||
const d = img.data;
|
||||
for (let i = 0; i < d.length; i += 4) {
|
||||
if (d[i] === br && d[i + 1] === bgn && d[i + 2] === bb) d[i + 3] = 0;
|
||||
}
|
||||
const tmp = document.createElement('canvas');
|
||||
tmp.width = img.width; tmp.height = img.height;
|
||||
tmp.getContext('2d').putImageData(img, 0, 0);
|
||||
g.drawImage(tmp, 0, 0);
|
||||
}
|
||||
|
||||
const keptSorted = () => [...state.keep].sort((a, b) => a - b);
|
||||
|
|
@ -1292,7 +1300,11 @@ const paint = new PaintUI({
|
|||
g.fillStyle = '#0d0f16'; g.fillRect(0, 0, cv.width, cv.height);
|
||||
return;
|
||||
}
|
||||
compositeRender(cv, state.frame, z);
|
||||
const op = +el('paintOpacity').value / 100;
|
||||
// At 100% this is exactly the normal composite, so the slider changes
|
||||
// nothing at all until you reach for it.
|
||||
if (op >= 1) { compositeRender(cv, state.frame, z); return; }
|
||||
paintGhost(cv, z, op);
|
||||
},
|
||||
// paintLabels, not drawPaint: drawPaint re-renders the canvas, and this runs
|
||||
// from inside commit(), which renders immediately afterwards anyway.
|
||||
|
|
@ -1305,6 +1317,11 @@ el('paintTool').addEventListener('change', () => {
|
|||
paint.render();
|
||||
});
|
||||
el('paintColor').addEventListener('change', () => { paint.color = +el('paintColor').value; });
|
||||
el('paintOpacity').addEventListener('input', () => {
|
||||
el('paintOpacityv').textContent = `${el('paintOpacity').value}%`;
|
||||
paint.render();
|
||||
});
|
||||
el('paintOpacityv').textContent = `${el('paintOpacity').value}%`;
|
||||
el('btn-celclear').onclick = () => {
|
||||
if (!state.dense) return;
|
||||
state.cels.set(celFrame(), newCel());
|
||||
|
|
@ -1362,6 +1379,32 @@ function drawPaint() {
|
|||
paint.render();
|
||||
}
|
||||
|
||||
// The drawing dimmed over the source frame, for tracing.
|
||||
//
|
||||
// An EDITING AID ONLY - opacity is never written to the raster, never exported,
|
||||
// and the flat render is untouched. Nothing here may reach the output: a
|
||||
// translucent fill is the one thing this format cannot express, so if it ever
|
||||
// leaked into the rasteriser it would have to be flattened against a background
|
||||
// and would silently become a colour that is not in the ramp.
|
||||
//
|
||||
// The plate oval is deliberately not drawn. It is a stand-in for art that does
|
||||
// not exist yet, and it covers the performer's face in flat skin - which is
|
||||
// exactly the part of the frame you turned the opacity down to look at.
|
||||
function paintGhost(cv, z, op) {
|
||||
cv.width = RW * z; cv.height = RH * z;
|
||||
const g = cv.getContext('2d');
|
||||
g.fillStyle = PALETTE[IDX.bg].hex;
|
||||
g.fillRect(0, 0, cv.width, cv.height);
|
||||
|
||||
const f = state.frame, pf = plateIndex(f);
|
||||
const img = state.images[pf];
|
||||
if (img) drawRegistered(g, img, state.stab.transforms[pf], state.xform, z, 1);
|
||||
|
||||
g.globalAlpha = op;
|
||||
blitIndexed(g, renderFrame(f, 'off'), z, !!img);
|
||||
g.globalAlpha = 1;
|
||||
}
|
||||
|
||||
function paintLabels() {
|
||||
if (!state.dense) return;
|
||||
const kept = keptSorted(), cf = celFrame();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue