Guard palette array indexing

This commit is contained in:
lucast committed 2026-10-06 02:45:08 +02:00
1 parent 8f1cc8d4a7
commit a6485e5c73
1 file changed
+16 -13
@@ -73,16 +73,19 @@ export function extractPalette(image: HTMLImageElement, size = 48): AlbumPalette
const pixels = context.getImageData(0, 0, size, size).data const pixels = context.getImageData(0, 0, size, size).data
const bins = new Map<number, { count: number; r: number; g: number; b: number }>() const bins = new Map<number, { count: number; r: number; g: number; b: number }>()
for (let i = 0; i < pixels.length; i += 4) { for (let i = 0; i < pixels.length; i += 4) {
if (pixels[i + 3] < 96) continue const red = pixels[i]!
const r = pixels[i] >> 3 const green = pixels[i + 1]!
const g = pixels[i + 1] >> 3 const blue = pixels[i + 2]!
const b = pixels[i + 2] >> 3 if (pixels[i + 3]! < 96) continue
const r = red >> 3
const g = green >> 3
const b = blue >> 3
const key = (r << 10) | (g << 5) | b const key = (r << 10) | (g << 5) | b
const bin = bins.get(key) ?? { count: 0, r: 0, g: 0, b: 0 } const bin = bins.get(key) ?? { count: 0, r: 0, g: 0, b: 0 }
bin.count++ bin.count++
bin.r += pixels[i] bin.r += red
bin.g += pixels[i + 1] bin.g += green
bin.b += pixels[i + 2] bin.b += blue
bins.set(key, bin) bins.set(key, bin)
} }
const entries = [...bins.values()].map((bin) => ({ const entries = [...bins.values()].map((bin) => ({
@@ -99,7 +102,7 @@ export function extractPalette(image: HTMLImageElement, size = 48): AlbumPalette
} }
const centers: RGB[] = [entries.reduce((best, entry) => entry.count > best.count ? entry : best).color] const centers: RGB[] = [entries.reduce((best, entry) => entry.count > best.count ? entry : best).color]
while (centers.length < 4) { while (centers.length < 4) {
let candidate = entries[0] let candidate = entries[0]!
let bestScore = -1 let bestScore = -1
for (const entry of entries) { for (const entry of entries) {
const nearestDistance = Math.min(...centers.map((center) => distance(entry.color, center))) const nearestDistance = Math.min(...centers.map((center) => distance(entry.color, center)))
@@ -119,30 +122,30 @@ export function extractPalette(image: HTMLImageElement, size = 48): AlbumPalette
let nearest = 0 let nearest = 0
let nearestDistance = Infinity let nearestDistance = Infinity
for (let i = 0; i < centers.length; i++) { for (let i = 0; i < centers.length; i++) {
const nextDistance = distance(entry.color, centers[i]) const nextDistance = distance(entry.color, centers[i]!)
if (nextDistance < nearestDistance) { if (nextDistance < nearestDistance) {
nearest = i nearest = i
nearestDistance = nextDistance nearestDistance = nextDistance
} }
} }
const cluster = clusters[nearest] const cluster = clusters[nearest]!
cluster.count += entry.count cluster.count += entry.count
cluster.red += entry.color[0] * entry.count cluster.red += entry.color[0] * entry.count
cluster.green += entry.color[1] * entry.count cluster.green += entry.color[1] * entry.count
cluster.blue += entry.color[2] * entry.count cluster.blue += entry.color[2] * entry.count
} }
for (let i = 0; i < clusters.length; i++) { for (let i = 0; i < clusters.length; i++) {
const cluster = clusters[i] const cluster = clusters[i]!
if (cluster.count) centers[i] = [cluster.red / cluster.count, cluster.green / cluster.count, cluster.blue / cluster.count] if (cluster.count) centers[i] = [cluster.red / cluster.count, cluster.green / cluster.count, cluster.blue / cluster.count]
} }
clusterSupport = clusters.map((cluster) => cluster.count) clusterSupport = clusters.map((cluster) => cluster.count)
} }
const dominantIndex = clusterSupport.reduce( const dominantIndex = clusterSupport.reduce(
(best, count, index) => count > clusterSupport[best] ? index : best, (best, count, index) => count > clusterSupport[best]! ? index : best,
0, 0,
) )
const dominant = centers[dominantIndex] const dominant = centers[dominantIndex]!
const distinctCenters = centers.filter((center) => distance(center, dominant) > 0.002) const distinctCenters = centers.filter((center) => distance(center, dominant) > 0.002)
const candidates = distinctCenters.length ? distinctCenters : centers const candidates = distinctCenters.length ? distinctCenters : centers
const accent = candidates.reduce((best, color) => { const accent = candidates.reduce((best, color) => {