Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets
at 1.7.0 6.4 kB view raw
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