Use center weighted sampling and fix pattern border

This commit is contained in:
wuyanwanwu
2026-08-14 01:18:23 +08:00
parent a5d4850728
commit 1bd885f5b0
15 changed files with 40 additions and 32 deletions
+22 -14
View File
@@ -253,9 +253,15 @@ function sampleDominantRegions(
cropX: number,
cropY: number,
) {
// An odd sampling grid has a real center point and avoids directional ties
// at object boundaries (the former 4x4 grid favored one side on exact ties).
const scale = 5;
// Center-weighted 3x3 sampling preserves geometric corners better than a
// flat vote: center=4, orthogonal neighbors=2, diagonal neighbors=1.
const scale = 3;
const sampleWeights = [
[1, 2, 1],
[2, 4, 2],
[1, 2, 1],
];
const totalSampleWeight = 16;
const sample = document.createElement("canvas");
sample.width = width * scale;
sample.height = height * scale;
@@ -269,13 +275,14 @@ function sampleDominantRegions(
for (let row = 0; row < height; row++) {
for (let column = 0; column < width; column++) {
const groups: Array<{ count: number; lab: Oklab; samples: Array<{ rgb: [number, number, number]; lab: Oklab }>; containsCenter: boolean }> = [];
let transparentCount = 0;
const groups: Array<{ count: number; weight: number; lab: Oklab; samples: Array<{ rgb: [number, number, number]; lab: Oklab }>; containsCenter: boolean }> = [];
let transparentWeight = 0;
for (let offsetY = 0; offsetY < scale; offsetY++) {
for (let offsetX = 0; offsetX < scale; offsetX++) {
const sourceIndex = ((row * scale + offsetY) * sample.width + column * scale + offsetX) * 4;
const sampleWeight = sampleWeights[offsetY][offsetX];
if (source[sourceIndex + 3] < TRANSPARENT_ALPHA_THRESHOLD) {
transparentCount += 1;
transparentWeight += sampleWeight;
continue;
}
const rgb: [number, number, number] = [source[sourceIndex], source[sourceIndex + 1], source[sourceIndex + 2]];
@@ -291,23 +298,24 @@ function sampleDominantRegions(
}
}
if (closest && closestDistance <= localMergeThreshold) {
const total = closest.count + 1;
closest.lab = closest.lab.map((value, channel) => (value * closest.count + lab[channel]) / total) as Oklab;
closest.count = total;
const totalWeight = closest.weight + sampleWeight;
closest.lab = closest.lab.map((value, channel) => (value * closest.weight + lab[channel] * sampleWeight) / totalWeight) as Oklab;
closest.weight = totalWeight;
closest.count += 1;
closest.samples.push({ rgb, lab });
closest.containsCenter ||= isCenter;
} else {
groups.push({ count: 1, lab: [...lab], samples: [{ rgb, lab }], containsCenter: isCenter });
groups.push({ count: 1, weight: sampleWeight, lab: [...lab], samples: [{ rgb, lab }], containsCenter: isCenter });
}
}
}
const targetIndex = (row * width + column) * 4;
if (transparentCount >= scale * scale / 2 || groups.length === 0) {
if (transparentWeight >= totalSampleWeight / 2 || groups.length === 0) {
result[targetIndex + 3] = 0;
confidence[row * width + column] = transparentCount / (scale * scale);
confidence[row * width + column] = transparentWeight / totalSampleWeight;
continue;
}
const dominant = groups.sort((a, b) => b.count - a.count || Number(b.containsCenter) - Number(a.containsCenter))[0];
const dominant = groups.sort((a, b) => b.weight - a.weight || Number(b.containsCenter) - Number(a.containsCenter))[0];
const representative = dominant.samples.reduce((best, current) =>
oklabDistance(current.lab, dominant.lab) < oklabDistance(best.lab, dominant.lab) ? current : best,
);
@@ -315,7 +323,7 @@ function sampleDominantRegions(
result[targetIndex + 1] = representative.rgb[1];
result[targetIndex + 2] = representative.rgb[2];
result[targetIndex + 3] = 255;
confidence[row * width + column] = dominant.count / Math.max(1, scale * scale - transparentCount);
confidence[row * width + column] = dominant.weight / Math.max(1, totalSampleWeight - transparentWeight);
}
}
return { data: result, confidence };