package library import ( "fmt" "math" "sort" ) // Palette выбирает 3 цвета обложки по пикселям RGB (rgb24): доминирующий, второй и акцент — // самый насыщенный из оставшихся. k-means с детерминированной инициализацией по яркости, // чтобы одна и та же обложка всегда давала одну палитру. func Palette(rgb []byte) []string { n := len(rgb) / 3 if n == 0 { return nil } px := make([][3]float64, n) for i := range n { px[i] = [3]float64{float64(rgb[3*i]), float64(rgb[3*i+1]), float64(rgb[3*i+2])} } const k = 4 // Начальные центры — квантили по яркости. order := make([]int, n) for i := range order { order[i] = i } luma := func(p [3]float64) float64 { return 0.299*p[0] + 0.587*p[1] + 0.114*p[2] } sort.SliceStable(order, func(a, b int) bool { return luma(px[order[a]]) < luma(px[order[b]]) }) centers := make([][3]float64, k) for c := range k { centers[c] = px[order[(2*c+1)*n/(2*k)]] } assign := make([]int, n) for range 12 { var sum [k][3]float64 var cnt [k]int for i, p := range px { best, bestD := 0, math.MaxFloat64 for c, ctr := range centers { d := sq(p[0]-ctr[0]) + sq(p[1]-ctr[1]) + sq(p[2]-ctr[2]) if d < bestD { best, bestD = c, d } } assign[i] = best cnt[best]++ for j := range 3 { sum[best][j] += p[j] } } for c := range k { if cnt[c] > 0 { for j := range 3 { centers[c][j] = sum[c][j] / float64(cnt[c]) } } } } var size [k]int for _, a := range assign { size[a]++ } idx := []int{} for c := range k { if size[c] > 0 { idx = append(idx, c) } } sort.SliceStable(idx, func(a, b int) bool { return size[idx[a]] > size[idx[b]] }) for len(idx) < 3 { idx = append(idx, idx[len(idx)-1]) } dominant, second, rest := idx[0], idx[1], idx[2:] accent := rest[0] for _, c := range rest { if saturation(centers[c]) > saturation(centers[accent]) { accent = c } } return []string{hexColor(centers[dominant]), hexColor(centers[second]), hexColor(centers[accent])} } func sq(x float64) float64 { return x * x } func saturation(p [3]float64) float64 { hi := math.Max(p[0], math.Max(p[1], p[2])) lo := math.Min(p[0], math.Min(p[1], p[2])) if hi == 0 { return 0 } return (hi - lo) / hi } func hexColor(p [3]float64) string { c := func(v float64) int { return int(math.Round(math.Max(0, math.Min(255, v)))) } return fmt.Sprintf("#%02X%02X%02X", c(p[0]), c(p[1]), c(p[2])) }