charmtone.go

  1// Package charmtone contains an API for the CharmTone color palette.
  2package charmtone
  3
  4import (
  5	"fmt"
  6	"image/color"
  7	"slices"
  8
  9	"github.com/lucasb-eyer/go-colorful"
 10)
 11
 12var _ color.Color = Key(0)
 13
 14// Key is a type for color keys.
 15type Key int
 16
 17// Available colors.
 18const (
 19	Cumin Key = iota
 20	Tang
 21	Yam
 22	Paprika
 23	Bengal
 24	Uni
 25	Sriracha
 26	Coral
 27	Salmon
 28	Chili
 29	Cherry
 30	Tuna
 31	Macaron
 32	Pony
 33	Cheeky
 34	Flamingo
 35	Dolly
 36	Blush
 37	Urchin
 38	Mochi
 39	Lilac
 40	Prince
 41	Violet
 42	Mauve
 43	Grape
 44	Plum
 45	Orchid
 46	Jelly
 47	Charple
 48	Hazy
 49	Ox
 50	Sapphire
 51	Guppy
 52	Oceania
 53	Thunder
 54	Anchovy
 55	Damson
 56	Malibu
 57	Sardine
 58	Zinc
 59	Turtle
 60	Lichen
 61	Guac
 62	Julep
 63	Bok
 64	Mustard
 65	Citron
 66	Zest
 67	Pepper
 68	BBQ
 69	Charcoal
 70	Iron
 71	Oyster
 72	Squid
 73	Smoke
 74	Ash
 75	Salt
 76	Butter
 77
 78	// Diffs: additions. The brightest color in this set is Julep, defined
 79	// above.
 80	Pickle
 81	Gator
 82	Spinach
 83
 84	// Diffs: deletions. The brightest color in this set is Cherry, defined
 85	// above.
 86	Pom
 87	Steak
 88	Toast
 89
 90	// Provisional.
 91	NeueGuac
 92	NeueZinc
 93)
 94
 95// RGBA returns the red, green, blue, and alpha values of the color. It
 96// satisfies the color.Color interface.
 97func (k Key) RGBA() (r, g, b, a uint32) {
 98	c, err := colorful.Hex(k.Hex())
 99	if err != nil {
100		panic(fmt.Sprintf("invalid color key %d: %s: %v", k, k.String(), err))
101	}
102	return c.RGBA()
103}
104
105// String returns the official CharmTone name of the color. It satisfies the
106// fmt.Stringer interface.
107func (k Key) String() string {
108	return map[Key]string{
109		Cumin:    "Cumin",
110		Tang:     "Tang",
111		Yam:      "Yam",
112		Paprika:  "Paprika",
113		Bengal:   "Bengal",
114		Uni:      "Uni",
115		Sriracha: "Sriracha",
116		Coral:    "Coral",
117		Salmon:   "Salmon",
118		Chili:    "Chili",
119		Cherry:   "Cherry",
120		Tuna:     "Tuna",
121		Macaron:  "Macaron",
122		Pony:     "Pony",
123		Cheeky:   "Cheeky",
124		Flamingo: "Flamingo",
125		Dolly:    "Dolly",
126		Blush:    "Blush",
127		Urchin:   "Urchin",
128		Mochi:    "Crystal",
129		Lilac:    "Lilac",
130		Prince:   "Prince",
131		Violet:   "Violet",
132		Mauve:    "Mauve",
133		Grape:    "Grape",
134		Plum:     "Plum",
135		Orchid:   "Orchid",
136		Jelly:    "Jelly",
137		Charple:  "Charple",
138		Hazy:     "Hazy",
139		Ox:       "Ox",
140		Sapphire: "Sapphire",
141		Guppy:    "Guppy",
142		Oceania:  "Oceania",
143		Thunder:  "Thunder",
144		Anchovy:  "Anchovy",
145		Damson:   "Damson",
146		Malibu:   "Malibu",
147		Sardine:  "Sardine",
148		Zinc:     "Zinc",
149		Turtle:   "Turtle",
150		Lichen:   "Lichen",
151		Guac:     "Guac",
152		Julep:    "Julep",
153		Bok:      "Bok",
154		Mustard:  "Mustard",
155		Citron:   "Citron",
156		Zest:     "Zest",
157		Pepper:   "Pepper",
158		BBQ:      "BBQ",
159		Charcoal: "Charcoal",
160		Iron:     "Iron",
161		Oyster:   "Oyster",
162		Squid:    "Squid",
163		Smoke:    "Smoke",
164		Salt:     "Salt",
165		Ash:      "Ash",
166		Butter:   "Butter",
167
168		// Diffs: additions.
169		Pickle:  "Pickle",
170		Gator:   "Gator",
171		Spinach: "Spinach",
172
173		// Diffs: deletions.
174		Pom:   "Pom",
175		Steak: "Steak",
176		Toast: "Toast",
177
178		// Provisional.
179		NeueGuac: "Neue Guac",
180		NeueZinc: "Neue Zinc",
181	}[k]
182}
183
184// Hex returns the hex value of the color.
185func (k Key) Hex() string {
186	return map[Key]string{
187		Cumin:    "#BF976F",
188		Tang:     "#FF985A",
189		Yam:      "#FFB587",
190		Paprika:  "#D36C64",
191		Bengal:   "#FF6E63",
192		Uni:      "#FF937D",
193		Sriracha: "#EB4268",
194		Coral:    "#FF577D",
195		Salmon:   "#FF7F90",
196		Chili:    "#E23080",
197		Cherry:   "#FF388B",
198		Tuna:     "#FF6DAA",
199		Macaron:  "#E940B0",
200		Pony:     "#FF4FBF",
201		Cheeky:   "#FF79D0",
202		Flamingo: "#F947E3",
203		Dolly:    "#FF60FF",
204		Blush:    "#FF84FF",
205		Urchin:   "#C337E0",
206		Mochi:    "#EB5DFF",
207		Lilac:    "#F379FF",
208		Prince:   "#9C35E1",
209		Violet:   "#C259FF",
210		Mauve:    "#D46EFF",
211		Grape:    "#7134DD",
212		Plum:     "#9953FF",
213		Orchid:   "#AD6EFF",
214		Jelly:    "#4A30D9",
215		Charple:  "#6B50FF",
216		Hazy:     "#8B75FF",
217		Ox:       "#3331B2",
218		Sapphire: "#4949FF",
219		Guppy:    "#7272FF",
220		Oceania:  "#2B55B3",
221		Thunder:  "#4776FF",
222		Anchovy:  "#719AFC",
223		Damson:   "#007AB8",
224		Malibu:   "#00A4FF",
225		Sardine:  "#4FBEFE",
226		Zinc:     "#10B1AE",
227		Turtle:   "#0ADCD9",
228		Lichen:   "#5CDFEA",
229		Guac:     "#12C78F",
230		Julep:    "#00FFB2",
231		Bok:      "#68FFD6",
232		Mustard:  "#F5EF34",
233		Citron:   "#E8FF27",
234		Zest:     "#E8FE96",
235		Pepper:   "#201F26",
236		BBQ:      "#2d2c35",
237		Charcoal: "#3A3943",
238		Iron:     "#4D4C57",
239		Oyster:   "#605F6B",
240		Squid:    "#858392",
241		Smoke:    "#BFBCC8",
242		Ash:      "#DFDBDD",
243		Salt:     "#F1EFEF",
244		Butter:   "#FFFAF1",
245
246		// Diffs: additions.
247		Pickle:  "#00A475",
248		Gator:   "#18463D",
249		Spinach: "#1C3634",
250
251		// Diffs: deletions.
252		Pom:   "#AB2454",
253		Steak: "#582238",
254		Toast: "#412130",
255
256		// Provisional.
257		NeueGuac: "#00b875",
258		NeueZinc: "#0e9996",
259	}[k]
260}
261
262// Keys returns a slice of all CharmTone color keys.
263func Keys() []Key {
264	return []Key{
265		Cumin,
266		Tang,
267		Yam,
268		Paprika,
269		Bengal,
270		Uni,
271		Sriracha,
272		Coral,
273		Salmon,
274		Chili,
275		Cherry,
276		Tuna,
277		Macaron,
278		Pony,
279		Cheeky,
280		Flamingo,
281		Dolly,
282		Blush,
283		Urchin,
284		Mochi,
285		Lilac,
286		Prince,
287		Violet,
288		Mauve,
289		Grape,
290		Plum,
291		Orchid,
292		Jelly,
293		Charple,
294		Hazy,
295		Ox,
296		Sapphire,
297		Guppy,
298		Oceania,
299		Thunder,
300		Anchovy,
301		Damson,
302		Malibu,
303		Sardine,
304		Zinc,
305		Turtle,
306		Lichen,
307		Guac,
308		Julep,
309		Bok,
310		Mustard,
311		Citron,
312		Zest,
313		Pepper,
314		BBQ,
315		Charcoal,
316		Iron,
317		Oyster,
318		Squid,
319		Smoke,
320		Ash,
321		Salt,
322		Butter,
323
324		// XXX: additions and deletions are not included, yet.
325	}
326}
327
328// IsPrimary indicates which colors are part of the core palette.
329func (k Key) IsPrimary() bool {
330	return slices.Contains([]Key{
331		Charple,
332		Dolly,
333		Julep,
334		Zest,
335		Butter,
336	}, k)
337}
338
339// IsSecondary indicates which colors are part of the secondary palette.
340func (k Key) IsSecondary() bool {
341	return slices.Contains([]Key{
342		Hazy,
343		Blush,
344		Bok,
345	}, k)
346}
347
348// IsTertiary indicates which colors are part of the tertiary palette.
349func (k Key) IsTertiary() bool {
350	return slices.Contains([]Key{
351		Turtle,
352		Malibu,
353		Violet,
354		Tuna,
355		Coral,
356		Uni,
357	}, k)
358}
359
360// BlendColors returns a slice of colors blended between the given keys.
361// Blending is done as Hcl to stay in gamut.
362func BlendColors(size int, keys ...Key) []color.Color {
363	if len(keys) < 2 {
364		return nil
365	}
366
367	stops := make([]colorful.Color, len(keys))
368	for i, k := range keys {
369		stops[i], _ = colorful.Hex(k.Hex())
370	}
371
372	numSegments := len(stops) - 1
373	if numSegments == 0 {
374		return nil
375	}
376	blended := make([]color.Color, 0, size)
377
378	// Calculate how many colors each segment should have.
379	segmentSizes := make([]int, numSegments)
380	baseSize := size / numSegments
381	remainder := size % numSegments
382
383	// Distribute the remainder across segments.
384	for i := range numSegments {
385		segmentSizes[i] = baseSize
386		if i < remainder {
387			segmentSizes[i]++
388		}
389	}
390
391	// Generate colors for each segment.
392	for i := range numSegments {
393		c1 := stops[i]
394		c2 := stops[i+1]
395		segmentSize := segmentSizes[i]
396
397		for j := range segmentSize {
398			if segmentSize == 0 {
399				continue
400			}
401			t := float64(j) / float64(segmentSize)
402			c := c1.BlendHcl(c2, t)
403			blended = append(blended, c)
404		}
405	}
406
407	return blended
408}