mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 17:55:26 -04:00
Odysseus v1.0
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Pure composite helpers — flatten a layer list into a single canvas
|
||||
* for thumbnails / merged-mask use.
|
||||
*
|
||||
* Both helpers are stateless: the caller passes everything they need
|
||||
* (layer list, canvas dimensions, an offsets lookup). The legacy
|
||||
* gallery editor's module-level functions wrap these with their own
|
||||
* state.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cheap downscaled preview composited from all visible layers.
|
||||
* Returns a JPEG dataURL, or null when there's nothing to draw.
|
||||
*
|
||||
* @param {Array<{visible: boolean, opacity: number, id: string, canvas: HTMLCanvasElement}>} layers
|
||||
* @param {number} imgW Document width in canvas pixels.
|
||||
* @param {number} imgH Document height in canvas pixels.
|
||||
* @param {Map<string,{x:number,y:number}>} offsets Layer offsets, keyed by id.
|
||||
* @param {number} maxDim Longest-edge target in CSS pixels.
|
||||
* @param {number} quality JPEG quality 0..1.
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export function buildThumbnail(layers, imgW, imgH, offsets, maxDim, quality = 0.6) {
|
||||
if (!imgW || !imgH) return null;
|
||||
try {
|
||||
const scale = Math.min(1, maxDim / Math.max(imgW, imgH));
|
||||
const tw = Math.max(1, Math.round(imgW * scale));
|
||||
const th = Math.max(1, Math.round(imgH * scale));
|
||||
const c = document.createElement('canvas');
|
||||
c.width = tw; c.height = th;
|
||||
const ctx = c.getContext('2d');
|
||||
for (const layer of layers) {
|
||||
if (!layer.visible) continue;
|
||||
ctx.globalAlpha = layer.opacity;
|
||||
const off = offsets.get(layer.id) || { x: 0, y: 0 };
|
||||
ctx.drawImage(
|
||||
layer.canvas,
|
||||
off.x * scale, off.y * scale,
|
||||
layer.canvas.width * scale, layer.canvas.height * scale,
|
||||
);
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
return c.toDataURL('image/jpeg', quality);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Union of every visible mask sub-layer across `layers`, rendered as a
|
||||
* binary white canvas the size of the document.
|
||||
*
|
||||
* `lighter` composite = additive — overlapping pixels stay clamped at
|
||||
* 255, so wherever any mask painted, the result is solid white.
|
||||
* Returns null when no mask layer contributed any pixels (so the caller
|
||||
* can early-out cleanly).
|
||||
*
|
||||
* @param {Array<{masks?: Array<{visible: boolean, canvas: HTMLCanvasElement}>}>} layers
|
||||
* @param {number} imgW
|
||||
* @param {number} imgH
|
||||
* @returns {HTMLCanvasElement|null}
|
||||
*/
|
||||
export function buildMergedMaskCanvas(layers, imgW, imgH) {
|
||||
if (!imgW || !imgH) return null;
|
||||
const out = document.createElement('canvas');
|
||||
out.width = imgW;
|
||||
out.height = imgH;
|
||||
const ctx = out.getContext('2d');
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
let anyMask = false;
|
||||
for (const ly of layers) {
|
||||
if (!ly.masks || !ly.masks.length) continue;
|
||||
for (const mk of ly.masks) {
|
||||
if (!mk.visible) continue;
|
||||
if (!mk.canvas || !mk.canvas.width || !mk.canvas.height) continue;
|
||||
ctx.drawImage(mk.canvas, 0, 0);
|
||||
anyMask = true;
|
||||
}
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
return anyMask ? out : null;
|
||||
}
|
||||
Reference in New Issue
Block a user