diff --git a/Auto-Image.js b/Auto-Image.js
index 226bfea..d959291 100644
--- a/Auto-Image.js
+++ b/Auto-Image.js
@@ -977,6 +977,12 @@
cooldownChargeThreshold: CONFIG.COOLDOWN_CHARGE_THRESHOLD,
overlayOpacity: CONFIG.OVERLAY.OPACITY_DEFAULT,
blueMarbleEnabled: CONFIG.OVERLAY.BLUE_MARBLE_DEFAULT,
+ // Advanced color matching settings
+ colorMatchingAlgorithm: 'lab', // 'lab' | 'legacy'
+ enableChromaPenalty: true,
+ chromaPenaltyWeight: 0.15,
+ customTransparencyThreshold: CONFIG.TRANSPARENCY_THRESHOLD,
+ customWhiteThreshold: CONFIG.WHITE_THRESHOLD,
}
// Placeholder for the resize preview update function
@@ -1688,30 +1694,55 @@
.filter(c => c.rgb)
.map(c => [c.rgb.r, c.rgb.g, c.rgb.b]);
}
-
- // Convert target to Lab once
+ if (state.colorMatchingAlgorithm === 'legacy') {
+ let menorDist = Infinity;
+ let cor = [0, 0, 0];
+ for (let i = 0; i < palette.length; i++) {
+ const [pr, pg, pb] = palette[i];
+ const rmean = (pr + r) / 2;
+ const rdiff = pr - r;
+ const gdiff = pg - g;
+ const bdiff = pb - b;
+ const dist = Math.sqrt(((512 + rmean) * rdiff * rdiff >> 8) + 4 * gdiff * gdiff + ((767 - rmean) * bdiff * bdiff >> 8));
+ if (dist < menorDist) {
+ menorDist = dist;
+ cor = [pr, pg, pb];
+ }
+ }
+ return cor;
+ }
+ // LAB algorithm
const [Lt, at, bt] = Utils._lab(r, g, b);
+ const targetChroma = Math.sqrt(at * at + bt * bt);
let best = null;
let bestDist = Infinity;
- // Iterate palette using cached Lab conversions; use squared distance (no sqrt) for speed
for (let i = 0; i < palette.length; i++) {
const [pr, pg, pb] = palette[i];
const [Lp, ap, bp] = Utils._lab(pr, pg, pb);
const dL = Lt - Lp;
const da = at - ap;
const db = bt - bp;
- const dist = dL * dL + da * da + db * db; // CIE76 squared
+ let dist = dL * dL + da * da + db * db;
+ if (state.enableChromaPenalty && targetChroma > 20) {
+ const candChroma = Math.sqrt(ap * ap + bp * bp);
+ if (candChroma < targetChroma) {
+ const chromaDiff = targetChroma - candChroma;
+ dist += chromaDiff * chromaDiff * state.chromaPenaltyWeight;
+ }
+ }
if (dist < bestDist) {
bestDist = dist;
best = palette[i];
- if (bestDist === 0) break; // Exact match
+ if (bestDist === 0) break;
}
}
return best || [0, 0, 0];
},
- isWhitePixel: (r, g, b) =>
- r >= CONFIG.WHITE_THRESHOLD && g >= CONFIG.WHITE_THRESHOLD && b >= CONFIG.WHITE_THRESHOLD,
+ isWhitePixel: (r, g, b) => {
+ const wt = state.customWhiteThreshold || CONFIG.WHITE_THRESHOLD;
+ return r >= wt && g >= wt && b >= wt;
+ },
createImageUploader: () =>
new Promise((resolve) => {
@@ -2133,51 +2164,52 @@
function findClosestColor(targetRgb, availableColors) {
if (!availableColors || availableColors.length === 0) return 1
-
- const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}`
+ const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}|${state.colorMatchingAlgorithm}|${state.enableChromaPenalty?'c':'nc'}|${state.chromaPenaltyWeight}`
if (colorCache.has(cacheKey)) return colorCache.get(cacheKey)
- // Fast path for near white
- if (targetRgb[0] >= 250 && targetRgb[1] >= 250 && targetRgb[2] >= 250) {
- const whiteEntry = availableColors.find(c => c.rgb[0] >= 250 && c.rgb[1] >= 250 && c.rgb[2] >= 250)
- if (whiteEntry) {
- colorCache.set(cacheKey, whiteEntry.id)
- return whiteEntry.id
- }
+ const whiteThreshold = state.customWhiteThreshold || CONFIG.WHITE_THRESHOLD
+ if (targetRgb[0] >= whiteThreshold && targetRgb[1] >= whiteThreshold && targetRgb[2] >= whiteThreshold) {
+ const whiteEntry = availableColors.find(c => c.rgb[0] >= whiteThreshold && c.rgb[1] >= whiteThreshold && c.rgb[2] >= whiteThreshold)
+ if (whiteEntry) { colorCache.set(cacheKey, whiteEntry.id); return whiteEntry.id }
}
- const [Lt, at, bt] = Utils._lab(targetRgb[0], targetRgb[1], targetRgb[2])
- const targetChroma = Math.sqrt(at * at + bt * bt)
let bestId = availableColors[0].id
let bestScore = Infinity
- for (let i = 0; i < availableColors.length; i++) {
- const c = availableColors[i]
- const [r, g, b] = c.rgb
- const [L2, a2, b2] = Utils._lab(r, g, b)
- const dL = Lt - L2
- const da = at - a2
- const db = bt - b2
- let dist = dL * dL + da * da + db * db // CIE76 squared
- if (targetChroma > 20) {
- const candChroma = Math.sqrt(a2 * a2 + b2 * b2)
- if (candChroma < targetChroma) {
- const chromaDiff = targetChroma - candChroma
- dist += chromaDiff * chromaDiff * 0.15
- }
+ if (state.colorMatchingAlgorithm === 'legacy') {
+ for (let i = 0; i < availableColors.length; i++) {
+ const c = availableColors[i]
+ const [r, g, b] = c.rgb
+ const rmean = (r + targetRgb[0]) / 2
+ const rdiff = r - targetRgb[0]
+ const gdiff = g - targetRgb[1]
+ const bdiff = b - targetRgb[2]
+ const dist = Math.sqrt(((512 + rmean) * rdiff * rdiff >> 8) + 4 * gdiff * gdiff + ((767 - rmean) * bdiff * bdiff >> 8))
+ if (dist < bestScore) { bestScore = dist; bestId = c.id; if (dist === 0) break }
}
- if (dist < bestScore) {
- bestScore = dist
- bestId = c.id
- if (dist === 0) break
+ } else { // lab
+ const [Lt, at, bt] = Utils._lab(targetRgb[0], targetRgb[1], targetRgb[2])
+ const targetChroma = Math.sqrt(at * at + bt * bt)
+ const penaltyWeight = state.enableChromaPenalty ? (state.chromaPenaltyWeight || 0.15) : 0
+ for (let i = 0; i < availableColors.length; i++) {
+ const c = availableColors[i]
+ const [r, g, b] = c.rgb
+ const [L2, a2, b2] = Utils._lab(r, g, b)
+ const dL = Lt - L2, da = at - a2, db = bt - b2
+ let dist = dL * dL + da * da + db * db
+ if (penaltyWeight > 0 && targetChroma > 20) {
+ const candChroma = Math.sqrt(a2 * a2 + b2 * b2)
+ if (candChroma < targetChroma) {
+ const cd = targetChroma - candChroma
+ dist += cd * cd * penaltyWeight
+ }
+ }
+ if (dist < bestScore) { bestScore = dist; bestId = c.id; if (dist === 0) break }
}
}
colorCache.set(cacheKey, bestId)
- if (colorCache.size > 12000) { // prune oldest
- const firstKey = colorCache.keys().next().value
- colorCache.delete(firstKey)
- }
+ if (colorCache.size > 15000) { const firstKey = colorCache.keys().next().value; colorCache.delete(firstKey) }
return bestId
}
@@ -3847,6 +3879,47 @@
+
+
+
+
+ Advanced Color Matching
+
+
+
+
+