mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-15 17:25:26 -04:00
Odysseus v1.0
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Mask builders used by the AI Harmonize pipeline.
|
||||
*
|
||||
* - `layerUnionAlpha` — union of every non-base layer's alpha, as a
|
||||
* binary (0/255) mask. Used as the substrate
|
||||
* for both seam and body masks.
|
||||
* - `seamMask` — feathered band along the alpha edges of all
|
||||
* non-base layers. White = "blend here",
|
||||
* black = "leave alone". Returned as base64
|
||||
* PNG (so it can be POST'd to the diffusion
|
||||
* endpoint as JSON).
|
||||
* - `layerBodyMask` — feathered FULL shape of every non-base layer.
|
||||
* White = "AI may redraw this pixel using the
|
||||
* existing pixels as a starting point";
|
||||
* black = "preserve exactly". Returned as base64.
|
||||
*
|
||||
* Each helper takes the visible layer list + the doc dimensions; no
|
||||
* module state.
|
||||
*
|
||||
* @typedef {{ visible: boolean, id: string, canvas: HTMLCanvasElement, offset: {x: number, y: number} }} HarmLayer
|
||||
*/
|
||||
|
||||
/**
|
||||
* Build a binary alpha mask = the UNION of every non-base visible
|
||||
* layer's pixels. Returns null when fewer than 2 visible layers exist
|
||||
* or when the non-base layers are entirely transparent.
|
||||
*
|
||||
* @param {number} w / h Canvas dimensions in pixels.
|
||||
* @param {HarmLayer[]} layers All layers in stack order; the
|
||||
* first VISIBLE layer is treated as
|
||||
* the base / background.
|
||||
* @returns {HTMLCanvasElement|null}
|
||||
*/
|
||||
export function layerUnionAlpha(w, h, layers) {
|
||||
const visible = layers.filter(l => l.visible);
|
||||
if (visible.length < 2) return null;
|
||||
const bgId = visible[0].id;
|
||||
const alphaCanvas = document.createElement('canvas');
|
||||
alphaCanvas.width = w; alphaCanvas.height = h;
|
||||
const actx = alphaCanvas.getContext('2d');
|
||||
let hasFg = false;
|
||||
for (const layer of visible) {
|
||||
if (layer.id === bgId) continue;
|
||||
const off = layer.offset || { x: 0, y: 0 };
|
||||
actx.drawImage(layer.canvas, off.x, off.y);
|
||||
hasFg = true;
|
||||
}
|
||||
if (!hasFg) return null;
|
||||
const src = actx.getImageData(0, 0, w, h);
|
||||
const bin = document.createElement('canvas');
|
||||
bin.width = w; bin.height = h;
|
||||
const bctx = bin.getContext('2d');
|
||||
const binImg = bctx.createImageData(w, h);
|
||||
let any = false;
|
||||
for (let i = 0; i < src.data.length; i += 4) {
|
||||
const v = src.data[i + 3] > 0 ? 255 : 0;
|
||||
if (v) any = true;
|
||||
binImg.data[i] = binImg.data[i + 1] = binImg.data[i + 2] = v;
|
||||
binImg.data[i + 3] = 255;
|
||||
}
|
||||
if (!any) return null;
|
||||
bctx.putImageData(binImg, 0, 0);
|
||||
return bin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a feathered seam mask along the alpha edges of all non-base
|
||||
* visible layers. Returns base64-encoded PNG (no `data:` prefix), or
|
||||
* null if there's nothing to harmonize.
|
||||
*/
|
||||
export function seamMask(w, h, layers, featherPx = 12) {
|
||||
const bin = layerUnionAlpha(w, h, layers);
|
||||
if (!bin) return null;
|
||||
const blur = document.createElement('canvas');
|
||||
blur.width = w; blur.height = h;
|
||||
const blctx = blur.getContext('2d');
|
||||
blctx.filter = `blur(${featherPx}px)`;
|
||||
blctx.drawImage(bin, 0, 0);
|
||||
blctx.filter = 'none';
|
||||
const blurred = blctx.getImageData(0, 0, w, h);
|
||||
const mask = blctx.createImageData(w, h);
|
||||
// Triangular weight peaked at mid-grey — picks out the alpha-edge band.
|
||||
for (let i = 0; i < blurred.data.length; i += 4) {
|
||||
const v = blurred.data[i];
|
||||
const dist = Math.abs(v - 128);
|
||||
const wt = Math.max(0, 255 - dist * 2);
|
||||
mask.data[i] = mask.data[i + 1] = mask.data[i + 2] = wt;
|
||||
mask.data[i + 3] = 255;
|
||||
}
|
||||
blctx.putImageData(mask, 0, 0);
|
||||
const soft = document.createElement('canvas');
|
||||
soft.width = w; soft.height = h;
|
||||
const sctx = soft.getContext('2d');
|
||||
sctx.filter = `blur(${Math.max(2, Math.floor(featherPx / 4))}px)`;
|
||||
sctx.drawImage(blur, 0, 0);
|
||||
sctx.filter = 'none';
|
||||
return soft.toDataURL('image/png').split(',')[1];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a feathered FULL-shape mask of every non-base visible layer.
|
||||
* Returns base64-encoded PNG, or null if there are no non-base layers.
|
||||
*/
|
||||
export function layerBodyMask(w, h, layers, featherPx = 12) {
|
||||
const bin = layerUnionAlpha(w, h, layers);
|
||||
if (!bin) return null;
|
||||
const soft = document.createElement('canvas');
|
||||
soft.width = w; soft.height = h;
|
||||
const sctx = soft.getContext('2d');
|
||||
sctx.filter = `blur(${featherPx}px)`;
|
||||
sctx.drawImage(bin, 0, 0);
|
||||
sctx.filter = 'none';
|
||||
return soft.toDataURL('image/png').split(',')[1];
|
||||
}
|
||||
Reference in New Issue
Block a user