1/** 2 * Mask-canvas helpers used by the inpaint pipeline. 3 * 4 * Pure utility functions â they take a canvas (or layer-shape) as 5 * input and return a fresh canvas, with no module-level state. 6 */ 7 8/** 9 * Dilate (positive `px`) or erode (negative `px`) a binary alpha mask. 10 * 11 * Strategy: blur the source by `|px|`, then re-threshold the result. 12 * - Dilation keeps anything with non-trivial blurred alpha (low cutoff). 13 * - Erosion keeps only pixels that retained near-full alpha after blur. 14 * 15 * @param {HTMLCanvasElement} src Source mask canvas. 16 * @param {number} px Pixels to dilate (>0) or erode (<0). 0 = copy. 17 * @returns {HTMLCanvasElement} Fresh canvas with the same dimensions. 18 */ 19export function dilateMask(src, px) { 20 const w = src.width, h = src.height; 21 const tmp = document.createElement('canvas'); 22 tmp.width = w; tmp.height = h; 23 const ctx = tmp.getContext('2d'); 24 if (px === 0) { 25 ctx.drawImage(src, 0, 0); 26 return tmp; 27 } 28 const dilate = px > 0; 29 const radius = Math.abs(px); 30 ctx.filter = `blur(${radius}px)`; 31 ctx.drawImage(src, 0, 0); 32 ctx.filter = 'none'; 33 const img = ctx.getImageData(0, 0, w, h); 34 const threshold = dilate ? 8 : 247; 35 for (let i = 0; i < img.data.length; i += 4) { 36 const a = img.data[i + 3]; 37 const keep = dilate ? a > threshold : a >= threshold; 38 if (keep) { 39 img.data[i] = img.data[i + 1] = img.data[i + 2] = 255; 40 img.data[i + 3] = 255; 41 } else { 42 img.data[i + 3] = 0; 43 } 44 } 45 ctx.putImageData(img, 0, 0); 46 return tmp; 47} 48 49 50/** 51 * Re-derive an inpaint-result layer's alpha from its cached AI image + 52 * the hard mask, applying a feather + optional dilate/erode of the 53 * boundary. Mutates `layer.canvas` in place via `layer.ctx`. 54 * 55 * The layer must carry an `inpaintSource = { ai, mask }` cache from the 56 * original inpaint call so we can re-shape the alpha cheaply (no 57 * second model call required). 58 * 59 * @param {{canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, 60 * inpaintSource?: {ai: CanvasImageSource, mask: HTMLCanvasElement}}} layer 61 * @param {number} featherPx Gaussian blur radius applied to the mask alpha. 62 * @param {number} [edgeShiftPx] Dilate (+) or erode (-) the mask before blurring. 63 */ 64export function applyInpaintFeather(layer, featherPx, edgeShiftPx = 0) { 65 if (!layer || !layer.inpaintSource) return; 66 const { ai, mask } = layer.inpaintSource; 67 const w = layer.canvas.width; 68 const h = layer.canvas.height; 69 // 1) Optional dilate/erode, then optional blur, into a fresh mask. 70 let shaped = mask; 71 if (edgeShiftPx !== 0) shaped = dilateMask(mask, edgeShiftPx); 72 const softMask = document.createElement('canvas'); 73 softMask.width = w; softMask.height = h; 74 const smCtx = softMask.getContext('2d'); 75 if (featherPx > 0) { 76 smCtx.filter = `blur(${featherPx}px)`; 77 smCtx.drawImage(shaped, 0, 0, w, h); 78 smCtx.filter = 'none'; 79 } else { 80 smCtx.drawImage(shaped, 0, 0, w, h); 81 } 82 // 2) Draw the AI image fresh, then multiply alpha by the soft mask. 83 const ctx = layer.ctx; 84 ctx.save(); 85 ctx.globalCompositeOperation = 'source-over'; 86 ctx.clearRect(0, 0, w, h); 87 ctx.drawImage(ai, 0, 0); 88 ctx.globalCompositeOperation = 'destination-in'; 89 ctx.drawImage(softMask, 0, 0); 90 ctx.restore(); 91}
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.