forked from sspencer/colorart
-
Notifications
You must be signed in to change notification settings - Fork 0
/
colorart.go
154 lines (126 loc) · 3.61 KB
/
colorart.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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package colorart
import "image"
const (
// 1 to examine every pixel, 2 to skip every other pixel
loopSkipper = 2
// detune colors so colors within a few pixels of each other
// map to the same color. Makes algorthim much faster.
// 0 is no change.
// 1 divides by 2, multiplies by 2
// 2 divides by 4, multiplies by 4
// 3 divides by 8, multiplies by 8
// Don't go much beyond 3...
colorShifter = 2
)
type colorArt struct {
img *pixelGetter
}
// Analyze an image for its main colors.
func Analyze(img image.Image) (backgroundColor, primaryColor, secondaryColor, detailColor Color) {
c := &colorArt{}
c.img = newPixelGetter(img)
backgroundColor = c.findEdgeColor()
primaryColor, secondaryColor, detailColor = c.findTextColors(backgroundColor)
darkBackground := backgroundColor.IsDarkColor()
if !primaryColor.set {
if darkBackground {
primaryColor = WhiteColor
} else {
primaryColor = BlackColor
}
}
if !secondaryColor.set {
if darkBackground {
secondaryColor = WhiteColor
} else {
secondaryColor = BlackColor
}
}
if !detailColor.set {
if darkBackground {
detailColor = WhiteColor
} else {
detailColor = BlackColor
}
}
return
}
func (c *colorArt) findTextColors(backgroundColor Color) (primaryColor, secondaryColor, detailColor Color) {
b := c.img.imgBounds
imageColors := parallelize(b.Min.Y, b.Max.Y, func(ch chan CountedSet, pmin, pmax int) {
b := c.img.imgBounds
colors := NewCountedSet(10000)
for y := pmin; y < pmax; y += loopSkipper {
for x := b.Min.X; x < b.Max.X; x += loopSkipper {
colors.AddPixel(c.img.getPixel(x, y))
}
}
ch <- colors
})
useDarkTextColor := !backgroundColor.IsDarkColor()
selectColors := NewCountedSet(5000)
for key, cnt := range imageColors {
// don't bother unless there's more than a few of the same color
curColor := rgbToColor(key).ColorWithMinimumSaturation(0.15)
if curColor.IsDarkColor() == useDarkTextColor {
selectColors.AddCount(key, cnt)
}
}
sortedColors := selectColors.SortedSet()
for _, e := range sortedColors {
curColor := rgbToColor(e.Color)
if !primaryColor.set {
if curColor.IsContrastingColor(backgroundColor) {
primaryColor = curColor
}
} else if !secondaryColor.set {
if !primaryColor.IsDistinctColor(curColor) || !curColor.IsContrastingColor(backgroundColor) {
continue
}
secondaryColor = curColor
} else if !detailColor.set {
if !secondaryColor.IsDistinctColor(curColor) ||
!primaryColor.IsDistinctColor(curColor) ||
!curColor.IsContrastingColor(backgroundColor) {
continue
}
detailColor = curColor
}
if primaryColor.set && secondaryColor.set && detailColor.set {
break
}
}
return
}
func (c *colorArt) findEdgeColor() Color {
edgeColors := NewCountedSet(500)
b := c.img.imgBounds
x0 := b.Min.X
x1 := b.Max.X - 1
for y := b.Min.Y; y < b.Max.Y; y++ {
edgeColors.AddPixel(c.img.getPixel(x0, y))
edgeColors.AddPixel(c.img.getPixel(x1, y))
}
sortedColors := edgeColors.SortedSet()
proposedEntry := sortedColors[0]
proposedColor := rgbToColor(proposedEntry.Color)
// try another color if edge is close to black or white
if proposedColor.IsBlackOrWhite() {
for i, e := range sortedColors {
if i == 0 {
// first entry is already set as "proposedEntry"
continue
}
nextProposedEntry := e
// make sure second choice is 30% as common as first choice
if float32(nextProposedEntry.Count)/float32(proposedEntry.Count) > 0.3 {
nextProposedColor := rgbToColor(nextProposedEntry.Color)
if !nextProposedColor.IsBlackOrWhite() {
proposedColor = nextProposedColor
break
}
}
}
}
return proposedColor
}