Skip to content

Commit 2148bf2

Browse files
committed
Increase Color precision using floats internally
1 parent ab1e5c2 commit 2148bf2

File tree

1 file changed

+85
-108
lines changed

1 file changed

+85
-108
lines changed

sdl2/ext/color.py

Lines changed: 85 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
"COLOR"]
1111

1212

13+
def _clip(val, _min, _max):
14+
return max(min(val, _max), _min)
15+
16+
1317
class Color(object):
1418
"""A class for working with and converting RGBA colors.
1519
@@ -47,10 +51,10 @@ class Color(object):
4751
def __init__(self, r=255, g=255, b=255, a=255):
4852
for val in (r, g, b, a):
4953
self._verify_rgba_value(val)
50-
self._r = int(r)
51-
self._g = int(g)
52-
self._b = int(b)
53-
self._a = int(a)
54+
self._r = float(int(r))
55+
self._g = float(int(g))
56+
self._b = float(int(b))
57+
self._a = float(int(a))
5458

5559
def _verify_rgba_value(self, val):
5660
"""Verifies that the input is a valid uint8 RGBA value."""
@@ -59,7 +63,7 @@ def _verify_rgba_value(self, val):
5963
float(val)
6064
except (ValueError, TypeError):
6165
raise TypeError(e.format(val))
62-
if int(val) != val or val < 0 or val > 255:
66+
if val < 0 or val > 255:
6367
raise ValueError(e.format(val))
6468

6569
def __repr__(self):
@@ -108,31 +112,35 @@ def __mod__(self, color):
108112

109113
def __div__(self, color):
110114
vals = [0, 0, 0, 0]
111-
if color.r != 0:
112-
vals[0] = self.r / color.r
113-
if color.g != 0:
114-
vals[1] = self.g / color.g
115-
if color.b != 0:
116-
vals[2] = self.b / color.b
117-
if color.a != 0:
118-
vals[3] = self.a / color.a
115+
if color._r != 0:
116+
vals[0] = (self._r / color._r)
117+
if color._g != 0:
118+
vals[1] = (self._g / color._g)
119+
if color._b != 0:
120+
vals[2] = (self._b / color._b)
121+
if color._a != 0:
122+
vals[3] = (self._a / color._a)
119123
return Color(vals[0], vals[1], vals[2], vals[3])
120124

121125
def __truediv__(self, color):
122126
vals = [0, 0, 0, 0]
123-
if color.r != 0:
124-
vals[0] = self.r / color.r
125-
if color.g != 0:
126-
vals[1] = self.g / color.g
127-
if color.b != 0:
128-
vals[2] = self.b / color.b
129-
if color.a != 0:
130-
vals[3] = self.a / color.a
127+
if color._r != 0:
128+
vals[0] = (self._r / color._r)
129+
if color._g != 0:
130+
vals[1] = (self._g / color._g)
131+
if color._b != 0:
132+
vals[2] = (self._b / color._b)
133+
if color._a != 0:
134+
vals[3] = (self._a / color._a)
131135
return Color(vals[0], vals[1], vals[2], vals[3])
132136

133137
def __mul__(self, color):
134-
vals = (min(self.r * color.r, 255), min(self.g * color.g, 255),
135-
min(self.b * color.b, 255), min(self.a * color.a, 255))
138+
vals = (
139+
min(self._r * color._r, 255),
140+
min(self._g * color._g, 255),
141+
min(self._b * color._b, 255),
142+
min(self._a * color._a, 255)
143+
)
136144
return Color(vals[0], vals[1], vals[2], vals[3])
137145

138146
def __sub__(self, color):
@@ -162,42 +170,42 @@ def __setitem__(self, index, val):
162170
@property
163171
def r(self):
164172
""""int: The 8-bit RGBA red level for the color."""
165-
return self._r
173+
return int(round(self._r))
166174

167175
@r.setter
168176
def r(self, val):
169177
self._verify_rgba_value(val)
170-
self._r = int(val)
178+
self._r = float(val)
171179

172180
@property
173181
def g(self):
174182
""""int: The 8-bit RGBA green level for the color."""
175-
return self._g
183+
return int(round(self._g))
176184

177185
@g.setter
178186
def g(self, val):
179187
self._verify_rgba_value(val)
180-
self._g = int(val)
188+
self._g = float(val)
181189

182190
@property
183191
def b(self):
184192
""""int: The 8-bit RGBA blue level for the color."""
185-
return self._b
193+
return int(round(self._b))
186194

187195
@b.setter
188196
def b(self, val):
189197
self._verify_rgba_value(val)
190-
self._b = int(val)
198+
self._b = float(val)
191199

192200
@property
193201
def a(self):
194202
""""int: The 8-bit RGBA alpha transparency level for the color."""
195-
return self._a
203+
return int(round(self._a))
196204

197205
@a.setter
198206
def a(self, val):
199207
self._verify_rgba_value(val)
200-
self._a = int(val)
208+
self._a = float(val)
201209

202210
@property
203211
def hsva(self):
@@ -212,10 +220,10 @@ def hsva(self):
212220
values for the given color.
213221
214222
"""
215-
rn = self.r / 255.0
216-
gn = self.g / 255.0
217-
bn = self.b / 255.0
218-
an = self.a / 255.0
223+
rn = self._r / 255.0
224+
gn = self._g / 255.0
225+
bn = self._b / 255.0
226+
an = self._a / 255.0
219227

220228
maxv = max(rn, gn, bn)
221229
minv = min(rn, gn, bn)
@@ -238,6 +246,7 @@ def hsva(self):
238246
h = (60 * (rn - gn) / diff) + 240.0
239247
if h < 0:
240248
h += 360.0
249+
241250
return (h, s, v, a)
242251

243252
@hsva.setter
@@ -269,7 +278,7 @@ def hsva(self, value):
269278
(t, p, v), # if hi == 4
270279
(v, p, q) # if hi == 5
271280
]
272-
vals = [int(n * 255) for n in rgb_map[hi]]
281+
vals = [_clip(n * 255.0, 0.0, 255.0) for n in rgb_map[hi]]
273282
self.r, self.g, self.b = vals
274283

275284
@property
@@ -285,10 +294,10 @@ def hsla(self):
285294
values for the given color.
286295
287296
"""
288-
rn = self.r / 255.0
289-
gn = self.g / 255.0
290-
bn = self.b / 255.0
291-
an = self.a / 255.0
297+
rn = self._r / 255.0
298+
gn = self._g / 255.0
299+
bn = self._b / 255.0
300+
an = self._a / 255.0
292301

293302
maxv = max(rn, gn, bn)
294303
minv = min(rn, gn, bn)
@@ -315,6 +324,7 @@ def hsla(self):
315324
h = (60 * (rn - gn) / diff) + 240.0
316325
if h < 0:
317326
h += 360.0
327+
318328
return (h, s, l, a)
319329

320330
@hsla.setter
@@ -328,14 +338,12 @@ def hsla(self, value):
328338
raise ValueError("invalid HSLA value")
329339

330340
self.a = int((a / 100.0) * 255)
331-
332341
s /= 100.0
333342
l /= 100.0
334-
335343
if s == 0:
336-
self.r = int(l * 255)
337-
self.g = int(l * 255)
338-
self.b = int(l * 255)
344+
self.r = l * 255.0
345+
self.g = l * 255.0
346+
self.b = l * 255.0
339347
return
340348

341349
q = 0
@@ -346,54 +354,23 @@ def hsla(self, value):
346354
p = 2 * l - q
347355

348356
ht = h / 360.0
357+
vals = []
358+
for h in [ht + (1.0 / 3.0), ht, ht - (1.0 / 3.0)]:
359+
if h < 0:
360+
h += 1
361+
elif h > 1:
362+
h -= 1
363+
if h < (1.0 / 6.0):
364+
val = (p + ((q - p) * 6 * h))
365+
elif h < 0.5:
366+
val = q
367+
elif h < (2.0 / 3.0):
368+
val = (p + ((q - p) * 6 * (2.0 / 3.0 - h)))
369+
else:
370+
val = p
371+
vals.append(_clip(val * 255.0, 0.0, 255.0))
349372

350-
# r
351-
h = ht + (1.0 / 3.0)
352-
if h < 0:
353-
h += 1
354-
elif h > 1:
355-
h -= 1
356-
357-
if h < (1.0 / 6.0):
358-
self.r = int((p + ((q - p) * 6 * h)) * 255)
359-
elif h < 0.5:
360-
self.r = int(q * 255)
361-
elif h < (2.0 / 3.0):
362-
self.r = int((p + ((q - p) * 6 * (2.0 / 3.0 - h))) * 255)
363-
else:
364-
self.r = int(p * 255)
365-
366-
# g
367-
h = ht
368-
if h < 0:
369-
h += 1
370-
elif h > 1:
371-
h -= 1
372-
373-
if h < (1.0 / 6.0):
374-
self.g = int((p + ((q - p) * 6 * h)) * 255)
375-
elif h < 0.5:
376-
self.g = int(q * 255)
377-
elif h < (2.0 / 3.0):
378-
self.g = int((p + ((q - p) * 6 * (2.0 / 3.0 - h))) * 255)
379-
else:
380-
self.g = int(p * 255)
381-
382-
# b
383-
h = ht - (1.0 / 3.0)
384-
if h < 0:
385-
h += 1
386-
elif h > 1:
387-
h -= 1
388-
389-
if h < (1.0 / 6.0):
390-
self.b = int((p + ((q - p) * 6 * h)) * 255)
391-
elif h < 0.5:
392-
self.b = int(q * 255)
393-
elif h < (2.0 / 3.0):
394-
self.b = int((p + ((q - p) * 6 * (2.0 / 3.0 - h))) * 255)
395-
else:
396-
self.b = int(p * 255)
373+
self.r, self.g, self.b = vals
397374

398375
@property
399376
def i1i2i3(self):
@@ -410,15 +387,15 @@ def i1i2i3(self):
410387
values for the given color.
411388
412389
"""
413-
rn = self.r / 255.0
414-
gn = self.g / 255.0
415-
bn = self.b / 255.0
390+
rn = self._r / 255.0
391+
gn = self._g / 255.0
392+
bn = self._b / 255.0
416393

417-
i1 = (rn + gn + bn) / 3.0
418-
i2 = (rn - bn) / 2.0
419-
i3 = (2 * gn - rn - bn) / 4.0
394+
i1 = _clip((rn + gn + bn) / 3.0, 0.0, 1.0)
395+
i2 = _clip((rn - bn) / 2.0, -0.5, 0.5)
396+
i3 = _clip((2 * gn - rn - bn) / 4.0, -0.5, 0.5)
420397

421-
return(i1, i2, i3)
398+
return (i1, i2, i3)
422399

423400
@i1i2i3.setter
424401
def i1i2i3(self, value):
@@ -434,9 +411,9 @@ def i1i2i3(self, value):
434411
ar = 2 * i2 + ab
435412
ag = 3 * i1 - ar - ab
436413

437-
self.r = int(ar * 255)
438-
self.g = int(ag * 255)
439-
self.b = int(ab * 255)
414+
self.r = _clip(ar * 255, 0.0, 255.0)
415+
self.g = _clip(ag * 255, 0.0, 255.0)
416+
self.b = _clip(ab * 255, 0.0, 255.0)
440417

441418
@property
442419
def cmy(self):
@@ -447,18 +424,18 @@ def cmy(self):
447424
All three values are represented as floats between 0.0 and 1.0.
448425
449426
"""
450-
return (1.0 - self.r / 255.0,
451-
1.0 - self.g / 255.0,
452-
1.0 - self.b / 255.0)
427+
return (1.0 - self._r / 255.0,
428+
1.0 - self._g / 255.0,
429+
1.0 - self._b / 255.0)
453430

454431
@cmy.setter
455432
def cmy(self, value):
456433
c, m, y = value
457434
if (c < 0 or c > 1) or (m < 0 or m > 1) or (y < 0 or y > 1):
458435
raise ValueError("invalid CMY value")
459-
self.r = int((1.0 - c) * 255)
460-
self.g = int((1.0 - m) * 255)
461-
self.b = int((1.0 - y) * 255)
436+
self._r = (1.0 - c) * 255
437+
self._g = (1.0 - m) * 255
438+
self._b = (1.0 - y) * 255
462439

463440
def normalize(self):
464441
"""Returns the RGBA values as floats between 0 and 1.

0 commit comments

Comments
 (0)