Use center weighted sampling and fix pattern border
This commit is contained in:
+1
-1
@@ -79,7 +79,7 @@ input[type="range"] { accent-color: var(--green); }
|
||||
.canvas-viewport { position: relative; width: 100%; aspect-ratio: 1 / 1; height: auto; min-height: 0; flex: 0 0 auto; align-self: stretch; contain: size layout; overflow: hidden; }
|
||||
.canvas-stage { position: absolute; inset: 0; width: auto; height: auto; overflow: scroll; display: block; padding: 12px; cursor: grab; touch-action: none; overscroll-behavior: contain; user-select: none; scrollbar-gutter: stable; }
|
||||
.canvas-stage.is-dragging { cursor: grabbing; }
|
||||
.canvas-wrap { width: max-content; line-height: 0; position: relative; margin: auto; border: 1px solid var(--ink); box-shadow: 10px 10px 0 rgba(32,39,36,.13); background: white; }
|
||||
.canvas-wrap { width: max-content; line-height: 0; position: relative; margin: auto; border: 1px solid var(--ink); background: white; }
|
||||
.canvas-wrap canvas { cursor: inherit; image-rendering: pixelated; }
|
||||
.pixel-tooltip { position: absolute; z-index: 5; width: max-content; max-width: 260px; padding: 9px 11px; display: grid; grid-template-columns: 14px auto; align-items: center; gap: 3px 8px; color: white; background: rgba(22, 30, 27, .94); border: 1px solid rgba(255,255,255,.2); box-shadow: 4px 4px 0 rgba(0,0,0,.2); border-radius: 3px; line-height: 1.35; pointer-events: none; }
|
||||
.tooltip-swatch { width: 14px; height: 14px; grid-row: 1 / 3; border-radius: 50%; border: 1px solid rgba(255,255,255,.6); }
|
||||
|
||||
+22
-14
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user