Expand preview and center dominant sampling

This commit is contained in:
wuyanwanwu
2026-08-14 01:11:38 +08:00
parent 5a312f5039
commit 552ed5e6fa
15 changed files with 30 additions and 26 deletions
+5 -5
View File
@@ -76,8 +76,8 @@ input[type="range"] { accent-color: var(--green); }
.zoom-control button { width: 36px; height: 36px; border: 1px solid var(--line); background: white; cursor: pointer; font-size: 20px; }
.zoom-control input { width: 95px; }
.zoom-control output { width: 38px; color: var(--muted); font-size: 11px; text-align: right; }
.canvas-viewport { position: relative; width: min(100%, 82vh, 1000px); aspect-ratio: 1 / 1; height: auto; min-height: 0; flex: 0 0 auto; align-self: center; contain: size layout; overflow: hidden; }
.canvas-stage { position: absolute; inset: 0; width: auto; height: auto; overflow: scroll; display: block; padding: 36px; cursor: grab; touch-action: none; overscroll-behavior: contain; user-select: none; scrollbar-gutter: stable; }
.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 canvas { cursor: inherit; image-rendering: pixelated; }
@@ -161,8 +161,8 @@ input[type="range"] { accent-color: var(--green); }
.pattern-toolbar { align-items: flex-start; flex-direction: column; padding: 20px; }
.zoom-control { width: 100%; }
.zoom-control input { flex: 1; }
.canvas-viewport { width: min(100%, 82vh); height: auto; min-height: 0; aspect-ratio: 1 / 1; flex-basis: auto; }
.canvas-stage { display: block; padding: 24px; }
.canvas-viewport { width: 100%; height: auto; min-height: 0; aspect-ratio: 1 / 1; flex-basis: auto; }
.canvas-stage { display: block; padding: 10px; }
.canvas-wrap { margin: auto; }
.pattern-footer { align-items: flex-start; flex-wrap: wrap; padding: 14px 18px; }
.view-options { width: 100%; margin: 5px 0 0; justify-content: space-between; }
@@ -182,7 +182,7 @@ input[type="range"] { accent-color: var(--green); }
@media (max-width: 370px) {
.view-options { gap: 6px; }
.view-options label { font-size: 10px; }
.canvas-stage { padding: 16px; }
.canvas-stage { padding: 8px; }
}
@media (prefers-reduced-motion: reduce) { * { scroll-behavior: auto !important; transition: none !important; } }
+8 -4
View File
@@ -214,7 +214,9 @@ function sampleDominantRegions(
cropX: number,
cropY: number,
) {
const scale = 4;
// 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;
const sample = document.createElement("canvas");
sample.width = width * scale;
sample.height = height * scale;
@@ -228,7 +230,7 @@ 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 }> }> = [];
const groups: Array<{ count: number; lab: Oklab; samples: Array<{ rgb: [number, number, number]; lab: Oklab }>; containsCenter: boolean }> = [];
let transparentCount = 0;
for (let offsetY = 0; offsetY < scale; offsetY++) {
for (let offsetX = 0; offsetX < scale; offsetX++) {
@@ -239,6 +241,7 @@ function sampleDominantRegions(
}
const rgb: [number, number, number] = [source[sourceIndex], source[sourceIndex + 1], source[sourceIndex + 2]];
const lab = rgbToOklab(rgb);
const isCenter = offsetX === Math.floor(scale / 2) && offsetY === Math.floor(scale / 2);
let closest: (typeof groups)[number] | undefined;
let closestDistance = Number.POSITIVE_INFINITY;
for (const group of groups) {
@@ -253,8 +256,9 @@ function sampleDominantRegions(
closest.lab = closest.lab.map((value, channel) => (value * closest.count + lab[channel]) / total) as Oklab;
closest.count = total;
closest.samples.push({ rgb, lab });
closest.containsCenter ||= isCenter;
} else {
groups.push({ count: 1, lab: [...lab], samples: [{ rgb, lab }] });
groups.push({ count: 1, lab: [...lab], samples: [{ rgb, lab }], containsCenter: isCenter });
}
}
}
@@ -264,7 +268,7 @@ function sampleDominantRegions(
confidence[row * width + column] = transparentCount / (scale * scale);
continue;
}
const dominant = groups.sort((a, b) => b.count - a.count)[0];
const dominant = groups.sort((a, b) => b.count - a.count || 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,
);