| Server IP : 217.154.3.148 / Your IP : 216.73.216.90 Web Server : Apache System : Linux blissful-wright.217-154-3-148.plesk.page 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64 User : gracious-boyd_hfhh1h8vo9n ( 10000) PHP Version : 8.3.31 Disable Function : opcache_get_status MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/vhosts/gracious-boyd.217-154-3-148.plesk.page/nextjs/lib/masks/ |
Upload File : |
import { normalizeIllumination } from "./illumination";
import { autoTuneMask } from "./autotune";
import { buildLatticeMasks, coverageOfMask, computeBandPx, leakRatio } from "./lattice";
import type { PipelineInput, PipelineResult } from "./types";
export function runMaskPipeline(input: PipelineInput): PipelineResult {
const debug: Record<string, unknown> = {
preset: input.preset,
flags: input.flags,
};
let workingMask = Uint8Array.from(input.normalizedMask);
let intensity = input.intensity;
if (input.flags.enableIllumination) {
const grid = input.grid;
const avgCell = grid ? Math.max(grid.tileWidthPx, grid.tileHeightPx) : 0;
const radius = Math.max(24, Math.round(avgCell * 0.6));
const amount = 0.75;
intensity = normalizeIllumination(intensity, input.width, input.height, { radius, amount });
debug.illumination = { enabled: true, radius, amount };
} else {
debug.illumination = { enabled: false };
}
let leak: number | null = null;
let interiorCoverage: number | null = null;
let latticeWeights: Float32Array | null = null;
let latticeAxis: Int8Array | null = null;
let latticeLineMask: Uint8Array | null = null;
let interiorMaskRef: Uint8Array | null = null;
let leakExtras: {
weights?: Float32Array;
axisMap?: Int8Array;
gradX?: Float32Array;
gradY?: Float32Array;
gradientThreshold?: number;
angleThresholdRad?: number;
} | null = null;
let gradient: { gx: Float32Array; gy: Float32Array } | null = null;
const thinFrac = Math.max(0.0, Math.min(0.5, input.flags.thinWidthFrac ?? 0.05));
const minAreaFrac = Math.max(0.05, Math.min(0.8, input.flags.minAreaFrac ?? 0.3));
const warnings: string[] = [];
const cellMajor = input.grid ? Math.max(input.grid.tileWidthPx, input.grid.tileHeightPx) : 0;
if (input.flags.enableLattice && input.grid && input.grid.vlines.length && input.grid.hlines.length) {
const bandPx = computeBandPx(input.grid);
const leakBandPx = leakBandWidth(input.grid);
const { interiorMask, lineMask, weightMap, axisMap } = buildLatticeMasks(
input.width,
input.height,
input.grid.vlines,
input.grid.hlines,
bandPx,
);
latticeWeights = weightMap;
latticeAxis = axisMap;
latticeLineMask = lineMask;
interiorMaskRef = interiorMask;
interiorCoverage = coverageOfMask(interiorMask);
gradient = computeGradientVectors(intensity, input.width, input.height);
leakExtras = {
weights: latticeWeights,
axisMap: latticeAxis ?? undefined,
gradX: gradient?.gx,
gradY: gradient?.gy,
gradientThreshold: 12,
angleThresholdRad: (15 * Math.PI) / 180,
};
if (gradient && interiorMaskRef) {
const edgeEnergy = computeEdgeEnergy(gradient);
const tangentSeeds = buildTangentSeedMask(
input.width,
input.height,
interiorMaskRef,
latticeWeights,
gradient,
{
coherence: 0.6,
weightThreshold: 0.45,
magnitudeThreshold: 22,
},
);
if (tangentSeeds) {
let added = 0;
for (let i = 0; i < workingMask.length; i++) {
if (tangentSeeds[i] && !workingMask[i]) {
workingMask[i] = 255;
added += 1;
}
}
debug.tangentSeeds = { enabled: true, count: added };
} else {
debug.tangentSeeds = { enabled: false };
}
const fillStats = fillComponentHoles(
workingMask,
input.width,
input.height,
interiorMaskRef,
latticeWeights,
edgeEnergy,
leakBandPx,
cellMajor,
);
if (fillStats) {
debug.componentFill = fillStats;
if (latticeLineMask && leakExtras) {
leak = leakRatio(workingMask, latticeLineMask, leakExtras);
(debug.componentFill as any).leakAfter = leak;
}
} else {
debug.componentFill = { enabled: false, components: 0, holesFilled: 0, pixelsFilled: 0 };
}
const specStats = applySpecularBridges(
workingMask,
input.width,
input.height,
intensity,
latticeWeights,
interiorMaskRef,
gradient,
edgeEnergy,
);
debug.specularBridge = specStats;
if (specStats.enabled && latticeLineMask && leakExtras) {
leak = leakRatio(workingMask, latticeLineMask, leakExtras);
(debug.specularBridge as any).leakAfter = leak;
}
} else {
debug.componentFill = { enabled: false, components: 0, holesFilled: 0, pixelsFilled: 0 };
}
if (!debug.tangentSeeds) {
debug.tangentSeeds = { enabled: false };
}
if (!debug.specularBridge) {
debug.specularBridge = { enabled: false, pixels: 0 };
}
debug.lattice = {
enabled: true,
bandPx,
interiorCoverage,
vlines: input.grid.vlines.length,
hlines: input.grid.hlines.length,
weightMean: meanOf(weightMap),
};
if (input.flags.enableAutotune) {
const tileArea = Math.max(1, input.grid.tileWidthPx * input.grid.tileHeightPx);
const cellMajorLocal = cellMajor > 0 ? cellMajor : Math.max(input.grid.tileWidthPx, input.grid.tileHeightPx);
let minArea = Math.max(32, Math.round(tileArea * minAreaFrac));
let thinWidth = Math.max(2, Math.round(Math.max(1, cellMajorLocal * thinFrac)));
const occluderCoverage = input.stats.background?.occluderCoverage ?? 0;
if (occluderCoverage >= 0.3) {
thinWidth = Math.max(9, thinWidth);
minArea = Math.max(900, minArea);
} else if (occluderCoverage >= 0.18) {
thinWidth = Math.max(6, thinWidth);
minArea = Math.max(650, minArea);
} else {
thinWidth = Math.max(4, thinWidth);
minArea = Math.max(450, minArea);
}
const maxArea = Math.max(minArea, Math.round(tileArea * 1.8));
minArea = Math.min(minArea, maxArea);
thinWidth = Math.min(thinWidth, 12);
const autoOptions = {
targetLow: 0.20,
targetHigh: 0.24,
minArea,
thinWidth,
thinLength: 0.3,
leakLimit: 0.03,
maxDilateSteps: 6,
u2BoostMask: input.u2BoostMask ?? undefined,
};
const tuned = autoTuneMask(
workingMask,
interiorMask,
lineMask,
input.width,
input.height,
autoOptions,
leakExtras ?? {},
);
workingMask = tuned.mask;
leak = tuned.leak;
debug.autotune = {
enabled: true,
targetLow: autoOptions.targetLow,
targetHigh: autoOptions.targetHigh,
minArea,
thinWidth,
adjustments: tuned.adjustments,
leak,
budgetMs: input.flags.autotuneMs,
};
} else {
leak = leakRatio(workingMask, lineMask, leakExtras ?? {});
debug.autotune = { enabled: false, leak, budgetMs: input.flags.autotuneMs };
}
} else {
debug.lattice = { enabled: false };
if (input.flags.enableAutotune) {
debug.autotune = { enabled: false, reason: "no-grid", budgetMs: input.flags.autotuneMs };
}
}
if (!debug.componentFill) {
debug.componentFill = { enabled: false, components: 0, holesFilled: 0, pixelsFilled: 0 };
}
if (latticeWeights) {
const len = workingMask.length;
for (let i = 0; i < len; i++) {
if (workingMask[i] && latticeWeights[i] <= 0.25) {
workingMask[i] = 0;
}
}
}
if (latticeLineMask && leakExtras && latticeWeights && gradient && latticeAxis) {
const leakBandPx = leakBandWidth(input.grid);
const clamp = applyLeakClamp(
workingMask,
input.width,
input.height,
latticeWeights,
latticeAxis,
gradient,
leakBandPx,
latticeLineMask,
leakExtras,
);
leak = clamp.leakAfter;
debug.leakClamp = clamp;
} else {
debug.leakClamp = { enabled: false };
if (latticeLineMask && leakExtras) {
leak = leakRatio(workingMask, latticeLineMask, leakExtras);
}
}
const finalCoverage = coverageOfMask(workingMask);
if (finalCoverage > 0.26 && finalCoverage <= 0.55) warnings.push("coverage_high");
if (finalCoverage < 0.12 && finalCoverage >= 0.06) warnings.push("coverage_low");
if (leak != null && leak > 0 && leak > 0.05) warnings.push("leak_warn");
debug.coverage = {
before: input.normalizedCoverage,
after: finalCoverage,
leak,
warnings,
};
return {
mask: workingMask,
coverage: finalCoverage,
leak,
interiorCoverage,
debug,
};
}
function computeGradientVectors(gray: Uint8Array, width: number, height: number) {
const len = gray.length;
const gx = new Float32Array(len);
const gy = new Float32Array(len);
const gxK = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
const gyK = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let sx = 0;
let sy = 0;
let idxK = 0;
for (let dy = -1; dy <= 1; dy++) {
const yy = y + dy;
for (let dx = -1; dx <= 1; dx++, idxK++) {
const xx = x + dx;
const value = gray[yy * width + xx];
sx += value * gxK[idxK];
sy += value * gyK[idxK];
}
}
const idx = y * width + x;
gx[idx] = sx;
gy[idx] = sy;
}
}
return { gx, gy };
}
function meanOf(values: Float32Array): number {
if (!values?.length) return 0;
let sum = 0;
for (let i = 0; i < values.length; i++) sum += values[i];
return sum / values.length;
}
function leakBandWidth(grid: PipelineInput["grid"]): number {
if (!grid) return 3;
const grout = Math.max(1, grid.groutPx || 1);
return Math.min(6, Math.max(2, Math.round(grout * 1.5)));
}
type LeakClampStats = {
enabled: boolean;
removed?: number;
weakened?: number;
leakBefore?: number;
leakAfter?: number;
};
function applyLeakClamp(
mask: Uint8Array,
width: number,
height: number,
weights: Float32Array,
axis: Int8Array,
gradient: { gx: Float32Array; gy: Float32Array },
bandPx: number,
lineMask: Uint8Array,
leakExtras: {
weights?: Float32Array;
axisMap?: Int8Array;
gradX?: Float32Array;
gradY?: Float32Array;
gradientThreshold?: number;
angleThresholdRad?: number;
},
leakLimit = 0.03,
): LeakClampStats {
const leakBefore = leakRatio(mask, lineMask, leakExtras);
if (leakBefore <= leakLimit) {
return { enabled: false, leakBefore, leakAfter: leakBefore };
}
const cosMax = Math.cos((15 * Math.PI) / 180);
const gxArr = gradient.gx;
const gyArr = gradient.gy;
const len = mask.length;
let removed = 0;
let weakened = 0;
for (let i = 0; i < len; i++) {
if (!mask[i]) continue;
const w = weights[i];
if (w > 0.35) continue;
const orient = axis[i];
if (orient < 0) {
mask[i] = 0;
removed += 1;
continue;
}
const gx = gxArr[i];
const gy = gyArr[i];
const mag = Math.hypot(gx, gy);
if (mag < 1e-6) {
mask[i] = 0;
removed += 1;
continue;
}
const normalX = orient === 0 ? 1 : 0;
const normalY = orient === 1 ? 1 : 0;
const dot = Math.abs((gx * normalX + gy * normalY) / mag);
if (dot > cosMax || w < 0.2) {
mask[i] = 0;
removed += 1;
} else {
weakened += 1;
}
}
const leakAfter = leakRatio(mask, lineMask, leakExtras);
return { enabled: true, removed, weakened, leakBefore, leakAfter };
}
function computeEdgeEnergy(gradient: { gx: Float32Array; gy: Float32Array }): Float32Array {
const { gx, gy } = gradient;
const len = gx.length;
const out = new Float32Array(len);
for (let i = 0; i < len; i++) {
const mag = Math.hypot(gx[i], gy[i]);
out[i] = Math.min(1, mag / 1024);
}
return out;
}
type ComponentFillStats = {
enabled: boolean;
components: number;
holesFilled: number;
pixelsFilled: number;
};
function fillComponentHoles(
mask: Uint8Array,
width: number,
height: number,
interiorMask: Uint8Array,
weights: Float32Array | null,
edgeEnergy: Float32Array,
bandPx: number,
cellSize: number,
): ComponentFillStats | null {
const len = mask.length;
if (len === 0) return null;
const visited = new Uint8Array(len);
const queue = new Int32Array(len);
const componentIndices: number[] = [];
let components = 0;
let holesFilled = 0;
let pixelsFilled = 0;
const maxHoleRatio = 0.35;
const minComponentArea = Math.max(64, Math.round(cellSize * cellSize * 0.05));
const minHoleArea = 12;
for (let i = 0; i < len; i++) {
if (!mask[i] || visited[i]) continue;
componentIndices.length = 0;
let head = 0;
let tail = 0;
queue[tail++] = i;
visited[i] = 1;
let minX = width - 1;
let maxX = 0;
let minY = height - 1;
let maxY = 0;
while (head < tail) {
const current = queue[head++];
componentIndices.push(current);
const x = current % width;
const y = (current / width) | 0;
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
const tryPush = (idx: number) => {
if (!mask[idx] || visited[idx]) return;
visited[idx] = 1;
queue[tail++] = idx;
};
if (x > 0) tryPush(current - 1);
if (x + 1 < width) tryPush(current + 1);
if (y > 0) tryPush(current - width);
if (y + 1 < height) tryPush(current + width);
}
const compArea = componentIndices.length;
if (compArea < minComponentArea) continue;
components += 1;
const regionMinX = Math.max(0, minX - 1);
const regionMinY = Math.max(0, minY - 1);
const regionMaxX = Math.min(width - 1, maxX + 1);
const regionMaxY = Math.min(height - 1, maxY + 1);
const regionW = regionMaxX - regionMinX + 1;
const regionH = regionMaxY - regionMinY + 1;
const regionSize = regionW * regionH;
const regionComp = new Uint8Array(regionSize);
const regionCandidate = new Uint8Array(regionSize);
const regionVisited = new Uint8Array(regionSize);
const regionQueue = new Int32Array(regionSize);
const toLocal = (globalIdx: number) => {
const gx = globalIdx % width;
const gy = (globalIdx / width) | 0;
return (gy - regionMinY) * regionW + (gx - regionMinX);
};
for (const idx of componentIndices) {
const local = toLocal(idx);
regionComp[local] = 1;
}
for (let ry = 0; ry < regionH; ry++) {
const gy = regionMinY + ry;
for (let rx = 0; rx < regionW; rx++) {
const gx = regionMinX + rx;
const globalIdx = gy * width + gx;
if (!interiorMask[globalIdx]) continue;
if (weights && weights[globalIdx] <= 0.5) continue;
regionCandidate[ry * regionW + rx] = 1;
}
}
let rHead = 0;
let rTail = 0;
const markVisited = (local: number) => {
regionVisited[local] = 1;
regionQueue[rTail++] = local;
};
const pushNeighbors = (local: number) => {
const x = local % regionW;
const y = (local / regionW) | 0;
const tryPush = (lx: number, ly: number) => {
if (lx < 0 || ly < 0 || lx >= regionW || ly >= regionH) return;
const idxLocal = ly * regionW + lx;
if (regionVisited[idxLocal]) return;
if (regionComp[idxLocal]) return;
if (!regionCandidate[idxLocal]) return;
markVisited(idxLocal);
};
tryPush(x - 1, y);
tryPush(x + 1, y);
tryPush(x, y - 1);
tryPush(x, y + 1);
};
for (let rx = 0; rx < regionW; rx++) {
const top = rx;
const bottom = (regionH - 1) * regionW + rx;
if (!regionComp[top] && regionCandidate[top] && !regionVisited[top]) markVisited(top);
if (!regionComp[bottom] && regionCandidate[bottom] && !regionVisited[bottom]) markVisited(bottom);
}
for (let ry = 0; ry < regionH; ry++) {
const left = ry * regionW;
const right = ry * regionW + (regionW - 1);
if (!regionComp[left] && regionCandidate[left] && !regionVisited[left]) markVisited(left);
if (!regionComp[right] && regionCandidate[right] && !regionVisited[right]) markVisited(right);
}
while (rHead < rTail) {
const local = regionQueue[rHead++];
pushNeighbors(local);
}
for (let local = 0; local < regionSize; local++) {
if (regionComp[local] || !regionCandidate[local] || regionVisited[local]) continue;
rHead = 0;
rTail = 0;
regionQueue[rTail++] = local;
regionVisited[local] = 1;
const holeLocals: number[] = [];
holeLocals.push(local);
while (rHead < rTail) {
const current = regionQueue[rHead++];
const cx = current % regionW;
const cy = (current / regionW) | 0;
const tryPush = (lx: number, ly: number) => {
if (lx < 0 || ly < 0 || lx >= regionW || ly >= regionH) return;
const idxLocal = ly * regionW + lx;
if (regionVisited[idxLocal]) return;
if (regionComp[idxLocal]) return;
if (!regionCandidate[idxLocal]) return;
regionVisited[idxLocal] = 1;
regionQueue[rTail++] = idxLocal;
holeLocals.push(idxLocal);
};
tryPush(cx - 1, cy);
tryPush(cx + 1, cy);
tryPush(cx, cy - 1);
tryPush(cx, cy + 1);
}
const holeArea = holeLocals.length;
if (holeArea < minHoleArea) continue;
if (holeArea > compArea * maxHoleRatio) continue;
let energySum = 0;
for (const localIdx of holeLocals) {
const gx = regionMinX + (localIdx % regionW);
const gy = regionMinY + ((localIdx / regionW) | 0);
const globalIdx = gy * width + gx;
energySum += edgeEnergy[globalIdx];
}
const meanEnergy = energySum / holeArea;
if (meanEnergy > 0.08) continue;
for (const localIdx of holeLocals) {
const gx = regionMinX + (localIdx % regionW);
const gy = regionMinY + ((localIdx / regionW) | 0);
const globalIdx = gy * width + gx;
if (!mask[globalIdx]) {
mask[globalIdx] = 255;
pixelsFilled += 1;
}
}
holesFilled += 1;
}
}
if (holesFilled === 0 && pixelsFilled === 0) return null;
return { enabled: true, components, holesFilled, pixelsFilled };
}
type SpecularStats = { enabled: boolean; pixels: number };
function applySpecularBridges(
mask: Uint8Array,
width: number,
height: number,
intensity: Uint8Array,
weights: Float32Array | null,
interiorMask: Uint8Array,
gradient: { gx: Float32Array; gy: Float32Array },
edgeEnergy: Float32Array,
): SpecularStats {
const len = mask.length;
if (len === 0) return { enabled: false, pixels: 0 };
let added = 0;
for (let idx = 0; idx < len; idx++) {
if (!interiorMask[idx]) continue;
if (weights && weights[idx] <= 0.5) continue;
if (intensity[idx] < 220) continue;
if (edgeEnergy[idx] > 0.05) continue;
let tx = -gradient.gy[idx];
let ty = gradient.gx[idx];
let norm = Math.hypot(tx, ty);
if (norm < 1e-6) continue;
tx /= norm;
ty /= norm;
const stepX = clampStep(tx);
const stepY = clampStep(ty);
if (stepX === 0 && stepY === 0) continue;
const x = idx % width;
const y = (idx / width) | 0;
const trySet = (sx: number, sy: number) => {
if (sx < 0 || sy < 0 || sx >= width || sy >= height) return;
const id = sy * width + sx;
if (!interiorMask[id]) return;
if (weights && weights[id] <= 0.5) return;
if (!mask[id]) {
mask[id] = 255;
added += 1;
}
};
trySet(x + stepX, y + stepY);
trySet(x - stepX, y - stepY);
}
return { enabled: added > 0, pixels: added };
}
function buildTangentSeedMask(
width: number,
height: number,
interiorMask: Uint8Array,
weights: Float32Array | null,
gradient: { gx: Float32Array; gy: Float32Array },
opts: { coherence?: number; weightThreshold?: number; magnitudeThreshold?: number },
): Uint8Array | null {
const coherenceThreshold = opts.coherence ?? 0.6;
const weightThreshold = opts.weightThreshold ?? 0.45;
const magnitudeThreshold = opts.magnitudeThreshold ?? 22;
const { gx, gy } = gradient;
const len = width * height;
const seeds = new Uint8Array(len);
let seeded = 0;
const radius = 1;
const idx = (x: number, y: number) => y * width + x;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const center = idx(x, y);
if (!interiorMask[center]) continue;
if (weights && weights[center] < weightThreshold) continue;
let sxx = 0;
let sxy = 0;
let syy = 0;
let samples = 0;
for (let dy = -radius; dy <= radius; dy++) {
const yy = y + dy;
if (yy < 0 || yy >= height) continue;
for (let dx = -radius; dx <= radius; dx++) {
const xx = x + dx;
if (xx < 0 || xx >= width) continue;
const p = idx(xx, yy);
const gxp = gx[p];
const gyp = gy[p];
sxx += gxp * gxp;
sxy += gxp * gyp;
syy += gyp * gyp;
samples += 1;
}
}
if (samples === 0) continue;
sxx /= samples;
sxy /= samples;
syy /= samples;
const trace = sxx + syy;
const diff = Math.sqrt(Math.max(0, (sxx - syy) * (sxx - syy) + 4 * sxy * sxy));
const lambda1 = 0.5 * (trace + diff);
const lambda2 = 0.5 * (trace - diff);
const coherence = (lambda1 - lambda2) / (lambda1 + lambda2 + 1e-5);
if (!Number.isFinite(coherence) || coherence < coherenceThreshold) continue;
const gxBase = gx[center];
const gyBase = gy[center];
const magnitude = Math.hypot(gxBase, gyBase);
if (magnitude < magnitudeThreshold) continue;
let orientation = 0.5 * Math.atan2(2 * sxy, sxx - syy);
if (!Number.isFinite(orientation)) orientation = 0;
let tangentAngle = orientation + Math.PI / 2;
let tx = Math.cos(tangentAngle);
let ty = Math.sin(tangentAngle);
let norm = Math.hypot(tx, ty);
if (norm < 1e-6) {
const gradAngle = Math.atan2(gyBase, gxBase);
tx = -Math.sin(gradAngle);
ty = Math.cos(gradAngle);
norm = Math.hypot(tx, ty);
}
if (norm < 1e-6) continue;
tx /= norm;
ty /= norm;
const stepX = clampStep(tx);
const stepY = clampStep(ty);
const negStepX = -stepX;
const negStepY = -stepY;
const setSeed = (sx: number, sy: number) => {
if (sx < 0 || sy < 0 || sx >= width || sy >= height) return;
const id = idx(sx, sy);
if (!interiorMask[id]) return;
if (weights && weights[id] < weightThreshold) return;
if (!seeds[id]) {
seeds[id] = 255;
seeded += 1;
}
};
setSeed(x, y);
if (stepX !== 0 || stepY !== 0) {
setSeed(x + stepX, y + stepY);
setSeed(x + negStepX, y + negStepY);
}
}
}
return seeded > 0 ? seeds : null;
}
function clampStep(value: number): number {
if (value > 0.66) return 1;
if (value < -0.66) return -1;
return 0;
}