@@ -77,19 +77,9 @@ def __init__(self, red, green=0.0, blue=0.0):
77
77
self .blue = ((b * hsv .saturation ) + invsat ) * hsv .value
78
78
else :
79
79
# 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 )
93
83
94
84
def __repr__ (self ): # pylint: disable=invalid-repr-returned
95
85
return (self .red , self .green , self .blue )
@@ -111,12 +101,59 @@ def __getitem__(self, key):
111
101
return self .blue
112
102
raise IndexError
113
103
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.
118
132
"""
119
133
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
+ )
120
157
return (
121
158
(denormalize (self .red ) << 16 )
122
159
| (denormalize (self .green ) << 8 )
@@ -148,14 +185,8 @@ def __init__(self, h, s=1.0, v=1.0):
148
185
self .hue = h # Don't clamp! Hue can wrap around forever.
149
186
else :
150
187
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 )
159
190
160
191
def __repr__ (self ): # pylint: disable=invalid-repr-returned
161
192
return (self .hue , self .saturation , self .value )
@@ -177,14 +208,23 @@ def __getitem__(self, key):
177
208
return self .value
178
209
raise IndexError
179
210
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.
184
224
"""
185
225
186
226
# Convert CHSV to CRGB, return packed result
187
- return CRGB (self ).pack ()
227
+ return CRGB (self ).pack (white )
188
228
189
229
190
230
def clamp (val , lower , upper ):
@@ -218,6 +258,17 @@ def normalize(val, inplace=False):
218
258
return [normalize (n ) for n in val ]
219
259
220
260
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
+
221
272
def denormalize (val , inplace = False ):
222
273
"""Convert normalized (0.0 to 1.0) value to 8-bit (0 to 255) value
223
274
0 commit comments