mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 09:45:24 -04:00
Odysseus v1.0
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Mask-canvas helpers used by the inpaint pipeline.
|
||||
*
|
||||
* Pure utility functions — they take a canvas (or layer-shape) as
|
||||
* input and return a fresh canvas, with no module-level state.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dilate (positive `px`) or erode (negative `px`) a binary alpha mask.
|
||||
*
|
||||
* Strategy: blur the source by `|px|`, then re-threshold the result.
|
||||
* - Dilation keeps anything with non-trivial blurred alpha (low cutoff).
|
||||
* - Erosion keeps only pixels that retained near-full alpha after blur.
|
||||
*
|
||||
* @param {HTMLCanvasElement} src Source mask canvas.
|
||||
* @param {number} px Pixels to dilate (>0) or erode (<0). 0 = copy.
|
||||
* @returns {HTMLCanvasElement} Fresh canvas with the same dimensions.
|
||||
*/
|
||||
export function dilateMask(src, px) {
|
||||
const w = src.width, h = src.height;
|
||||
const tmp = document.createElement('canvas');
|
||||
tmp.width = w; tmp.height = h;
|
||||
const ctx = tmp.getContext('2d');
|
||||
if (px === 0) {
|
||||
ctx.drawImage(src, 0, 0);
|
||||
return tmp;
|
||||
}
|
||||
const dilate = px > 0;
|
||||
const radius = Math.abs(px);
|
||||
ctx.filter = `blur(${radius}px)`;
|
||||
ctx.drawImage(src, 0, 0);
|
||||
ctx.filter = 'none';
|
||||
const img = ctx.getImageData(0, 0, w, h);
|
||||
const threshold = dilate ? 8 : 247;
|
||||
for (let i = 0; i < img.data.length; i += 4) {
|
||||
const a = img.data[i + 3];
|
||||
const keep = dilate ? a > threshold : a >= threshold;
|
||||
if (keep) {
|
||||
img.data[i] = img.data[i + 1] = img.data[i + 2] = 255;
|
||||
img.data[i + 3] = 255;
|
||||
} else {
|
||||
img.data[i + 3] = 0;
|
||||
}
|
||||
}
|
||||
ctx.putImageData(img, 0, 0);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Re-derive an inpaint-result layer's alpha from its cached AI image +
|
||||
* the hard mask, applying a feather + optional dilate/erode of the
|
||||
* boundary. Mutates `layer.canvas` in place via `layer.ctx`.
|
||||
*
|
||||
* The layer must carry an `inpaintSource = { ai, mask }` cache from the
|
||||
* original inpaint call so we can re-shape the alpha cheaply (no
|
||||
* second model call required).
|
||||
*
|
||||
* @param {{canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D,
|
||||
* inpaintSource?: {ai: CanvasImageSource, mask: HTMLCanvasElement}}} layer
|
||||
* @param {number} featherPx Gaussian blur radius applied to the mask alpha.
|
||||
* @param {number} [edgeShiftPx] Dilate (+) or erode (-) the mask before blurring.
|
||||
*/
|
||||
export function applyInpaintFeather(layer, featherPx, edgeShiftPx = 0) {
|
||||
if (!layer || !layer.inpaintSource) return;
|
||||
const { ai, mask } = layer.inpaintSource;
|
||||
const w = layer.canvas.width;
|
||||
const h = layer.canvas.height;
|
||||
// 1) Optional dilate/erode, then optional blur, into a fresh mask.
|
||||
let shaped = mask;
|
||||
if (edgeShiftPx !== 0) shaped = dilateMask(mask, edgeShiftPx);
|
||||
const softMask = document.createElement('canvas');
|
||||
softMask.width = w; softMask.height = h;
|
||||
const smCtx = softMask.getContext('2d');
|
||||
if (featherPx > 0) {
|
||||
smCtx.filter = `blur(${featherPx}px)`;
|
||||
smCtx.drawImage(shaped, 0, 0, w, h);
|
||||
smCtx.filter = 'none';
|
||||
} else {
|
||||
smCtx.drawImage(shaped, 0, 0, w, h);
|
||||
}
|
||||
// 2) Draw the AI image fresh, then multiply alpha by the soft mask.
|
||||
const ctx = layer.ctx;
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.drawImage(ai, 0, 0);
|
||||
ctx.globalCompositeOperation = 'destination-in';
|
||||
ctx.drawImage(softMask, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
Reference in New Issue
Block a user