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	Barbeque
 69	Charcoal
 70	Iron
 71	Oyster
 72	Squid
 73	Smoke
 74	Ash
 75	Salt
 76	Butter
 77)
 78
 79// RGBA returns the red, green, blue, and alpha values of the color. It
 80// satisfies the color.Color interface.
 81func (k Key) RGBA() (r, g, b, a uint32) {
 82	c, err := colorful.Hex(k.Hex())
 83	if err != nil {
 84		panic(fmt.Sprintf("invalid color key %d: %s: %v", k, k.String(), err))
 85	}
 86	return c.RGBA()
 87}
 88
 89// String returns the official CharmTone name of the color. It satisfies the
 90// fmt.Stringer interface.
 91func (k Key) String() string {
 92	return map[Key]string{
 93		Cumin:    "Cumin",
 94		Tang:     "Tang",
 95		Yam:      "Yam",
 96		Paprika:  "Paprika",
 97		Bengal:   "Bengal",
 98		Uni:      "Uni",
 99		Sriracha: "Sriracha",
100		Coral:    "Coral",
101		Salmon:   "Salmon",
102		Chili:    "Chili",
103		Cherry:   "Cherry",
104		Tuna:     "Tuna",
105		Macaron:  "Macaron",
106		Pony:     "Pony",
107		Cheeky:   "Cheeky",
108		Flamingo: "Flamingo",
109		Dolly:    "Dolly",
110		Blush:    "Blush",
111		Urchin:   "Urchin",
112		Mochi:    "Crystal",
113		Lilac:    "Lilac",
114		Prince:   "Prince",
115		Violet:   "Violet",
116		Mauve:    "Mauve",
117		Grape:    "Grape",
118		Plum:     "Plum",
119		Orchid:   "Orchid",
120		Jelly:    "Jelly",
121		Charple:  "Charple",
122		Hazy:     "Hazy",
123		Ox:       "Ox",
124		Sapphire: "Sapphire",
125		Guppy:    "Guppy",
126		Oceania:  "Oceania",
127		Thunder:  "Thunder",
128		Anchovy:  "Anchovy",
129		Damson:   "Damson",
130		Malibu:   "Malibu",
131		Sardine:  "Sardine",
132		Zinc:     "Zinc",
133		Turtle:   "Turtle",
134		Lichen:   "Lichen",
135		Guac:     "Guac",
136		Julep:    "Julep",
137		Bok:      "Bok",
138		Mustard:  "Mustard",
139		Citron:   "Citron",
140		Zest:     "Zest",
141		Pepper:   "Pepper",
142		Barbeque: "Barbeque",
143		Charcoal: "Charcoal",
144		Iron:     "Iron",
145		Oyster:   "Oyster",
146		Squid:    "Squid",
147		Smoke:    "Smoke",
148		Salt:     "Salt",
149		Ash:      "Ash",
150		Butter:   "Butter",
151	}[k]
152}
153
154// Hex returns the hex value of the color.
155func (k Key) Hex() string {
156	return map[Key]string{
157		Cumin:    "#BF976F",
158		Tang:     "#FF985A",
159		Yam:      "#FFB587",
160		Paprika:  "#D36C64",
161		Bengal:   "#FF6E63",
162		Uni:      "#FF937D",
163		Sriracha: "#EB4268",
164		Coral:    "#FF577D",
165		Salmon:   "#FF7F90",
166		Chili:    "#E23080",
167		Cherry:   "#FF388B",
168		Tuna:     "#FF6DAA",
169		Macaron:  "#E940B0",
170		Pony:     "#FF4FBF",
171		Cheeky:   "#FF79D0",
172		Flamingo: "#F947E3",
173		Dolly:    "#FF60FF",
174		Blush:    "#FF84FF",
175		Urchin:   "#C337E0",
176		Mochi:    "#EB5DFF",
177		Lilac:    "#F379FF",
178		Prince:   "#9C35E1",
179		Violet:   "#C259FF",
180		Mauve:    "#D46EFF",
181		Grape:    "#7134DD",
182		Plum:     "#9953FF",
183		Orchid:   "#AD6EFF",
184		Jelly:    "#4A30D9",
185		Charple:  "#6B50FF",
186		Hazy:     "#8B75FF",
187		Ox:       "#3331B2",
188		Sapphire: "#4949FF",
189		Guppy:    "#7272FF",
190		Oceania:  "#2B55B3",
191		Thunder:  "#4776FF",
192		Anchovy:  "#719AFC",
193		Damson:   "#007AB8",
194		Malibu:   "#00A4FF",
195		Sardine:  "#4FBEFE",
196		Zinc:     "#0e9996",
197		Turtle:   "#0ADCD9",
198		Lichen:   "#5CDFEA",
199		Guac:     "#00b875",
200		Julep:    "#00FFB2",
201		Bok:      "#68FFD6",
202		Mustard:  "#F5EF34",
203		Citron:   "#E8FF27",
204		Zest:     "#E8FE96",
205		Pepper:   "#201F26",
206		Barbeque: "#2d2c35",
207		Charcoal: "#3A3943",
208		Iron:     "#4D4C57",
209		Oyster:   "#605F6B",
210		Squid:    "#858392",
211		Smoke:    "#BFBCC8",
212		Ash:      "#DFDBDD",
213		Salt:     "#F1EFEF",
214		Butter:   "#FFFAF1",
215	}[k]
216}
217
218// Keys returns a slice of all CharmTone color keys.
219func Keys() []Key {
220	return []Key{
221		Cumin,
222		Tang,
223		Yam,
224		Paprika,
225		Bengal,
226		Uni,
227		Sriracha,
228		Coral,
229		Salmon,
230		Chili,
231		Cherry,
232		Tuna,
233		Macaron,
234		Pony,
235		Cheeky,
236		Flamingo,
237		Dolly,
238		Blush,
239		Urchin,
240		Mochi,
241		Lilac,
242		Prince,
243		Violet,
244		Mauve,
245		Grape,
246		Plum,
247		Orchid,
248		Jelly,
249		Charple,
250		Hazy,
251		Ox,
252		Sapphire,
253		Guppy,
254		Oceania,
255		Thunder,
256		Anchovy,
257		Damson,
258		Malibu,
259		Sardine,
260		Zinc,
261		Turtle,
262		Lichen,
263		Guac,
264		Julep,
265		Bok,
266		Mustard,
267		Citron,
268		Zest,
269		Pepper,
270		Barbeque,
271		Charcoal,
272		Iron,
273		Oyster,
274		Squid,
275		Smoke,
276		Ash,
277		Salt,
278		Butter,
279	}
280}
281
282// IsPrimary indicates which colors are part of the core palette.
283func (k Key) IsPrimary() bool {
284	return slices.Contains([]Key{
285		Charple,
286		Dolly,
287		Julep,
288		Zest,
289		Butter,
290	}, k)
291}
292
293// IsSecondary indicates which colors are part of the secondary palette.
294func (k Key) IsSecondary() bool {
295	return slices.Contains([]Key{
296		Hazy,
297		Blush,
298		Bok,
299	}, k)
300}
301
302// IsTertiary indicates which colors are part of the tertiary palette.
303func (k Key) IsTertiary() bool {
304	return slices.Contains([]Key{
305		Turtle,
306		Malibu,
307		Violet,
308		Tuna,
309		Coral,
310		Uni,
311	}, k)
312}
313
314// BlendColors returns a slice of colors blended between the given keys.
315// Blending is done as Hcl to stay in gamut.
316func BlendColors(size int, keys ...Key) []color.Color {
317	if len(keys) < 2 {
318		return nil
319	}
320
321	stops := make([]colorful.Color, len(keys))
322	for i, k := range keys {
323		stops[i], _ = colorful.Hex(k.Hex())
324	}
325
326	numSegments := len(stops) - 1
327	if numSegments == 0 {
328		return nil
329	}
330	blended := make([]color.Color, 0, size)
331
332	// Calculate how many colors each segment should have.
333	segmentSizes := make([]int, numSegments)
334	baseSize := size / numSegments
335	remainder := size % numSegments
336
337	// Distribute the remainder across segments.
338	for i := range numSegments {
339		segmentSizes[i] = baseSize
340		if i < remainder {
341			segmentSizes[i]++
342		}
343	}
344
345	// Generate colors for each segment.
346	for i := range numSegments {
347		c1 := stops[i]
348		c2 := stops[i+1]
349		segmentSize := segmentSizes[i]
350
351		for j := range segmentSize {
352			if segmentSize == 0 {
353				continue
354			}
355			t := float64(j) / float64(segmentSize)
356			c := c1.BlendHcl(c2, t)
357			blended = append(blended, c)
358		}
359	}
360
361	return blended
362}