Skip to content

Commit 8400bd6

Browse files
Merge pull request #26 from adafruit/pb-rgbw
Allow user code to set W element of RGBW pixels via pack() function (issue #25)
2 parents ec9e7a9 + 039c8e7 commit 8400bd6

File tree

1 file changed

+81
-30
lines changed

1 file changed

+81
-30
lines changed

adafruit_fancyled/adafruit_fancyled.py

Lines changed: 81 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,9 @@ def __init__(self, red, green=0.0, blue=0.0):
7777
self.blue = ((b * hsv.saturation) + invsat) * hsv.value
7878
else:
7979
# Red, green, blue arguments (normalized floats OR integers)
80-
# TODO(tannewt): Factor this out into a helper function
81-
if isinstance(red, float):
82-
self.red = clamp(red, 0.0, 1.0)
83-
else:
84-
self.red = normalize(red)
85-
if isinstance(green, float):
86-
self.green = clamp(green, 0.0, 1.0)
87-
else:
88-
self.green = normalize(green)
89-
if isinstance(blue, float):
90-
self.blue = clamp(blue, 0.0, 1.0)
91-
else:
92-
self.blue = normalize(blue)
80+
self.red = clamp_norm(red)
81+
self.green = clamp_norm(green)
82+
self.blue = clamp_norm(blue)
9383

9484
def __repr__(self): # pylint: disable=invalid-repr-returned
9585
return (self.red, self.green, self.blue)
@@ -111,12 +101,59 @@ def __getitem__(self, key):
111101
return self.blue
112102
raise IndexError
113103

114-
def pack(self):
115-
"""'Pack' a `CRGB` color into a 24-bit RGB integer.
116-
117-
:returns: 24-bit integer a la ``0x00RRGGBB``.
104+
def pack(self, white=None):
105+
"""'Pack' a `CRGB` color into a 24-bit RGB integer, OR, optionally
106+
assign a white element for RGBW NeoPixels and return as a 4-tuple,
107+
either of which can be passed to the NeoPixel setter.
108+
WITH REGARD TO RGBW PIXELS, THIS PROBABLY DOESN'T DO WHAT YOU THINK.
109+
FancyLED is currently RGB-focused through and through and has no
110+
concept of RGBW. This function does NOT perform white component
111+
replacement on the RGB elements -- those values are returned
112+
unmodified, this just allows appending a white element to pass
113+
through to the NeoPixel setter with RGBW pixels.
114+
The reason for this peculiar return option is that the core NeoPixel
115+
library can't accept packed 32-bit values for RGBW, only 4-tuples.
116+
This is intentional and by design, because space-constrained devices
117+
don't support the full 32-bit integer range in CircuitPython (but
118+
24-bit RGB fits).
119+
Also note, if gamma_adjust() was applied to an RGB color that's then
120+
passed to this function, that adjustment is NOT automatically applied
121+
to the white element -- this must be explicitly handled in user code
122+
(gamma_adjust() can accept both tuples (for RGB) and single values
123+
(for white)).
124+
:param white: integer 0 to 255, float 0.0 to 1.0, or None (default).
125+
If specified, this value is returned as the last element of an
126+
integer 4-tuple. Values outside these ranges will be clamped, not
127+
throw an exception.
128+
:returns: 24-bit integer a la ``0x00RRGGBB`` if no argument passed,
129+
or 4-element integer tuple a la ``(R,G,B,W)`` if argument for fourth
130+
element is provided.
131+
:rtype: integer or 4-tuple.
118132
"""
119133

134+
if white:
135+
# So really this is a quick-fix to the FancyLED + RGBW NeoPixel
136+
# combination, which is rare and has only come up once. But if
137+
# this were to become a common thing in the future, a generally
138+
# more robust approach would be to implement a distinct CRGBW
139+
# class, which could then do things like gamma_adjust() on all
140+
# elements, perhaps white component replacement, etc., and would
141+
# do away with this gross special kludge case.
142+
# Initially this was done as an __add__ function before moving
143+
# it here into pack(), as the CRGB + value syntax was guaranteed
144+
# to cause confusion (it would be easily assumed that it increases
145+
# brightness, not appends a value). So, note to future self,
146+
# don't try to be clever that way, this was on purpose.
147+
if isinstance(white, float):
148+
white = denormalize(white)
149+
else:
150+
white = clamp(white, 0, 255)
151+
return (
152+
denormalize(self.red),
153+
denormalize(self.green),
154+
denormalize(self.blue),
155+
white,
156+
)
120157
return (
121158
(denormalize(self.red) << 16)
122159
| (denormalize(self.green) << 8)
@@ -148,14 +185,8 @@ def __init__(self, h, s=1.0, v=1.0):
148185
self.hue = h # Don't clamp! Hue can wrap around forever.
149186
else:
150187
self.hue = float(h) / 256.0
151-
if isinstance(s, float):
152-
self.saturation = clamp(s, 0.0, 1.0)
153-
else:
154-
self.saturation = normalize(s)
155-
if isinstance(v, float):
156-
self.value = clamp(v, 0.0, 1.0)
157-
else:
158-
self.value = normalize(v)
188+
self.saturation = clamp_norm(s)
189+
self.value = clamp_norm(v)
159190

160191
def __repr__(self): # pylint: disable=invalid-repr-returned
161192
return (self.hue, self.saturation, self.value)
@@ -177,14 +208,23 @@ def __getitem__(self, key):
177208
return self.value
178209
raise IndexError
179210

180-
def pack(self):
181-
"""'Pack' a `CHSV` color into a 24-bit RGB integer.
182-
183-
:returns: 24-bit integer a la ``0x00RRGGBB``.
211+
def pack(self, white=None):
212+
"""'Pack' a `CHSV` color into a 24-bit RGB integer, OR, optionally
213+
assign a white element for RGBW NeoPixels and return as a 4-tuple,
214+
either of which can be passed to the NeoPixel setter.
215+
Please see notes accompanying CRGB.pack() for important RGBW
216+
peculiarities.
217+
:param white: integer 0 to 255, float 0.0 to 1.0, or None (default).
218+
If specified, this value is returned as the last element of a 4-tuple.
219+
Values outside these ranges will be clamped, not throw an exception.
220+
:returns: 24-bit integer a la ``0x00RRGGBB`` if no argument passed,
221+
or 4-element integer tuple a la ``(R,G,B,W)`` if argument for fourth
222+
element is provided.
223+
:rtype: integer or 4-tuple.
184224
"""
185225

186226
# Convert CHSV to CRGB, return packed result
187-
return CRGB(self).pack()
227+
return CRGB(self).pack(white)
188228

189229

190230
def clamp(val, lower, upper):
@@ -218,6 +258,17 @@ def normalize(val, inplace=False):
218258
return [normalize(n) for n in val]
219259

220260

261+
def clamp_norm(val):
262+
"""Clamp or normalize a value as appropriate to its type. If a float is
263+
received, the return value is the input clamped to a 0.0 to 1.0 range.
264+
If an integer is received, a range of 0-255 is scaled to a float value
265+
of 0.0 to 1.0 (also clamped).
266+
"""
267+
if isinstance(val, float):
268+
return clamp(val, 0.0, 1.0)
269+
return normalize(val)
270+
271+
221272
def denormalize(val, inplace=False):
222273
"""Convert normalized (0.0 to 1.0) value to 8-bit (0 to 255) value
223274

0 commit comments

Comments
 (0)