1/** 2 * Pure composite helpers â flatten a layer list into a single canvas 3 * for thumbnails / merged-mask use. 4 * 5 * Both helpers are stateless: the caller passes everything they need 6 * (layer list, canvas dimensions, an offsets lookup). The legacy 7 * gallery editor's module-level functions wrap these with their own 8 * state. 9 */ 10 11/** 12 * Cheap downscaled preview composited from all visible layers. 13 * Returns a JPEG dataURL, or null when there's nothing to draw. 14 * 15 * @param {Array<{visible: boolean, opacity: number, id: string, canvas: HTMLCanvasElement}>} layers 16 * @param {number} imgW Document width in canvas pixels. 17 * @param {number} imgH Document height in canvas pixels. 18 * @param {Map<string,{x:number,y:number}>} offsets Layer offsets, keyed by id. 19 * @param {number} maxDim Longest-edge target in CSS pixels. 20 * @param {number} quality JPEG quality 0..1. 21 * @returns {string|null} 22 */ 23export function buildThumbnail(layers, imgW, imgH, offsets, maxDim, quality = 0.6) { 24 if (!imgW || !imgH) return null; 25 try { 26 const scale = Math.min(1, maxDim / Math.max(imgW, imgH)); 27 const tw = Math.max(1, Math.round(imgW * scale)); 28 const th = Math.max(1, Math.round(imgH * scale)); 29 const c = document.createElement('canvas'); 30 c.width = tw;
30 c.height = th; 31 const ctx = c.getContext('2d'); 32 for (const layer of layers) { 33 if (!layer.visible) continue; 34 ctx.globalAlpha = layer.opacity; 35 const off = offsets.get(layer.id) || { x: 0, y: 0 }; 36 ctx.drawImage( 37 layer.canvas, 38 off.x * scale, off.y * scale, 39 layer.canvas.width * scale, layer.canvas.height * scale, 40 ); 41 } 42 ctx.globalAlpha = 1; 43 return c.toDataURL('image/jpeg', quality); 44 } catch (_) { 45 return null; 46 } 47} 48 49 50/** 51 * Union of every visible mask sub-layer across `layers`, rendered as a 52 * binary white canvas the size of the document. 53 * 54 * `lighter` composite = additive â overlapping pixels stay clamped at 55 * 255, so wherever any mask painted, the result is solid white. 56 * Returns null when no mask layer contributed any pixels (so the caller 57 * can early-out cleanly). 58 * 59 * @param {Array<{masks?: Array<{visible: boolean, canvas: HTMLCanvasElement}>}>} layers 60 * @param {number} imgW 61 * @param {number} imgH 62 * @returns {HTMLCanvasElement|null} 63 */ 64export function buildMergedMaskCanvas(layers, imgW, imgH) { 65 if (!imgW || !imgH) return null; 66 const out = document.createElement('canvas'); 67 out.width = imgW; 68 out.height = imgH; 69 const ctx = out.getContext('2d'); 70 ctx.globalCompositeOperation = 'lighter'; 71 let anyMask = false; 72 for (const ly of layers) { 73 if (!ly.masks || !ly.masks.length) continue; 74 for (const mk of ly.masks) { 75 if (!mk.visible) continue; 76 if (!mk.canvas || !mk.canvas.width || !mk.canvas.height) continue; 77 ctx.drawImage(mk.canvas, 0, 0); 78 anyMask = true; 79 } 80 } 81 ctx.globalCompositeOperation = 'source-over'; 82 return anyMask ? out : null; 83}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.