├── README.md
├── main.go
└── output.go
/README.md:
--------------------------------------------------------------------------------
1 | Terminal Colorscheme Generator
2 | ==============================
3 |
4 | # Please note:
5 |
6 | If you have no strong reason to use this version specifically, please instead use [schemer2](https://github.com/thefryscorer/schemer2). It has many more features, and a smarter interface. Any new features added will be added to schemer2.
7 |
8 | The reason schemer2 is a separate project is to support the AUR package (Now removed), and not break the existing workflow of anybody who has incorporated schemer into scripts or other programs. Additionally, schemer2 does not feature the SDL rendered preview.
9 |
10 | ## Screenshot
11 | 
12 |
13 | ## Installation
14 |
15 | ### Short version
16 |
17 | > go get github.com/thefryscorer/schemer
18 |
19 | ### Long Version
20 |
21 | #### Installing and configuring Go
22 | To build this program, you will need to have Go installed and properly configured. After installing the Go package, you will need to configure a GOPATH. This is a directory in which Go will keep its programs and source files. I recommend making the GOPATH directory in your home folder. If your GOPATH is in your root directory a kitten will die.
23 |
24 | > mkdir ~/Go
25 |
26 | You will also need to set the GOPATH variable so that Go knows where to put things. You can do this by running:
27 |
28 | > export GOPATH=$HOME/Go
29 |
30 | NOTE: You don't need to (and shouldn't) set the $GOROOT variable. This is handled for you and you shouldn't mess with it.
31 |
32 | #### Installing SDL1.2
33 | This program also makes use of SDL1.2 for the color preview window (which you can use by adding the "-d" flag in schemer). As such, SDL1.2 will need to be installed on your system for you to build and run schemer.
34 |
35 | **To install SDL1.2 in ArchLinux:**
36 |
37 | > sudo pacman -S sdl sdl_image sdl_ttf sdl_mixer
38 |
39 | **To install SDL1.2 in Mac OS:**
40 |
41 | > brew install sdl sdl_image sdl_ttf sdl_mixer
42 |
43 | If you haven't installed HomeBrew, you can find it at http://brew.sh
44 |
45 |
46 | **To install SDL1.2 on another system:**
47 |
48 | Learn how to use Google and your package manager. Both are very useful.
49 |
50 | #### Installing schemer
51 | You should now be able to install schemer using the command:
52 |
53 | > go get github.com/thefryscorer/schemer
54 |
55 | And it will be built in your GOPATH directory, in a subdirectory named 'bin'. To run it, you can either add $HOME/Go/bin to your system path and run it as you would any other command. Or cd into the bin directory and run it with:
56 |
57 | > ./schemer
58 |
59 | ## Usage
60 |
61 | > schemer -term="xfce" Image.png
62 |
63 | Then copy the generated config lines into your terminal config file.
64 |
65 | ## Features
66 |
67 | - Outputs configuration in several different formats for different terminals.
68 | - Configurable color difference threshold
69 | - Configurable minimum and maximum brightness value
70 | - Can preview colorscheme in SDL window
71 |
72 | ## Supported output formats
73 |
74 | - Colours in just plain text (default)
75 | - Konsole
76 | - xterm/rxvt/aterm
77 | - urxvt
78 | - iTerm2
79 | - XFCE Terminal
80 | - Roxterm
81 | - LilyTerm
82 | - Terminator
83 | - Chrome Shell
84 | - OS X Terminal
85 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "image"
7 | "image/color"
8 | _ "image/jpeg"
9 | _ "image/png"
10 | "io/ioutil"
11 | "log"
12 | "os"
13 | "strings"
14 |
15 | "github.com/jqln-0/colorshow"
16 | )
17 |
18 | func loadImage(filepath string) image.Image {
19 | infile, err := os.Open(filepath)
20 | if err != nil {
21 | log.Panic(err)
22 | }
23 | defer infile.Close()
24 |
25 | src, _, err := image.Decode(infile)
26 | if err != nil {
27 | log.Panic(err)
28 | }
29 | return src
30 | }
31 |
32 | func abs(n int) int {
33 | if n >= 0 {
34 | return n
35 | }
36 | return -n
37 | }
38 |
39 | func colorDifference(col1 color.Color, col2 color.Color, threshold int) bool {
40 | c1 := col1.(color.NRGBA)
41 | c2 := col2.(color.NRGBA)
42 |
43 | rDiff := abs(int(c1.R) - int(c2.R))
44 | gDiff := abs(int(c1.G) - int(c2.G))
45 | bDiff := abs(int(c1.B) - int(c2.B))
46 |
47 | total := rDiff + gDiff + bDiff
48 | return total >= threshold
49 | }
50 |
51 | func getDistinctColors(colors []color.Color, threshold int, minBrightness, maxBrightness int) []color.Color {
52 | distinctColors := make([]color.Color, 0)
53 | for _, c := range colors {
54 | same := false
55 | if !colorDifference(c, color.NRGBAModel.Convert(color.Black), minBrightness*3) {
56 | continue
57 | }
58 | if !colorDifference(c, color.NRGBAModel.Convert(color.White), (255-maxBrightness)*3) {
59 | continue
60 | }
61 | for _, k := range distinctColors {
62 | if !colorDifference(c, k, threshold) {
63 | same = true
64 | break
65 | }
66 | }
67 | if !same {
68 | distinctColors = append(distinctColors, c)
69 | }
70 | }
71 | return distinctColors
72 | }
73 | func usage() {
74 | fmt.Fprintln(os.Stderr, "Usage: colorscheme [flags] -term=\"terminal format\" imagepath")
75 | flag.PrintDefaults()
76 | os.Exit(2)
77 | }
78 |
79 | func main() {
80 | terminalSupport := "Terminal format to output colors as. Currently supported: \n"
81 | for _, t := range terminals {
82 | terminalSupport += strings.Join([]string{" ", t.friendlyName, ":", t.flagName, "\n"}, " ")
83 | }
84 | var (
85 | threshold = flag.Int("t", 50, "Threshold for minimum color difference")
86 | display = flag.Bool("d", false, "Display colors in SDL window")
87 | terminal = flag.String("term", "default", terminalSupport)
88 | minBrightness = flag.Int("minBright", 50, "Minimum brightness for colors")
89 | maxBrightness = flag.Int("maxBright", 200, "Maximum brightness for colors")
90 | debug = flag.Bool("debug", false, "Show debugging messages")
91 | )
92 | flag.Usage = usage
93 | flag.Parse()
94 | if len(flag.Args()) < 1 {
95 | usage()
96 | os.Exit(2)
97 | }
98 | if *minBrightness > 255 || *maxBrightness > 255 {
99 | fmt.Print("Minimum and maximum brightness must be an integer between 0 and 255.\n")
100 | os.Exit(2)
101 | }
102 | if *threshold > 255 {
103 | fmt.Print("Threshold should be an integer between 0 and 255.\n")
104 | os.Exit(2)
105 | }
106 | if !*debug {
107 | log.SetOutput(ioutil.Discard)
108 | }
109 |
110 | // Load the image and create array of colors
111 | fuzzyness := 5
112 | img := loadImage(flag.Args()[0])
113 | w, h := img.Bounds().Max.X, img.Bounds().Max.Y
114 | colors := make([]color.Color, 0, w*h)
115 | for x := 0; x < w; x += fuzzyness {
116 | for y := 0; y < h; y += fuzzyness {
117 | col := color.NRGBAModel.Convert(img.At(x, y))
118 | colors = append(colors, col)
119 | }
120 | }
121 | // Get the distinct colors from the array by comparing differences with a threshold
122 | distinctColors := getDistinctColors(colors, *threshold, *minBrightness, *maxBrightness)
123 |
124 | // Ensure there are 16 colors
125 | count := 0
126 | for len(distinctColors) < 16 {
127 | count++
128 | distinctColors = append(distinctColors, getDistinctColors(colors, *threshold-count, *minBrightness, *maxBrightness)...)
129 | if count == *threshold {
130 | fmt.Print("Could not get colors from image with settings specified. Aborting.\n")
131 | os.Exit(1)
132 | }
133 | }
134 |
135 | if len(distinctColors) > 16 {
136 | distinctColors = distinctColors[:16]
137 | }
138 |
139 | if *display {
140 | colorshow.DisplaySwatches(distinctColors)
141 | }
142 |
143 | // Output the configuration specified
144 | terminalMatch := false
145 | for _, t := range terminals {
146 | if *terminal == t.flagName {
147 | fmt.Print(t.output(distinctColors))
148 | terminalMatch = true
149 | break
150 | }
151 | }
152 | if !terminalMatch {
153 | fmt.Printf("Did not recognise format %v. \n", *terminal)
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/output.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/base64"
5 | "encoding/hex"
6 | "fmt"
7 | "image/color"
8 | "strconv"
9 | )
10 |
11 | type outputFunction (func([]color.Color) string)
12 |
13 | type Terminal struct {
14 | friendlyName string
15 | flagName string
16 | output outputFunction
17 | }
18 |
19 | // Terminals are defined here
20 | var terminals = []Terminal{
21 | {
22 | friendlyName: "Default output (colors only)",
23 | flagName: "default",
24 | output: printColors,
25 | },
26 | {
27 | friendlyName: "XFCE4Terminal",
28 | flagName: "xfce",
29 | output: printXfce,
30 | },
31 | {
32 | friendlyName: "LilyTerm",
33 | flagName: "lilyterm",
34 | output: printLilyTerm,
35 | },
36 | {
37 | friendlyName: "Termite",
38 | flagName: "termite",
39 | output: printTermite,
40 | },
41 | {
42 | friendlyName: "Terminator",
43 | flagName: "terminator",
44 | output: printTerminator,
45 | },
46 | {
47 | friendlyName: "ROXTerm",
48 | flagName: "roxterm",
49 | output: printRoxTerm,
50 | },
51 | {
52 | friendlyName: "rxvt/xterm/aterm",
53 | flagName: "xterm",
54 | output: printXterm,
55 | },
56 | {
57 | friendlyName: "Konsole",
58 | flagName: "konsole",
59 | output: printKonsole,
60 | },
61 | {
62 | friendlyName: "iTerm2",
63 | flagName: "iterm2",
64 | output: printITerm2,
65 | },
66 | {
67 | friendlyName: "urxvt",
68 | flagName: "urxvt",
69 | output: printURxvt,
70 | },
71 | {
72 | friendlyName: "Chrome Shell",
73 | flagName: "chrome",
74 | output: printChrome,
75 | },
76 | {
77 | friendlyName: "OS X Terminal",
78 | flagName: "osxterminal",
79 | output: printOSXTerminal,
80 | },
81 | }
82 |
83 | func printXfce(colors []color.Color) string {
84 | output := ""
85 | output += "ColorPalette="
86 | for _, c := range colors {
87 | bytes := []byte{byte(c.(color.NRGBA).R), byte(c.(color.NRGBA).R), byte(c.(color.NRGBA).G), byte(c.(color.NRGBA).G), byte(c.(color.NRGBA).B), byte(c.(color.NRGBA).B)}
88 | output += "#"
89 | output += hex.EncodeToString(bytes)
90 | output += ";"
91 | }
92 | output += "\n"
93 |
94 | return output
95 | }
96 |
97 | func printLilyTerm(colors []color.Color) string {
98 | output := ""
99 | for i, c := range colors {
100 | cc := c.(color.NRGBA)
101 | bytes := []byte{byte(cc.R), byte(cc.G), byte(cc.B)}
102 | output += "Color"
103 | output += strconv.Itoa(i)
104 | output += " = "
105 | output += "#"
106 | output += hex.EncodeToString(bytes)
107 | output += "\n"
108 | }
109 | return output
110 | }
111 |
112 | func printTermite(colors []color.Color) string {
113 | output := ""
114 | for i, c := range colors {
115 | cc := c.(color.NRGBA)
116 | bytes := []byte{byte(cc.R), byte(cc.G), byte(cc.B)}
117 | output += "color"
118 | output += strconv.Itoa(i)
119 | output += " = "
120 | output += "#"
121 | output += hex.EncodeToString(bytes)
122 | output += "\n"
123 | }
124 | return output
125 | }
126 |
127 | func printTerminator(colors []color.Color) string {
128 | output := "palette = \""
129 | for i, c := range colors {
130 | cc := c.(color.NRGBA)
131 | bytes := []byte{byte(cc.R), byte(cc.G), byte(cc.B)}
132 | if i < len(colors)-1 {
133 | output += "#"
134 | output += hex.EncodeToString(bytes)
135 | output += ":"
136 | } else if i == len(colors)-1 {
137 | output += "#"
138 | output += hex.EncodeToString(bytes)
139 | output += "\"\n"
140 | }
141 | }
142 | return output
143 | }
144 |
145 | func printXterm(colors []color.Color) string {
146 | output := ""
147 | output += "! Terminal colors"
148 | output += "\n"
149 | for i, c := range colors {
150 | cc := c.(color.NRGBA)
151 | bytes := []byte{byte(cc.R), byte(cc.G), byte(cc.B)}
152 | output += "*color"
153 | output += strconv.Itoa(i)
154 | output += ": #"
155 | output += hex.EncodeToString(bytes)
156 | output += "\n"
157 | }
158 |
159 | return output
160 | }
161 |
162 | func printKonsole(colors []color.Color) string {
163 | output := ""
164 | for i, c := range colors {
165 | cc := c.(color.NRGBA)
166 | output += "[Color"
167 | if i > 7 {
168 | output += strconv.Itoa(i - 8)
169 | output += "Intense"
170 | } else {
171 | output += strconv.Itoa(i)
172 | }
173 | output += "]\n"
174 | output += "Color="
175 | output += strconv.Itoa(int(cc.R)) + ","
176 | output += strconv.Itoa(int(cc.G)) + ","
177 | output += strconv.Itoa(int(cc.B)) + "\n"
178 | output += "Transparency=false\n\n"
179 | }
180 |
181 | return output
182 | }
183 |
184 | func printRoxTerm(colors []color.Color) string {
185 | output := "[roxterm colour scheme]\n"
186 | output += "pallete_size=16\n"
187 |
188 | for i, c := range colors {
189 | cc := c.(color.NRGBA)
190 | bytes := []byte{byte(cc.R), byte(cc.G), byte(cc.B)}
191 | output += "color"
192 | output += strconv.Itoa(i)
193 | output += " = "
194 | output += "#"
195 | output += hex.EncodeToString(bytes)
196 | output += "\n"
197 | }
198 |
199 | return output
200 | }
201 |
202 | func printITerm2(colors []color.Color) string {
203 | output := "\n"
204 | output += "\n"
205 | output += "\n"
206 | output += "\n"
207 | for i, c := range colors {
208 | cc := c.(color.NRGBA)
209 | output += "\tAnsi "
210 | output += strconv.Itoa(i)
211 | output += " Color\n"
212 | output += "\t\n"
213 | output += "\t\tBlue Component\n"
214 | output += "\t\t"
215 | output += strconv.FormatFloat(float64(cc.B)/255, 'f', 17, 64)
216 | output += "\n"
217 | output += "\t\tGreen Component\n"
218 | output += "\t\t"
219 | output += strconv.FormatFloat(float64(cc.G)/255, 'f', 17, 64)
220 | output += "\n"
221 | output += "\t\tRed Component\n"
222 | output += "\t\t"
223 | output += strconv.FormatFloat(float64(cc.R)/255, 'f', 17, 64)
224 | output += "\n"
225 | output += "\t\n"
226 | }
227 | output += "\n"
228 | output += "\n"
229 | return output
230 | }
231 |
232 | func printURxvt(colors []color.Color) string {
233 | output := ""
234 | for i, c := range colors {
235 | cc := c.(color.NRGBA)
236 | bytes := []byte{byte(cc.R), byte(cc.G), byte(cc.B)}
237 | output += "URxvt*color"
238 | output += strconv.Itoa(i)
239 | output += ": "
240 | output += "#"
241 | output += hex.EncodeToString(bytes)
242 | output += "\n"
243 | }
244 | return output
245 | }
246 |
247 | func printColors(colors []color.Color) string {
248 | output := ""
249 | for _, c := range colors {
250 | cc := c.(color.NRGBA)
251 | bytes := []byte{byte(cc.R), byte(cc.G), byte(cc.B)}
252 | output += "#"
253 | output += hex.EncodeToString(bytes)
254 | output += "\n"
255 | }
256 | return output
257 | }
258 | func printChrome(colors []color.Color) string {
259 | output := "{"
260 | for i, c := range colors {
261 | cc := c.(color.NRGBA)
262 | bytes := []byte{byte(cc.R), byte(cc.G), byte(cc.B)}
263 | output += " \""
264 | output += strconv.Itoa(i)
265 | output += "\""
266 | output += ": "
267 | output += " \""
268 | output += "#"
269 | output += hex.EncodeToString(bytes)
270 | output += "\" "
271 | if i != len(colors)-1 {
272 | output += ", "
273 | }
274 | }
275 | output += "}\n"
276 | return output
277 | }
278 |
279 | func printOSXTerminal(colors []color.Color) string {
280 | // The plist that is used by OS X's Terminal to store colours. Normally,
281 | // Terminal stores the colours in a base64 encoded binary plist but it'll
282 | // happily read base64 encoded xml plists which makes things easier.
283 | const OSXSerializedNSColorTemplate = `$archiverNSKeyedArchiver$objects$null$classCF$UID2NSColorSpace1NSRGB%s$classesNSColorNSObject$classnameNSColor$toprootCF$UID1$version100000`
284 | OSXColorNames := map[int]string{
285 | 0: "Black",
286 | 1: "Red",
287 | 2: "Green",
288 | 3: "Yellow",
289 | 4: "Blue",
290 | 5: "Magenta",
291 | 6: "Cyan",
292 | 7: "White",
293 | }
294 |
295 | output := "\n"
296 | output += "\n"
297 | output += "\n"
298 | output += "\n"
299 | for i, c := range colors {
300 | cc := c.(color.NRGBA)
301 | output += "\tANSI"
302 | if i > 7 {
303 | output += "Bright" + OSXColorNames[i-8]
304 | } else {
305 | output += OSXColorNames[i]
306 | }
307 | output += "Color\n"
308 | output += "\t\n"
309 | rgbColorString := fmt.Sprintf("%.10f %.10f %.10f", float64(cc.R)/255, float64(cc.G)/255, float64(cc.B)/255)
310 | serializedColor := fmt.Sprintf(OSXSerializedNSColorTemplate, base64.StdEncoding.EncodeToString([]byte(rgbColorString)))
311 | output += "\t" + base64.StdEncoding.EncodeToString([]byte(serializedColor))
312 | output += "\n\t\n"
313 | }
314 |
315 | output += "\ttype\n" // Need this key or Terminal says the file is corrupt
316 | output += "\tWindow Settings\n"
317 | output += "\n"
318 | output += "\n"
319 | return output
320 | }
321 |
--------------------------------------------------------------------------------