-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathcolorcutquantizer.go
88 lines (81 loc) · 2.64 KB
/
colorcutquantizer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package vibrant
import "container/heap"
const (
blackMaxLightness float64 = 0.05
whiteMinLightness float64 = 0.95
)
// A color quantizer based on the Median-cut algorithm, optimized for
// picking out distinct colors rather than representation colors.
//
// The color space is represented as a 3-dimensional cube with each
// dimension being an RGB component. The cube is then repeatedly divided
// until we have reduced the color space to the requested number of colors.
// An average color is then generated from each cube.
//
// Whereas median-cut divides cubes so they all have roughly the same
// population, this quantizer divides boxes based on their color volume.
type colorCutQuantizer struct {
Colors []int
ColorPopulations map[int]int
QuantizedColors []*Swatch
}
// true if the color is close to pure black, pure white, or
// "the red side of the I line" which I believe is Google-speak for
// "that particular shade of red which occurs in the red-eye effect"
// see enwp.org/Red-eye_effect
func shouldIgnoreColor(color int) bool {
h, s, l := rgbToHsl(color)
return l <= blackMaxLightness || l >= whiteMinLightness || (h >= 0.0278 && h <= 0.1028 && s <= 0.82)
}
func shouldIgnoreColorSwatch(sw *Swatch) bool {
return shouldIgnoreColor(int(sw.Color))
}
func newColorCutQuantizer(bitmap bitmap, maxColors int) *colorCutQuantizer {
pixels := bitmap.Pixels()
histo := newColorHistogram(pixels)
colorPopulations := make(map[int]int, histo.NumberColors)
for i, c := range histo.Colors {
colorPopulations[c] = histo.ColorCounts[i]
}
validColors := make([]int, 0)
i := 0
for _, c := range histo.Colors {
if !shouldIgnoreColor(c) {
validColors = append(validColors, c)
i++
}
}
validCount := len(validColors)
ccq := &colorCutQuantizer{Colors: validColors, ColorPopulations: colorPopulations}
if validCount <= maxColors {
// note: no quantization actually occurs
for _, c := range validColors {
ccq.QuantizedColors = append(ccq.QuantizedColors, &Swatch{Color: Color(c), Population: colorPopulations[c]})
}
} else {
ccq.quantizePixels(validCount-1, maxColors)
}
return ccq
}
// see also vbox.go
func (ccq *colorCutQuantizer) quantizePixels(maxColorIndex, maxColors int) {
pq := make(priorityQueue, 0)
heap.Init(&pq)
heap.Push(&pq, newVbox(0, maxColorIndex, ccq.Colors, ccq.ColorPopulations))
for pq.Len() < maxColors {
v := heap.Pop(&pq).(*vbox)
if v.CanSplit() {
heap.Push(&pq, v.Split())
heap.Push(&pq, v)
} else {
break
}
}
for pq.Len() > 0 {
v := heap.Pop(&pq).(*vbox)
swatch := v.AverageColor()
if !shouldIgnoreColorSwatch(swatch) {
ccq.QuantizedColors = append(ccq.QuantizedColors, swatch)
}
}
}