1
2import math
3import numpy as np
4import pwkit.colormaps # pip3 install pwkit
5
6
7# Most calculations from http://www.chilliant.com/rgb2hsv.html
8
9
10def limit(v, low=0, high=1):
11 return max(min(v, high), low)
12
13class Srgb:
14 LUMA_WEIGHTS = (0.299, 0.587, 0.114)
15 def __init__(self, r, g, b):
16 self.r = float(r)
17 self.g = float(g)
18 self.b = float(b)
19 def __iter__(self):
20 return iter((self.r, self.g, self.b))
21 def __repr__(self):
22 return 'RGB({}, {}, {})'.format(self.r, self.g, self.b)
23 def __str__(self):
24 return 'rgb({}%,{}%,{}%)'.format(self.r*100, self.g*100, self.b*100)
25 def luma(self, wts=None):
26 if wts is None: wts = self.LUMA_WEIGHTS
27 rw, gw, bw = wts
28 return rw*self.r + gw*self.g + bw*self.b
29 def toSrgb(self):
30 return self
31 @staticmethod
32 def fromHue(h):
33 h = h % 1
34 r = abs(h * 6 - 3) - 1
35 g = 2 - abs(h * 6 - 2)
36 b = 2 - abs(h * 6 - 4)
37 return Srgb(limit(r), limit(g), limit(b))
38
39class Hsl:
40 def __init__(self, h, s, l):
41 self.h = float(h) % 1
42 self.s = float(s)
43 self.l = float(l)
44 def __iter__(self):
45 return iter((self.h, self.s, self.l))
46 def __repr__(self):
47 return 'HSL({}, {}, {})'.format(self.h, self.s, self.l)
48 def __str__(self):
49 r, g, b = self.toSrgb()
50 return 'rgb({}%,{}%,{}%)'.format(round(r*100), round(g*100), round(b*100))
51 def toSrgb(self):
52 hs = Srgb.fromHue(self.h)
53 c = (1 - abs(2 * self.l - 1)) * self.s
54 return Srgb(
55 (hs.r - 0.5) * c + self.l,
56 (hs.g - 0.5) * c + self.l,
57 (hs.b - 0.5) * c + self.l
58 )
59
60class Hsv:
61 def __init__(self, h, s, v):
62 self.h = float(h) % 1
63 self.s = float(s)
64 self.v = float(v)
65 def __iter__(self):
66 return iter((self.h, self.s, self.v))
67 def __repr__(self):
68 return 'HSV({}, {}, {})'.format(self.h, self.s, self.v)
69 def __str__(self):
70 r, g, b = self.toSrgb()
71 return 'rgb({}%,{}%,{}%)'.format(round(r*100), round(g*100), round(b*100))
72 def toSrgb(self):
73 hs = Srgb.fromHue(self.h)
74 c = self.v * self.s
75 hp = self.h * 6
76 x = c * (1 - abs(hp % 2 - 1))
77 if hp < 1:
78 r1, g1, b1 = c, x, 0
79 elif hp < 2:
80 r1, g1, b1 = x, c, 0
81 elif hp < 3:
82 r1, g1, b1 = 0, c, x
83 elif hp < 4:
84 r1, g1, b1 = 0, x, c
85 elif hp < 5:
86 r1, g1, b1 = x, 0, c
87 else:
88 r1, g1, b1 = c, 0, x
89 m = self.v - c
90 return Srgb(r1+m, g1+m, b1+m)
91
92class Sin:
93 def __init__(self, h, s, l):
94 self.h = float(h) % 1
95 self.s = float(s)
96 self.l = float(l)
97 def __iter__(self):
98 return iter((self.h, self.s, self.l))
99 def __repr__(self):
100 return 'Sin({}, {}, {})'.format(self.h, self.s, self.l)
101 def __str__(self):
102 r, g, b = self.toSrgb()
103 return 'rgb({}%,{}%,{}%)'.format(round(r*100), round(g*100), round(b*100))
104 def toSrgb(self):
105 h = self.h
106 scale = self.s / 2
107 shift = self.l #* (1-2*scale)
108 return Srgb(
109 shift + scale * math.cos(math.pi*2 * (h - 0/6)),
110 shift + scale * math.cos(math.pi*2 * (h - 2/6)),
111 shift + scale * math.cos(math.pi*2 * (h - 4/6)),
112 )
113
114class Hcy:
115 HCY_WEIGHTS = Srgb.LUMA_WEIGHTS
116 def __init__(self, h, c, y):
117 self.h = float(h) % 1
118 self.c = float(c)
119 self.y = float(y)
120 def __iter__(self):
121 return iter((self.h, self.c, self.y))
122 def __repr__(self):
123 return 'HCY({}, {}, {})'.format(self.h, self.c, self.y)
124 def __str__(self):
125 r, g, b = self.toSrgb()
126 return 'rgb({}%,{}%,{}%)'.format(r*100, g*100, b*100)
127 def toSrgb(self):
128 hs = Srgb.fromHue(self.h)
129 y = hs.luma(wts=self.HCY_WEIGHTS)
130 c = self.c
131 if self.y < y:
132 c *= self.y / y
133 elif y < 1:
134 c *= (1 - self.y) / (1 - y)
135 return Srgb(
136 (hs.r - y) * c + self.y,
137 (hs.g - y) * c + self.y,
138 (hs.b - y) * c + self.y,
139 )
140 @staticmethod
141 def _rgbToHcv(srgb):
142 if srgb.g < srgb.b:
143 p = (srgb.b, srgb.g, -1., 2./3.)
144 else:
145 p = (srgb.g, srgb.b, 0., -1./3.)
146 if srgb.r < p[0]:
147 q = (p[0], p[1], p[3], srgb.r)
148 else:
149 q = (srgb.r, p[1], p[2], p[0])
150 c = q[0] - min(q[3], q[1])
151 h = abs((q[3] - q[1]) / (6*c + 1e-10) + q[2])
152 return (h, c, q[0])
153 @classmethod
154 def fromSrgb(cls, srgb):
155 hcv = list(cls._rgbToHcv(srgb))
156 rw, gw, bw = cls.HCY_WEIGHTS
157 y = rw*srgb.r + gw*srgb.g + bw*srgb.b
158 hs = Srgb.fromHue(hcv[0])
159 z = rw*hs.r + gw*hs.g + bw*hs.b
160 if y < z:
161 hcv[1] *= z / (y + 1e-10)
162 else:
163 hcv[1] *= (1 - z) / (1 - y + 1e-10)
164 return Hcy(hcv[0], hcv[1], y)
165
166class Cielab:
167 REF_WHITE = (0.95047, 1., 1.08883)
168 def __init__(self, l, a, b):
169 self.l = float(l)
170 self.a = float(a)
171 self.b = float(b)
172 def __iter__(self):
173 return iter((self.l, self.a, self.b))
174 def __repr__(self):
175 return 'CIELAB({}, {}, {})'.format(self.l, self.a, self.b)
176 def __str__(self):
177 r, g, b = self.toSrgb()
178 return 'rgb({}%,{}%,{}%)'.format(round(r*100), round(g*100), round(b*100))
179 def toSrgb(self):
180 inArr = np.array((*self.l,), dtype=float)
181 xyz = pwkit.colormaps.cielab_to_xyz(inArr, self.REF_WHITE)
182 linSrgb = pwkit.colormaps.xyz_to_linsrgb(xyz)
183 r, g, b = pwkit.colormaps.linsrgb_to_srgb(linSrgb)
184 return Srgb(r, g, b)
185 @classmethod
186 def fromSrgb(cls, srgb, refWhite=None):
187 if refWhite is None: refWhite = cls.REF_WHITE
188 inArr = np.array((*srgb,), dtype=float)
189 linSrgb = pwkit.colormaps.srgb_to_linsrgb(inArr)
190 xyz = pwkit.colormaps.linsrgb_to_xyz(linSrgb)
191 l, a, b = pwkit.colormaps.xyz_to_cielab(xyz, refWhite)
192 return Cielab(l, a, b)
193 def toSrgb(self):
194 inArr = np.array((self.l, self.a, self.b))
195 xyz = pwkit.colormaps.cielab_to_xyz(inArr, self.REF_WHITE)
196 linSrgb = pwkit.colormaps.xyz_to_linsrgb(xyz)
197 r, g, b = pwkit.colormaps.linsrgb_to_srgb(linSrgb)
198 return Srgb(r, g, b)
199