|
| 1 | +# fancyled is sort of a mild CircuitPython interpretation of a subset |
| 2 | +# of the FastLED library for Arduino. This is mainly to assist with |
| 3 | +# porting of existing Arduino projects to CircuitPython. |
| 4 | +# It is NOT fast. Whereas FastLED does a lot of bit-level numerical |
| 5 | +# tricks for performance, we don't really have that luxury in Python, |
| 6 | +# and the aim here is just to have equivalent (ish) functions for the |
| 7 | +# most oft-used calls. |
| 8 | + |
| 9 | +from math import pow |
| 10 | + |
| 11 | +gfactor = 2.5 # Default gamma-correction factor for function below |
| 12 | + |
| 13 | +# This approximates various invocations of FastLED's many-ways-overloaded |
| 14 | +# applyGamma_video() function. |
| 15 | +# ACCEPTS: One of three ways: |
| 16 | +# 1. A single brightness level (0-255) and optional gamma-correction |
| 17 | +# factor (float usu. > 1.0, default if unspecified is 2.5). |
| 18 | +# 2. A single RGB tuple (3 values 0-255) and optional gamma factor |
| 19 | +# or separate R, G, B gamma values. |
| 20 | +# 3. A list of RGB tuples (and optional gamma(s)). |
| 21 | +# In the tuple/list cases, the 'inPlace' flag determines whether |
| 22 | +# a new tuple/list is calculated and returned, or the existing |
| 23 | +# value is modified in-place. By default this is 'False'. |
| 24 | +# Can also use the napplyGamma_video() function to more directly |
| 25 | +# approximate FastLED syntax/behavior. |
| 26 | +# RETURNS: Corresponding to above cases: |
| 27 | +# 1. Single gamma-corrected brightness level (0-255). |
| 28 | +# 2. A gamma-corrected RGB tuple (3 values 0-255). |
| 29 | +# 3. A list of gamma-corrected RGB tuples (ea. 3 values 0-255). |
| 30 | +# In the tuple/list cases, there is NO return value if 'inPlace' |
| 31 | +# is true -- the original values are modified. |
| 32 | +def applyGamma_video(n, gR=gfactor, gG=None, gB=None, inPlace=False): |
| 33 | + if isinstance(n, int): |
| 34 | + # Input appears to be a single integer |
| 35 | + result = int(pow(n / 255.0, gR) * 255.0 + 0.5) |
| 36 | + # Never gamma-adjust a positive number down to zero |
| 37 | + if result == 0 and n > 0: result = 1 |
| 38 | + return result |
| 39 | + else: |
| 40 | + # Might be an RGB tuple, or a list of tuples, but |
| 41 | + # isinstance() doesn't seem to distinguish...so try |
| 42 | + # treating it as a list first, and if that fails, |
| 43 | + # fall back on the RGB tuple case. |
| 44 | + try: |
| 45 | + if inPlace: |
| 46 | + for i in range(len(n)): |
| 47 | + n[i] = applyGamma_video(n[i], |
| 48 | + gR, gG, gB) |
| 49 | + else: |
| 50 | + newlist = [] |
| 51 | + for i in n: |
| 52 | + newlist += applyGamma_video(i, |
| 53 | + gR, gG, gB) |
| 54 | + return newlist |
| 55 | + except TypeError: |
| 56 | + if gG is None: gG = gR |
| 57 | + if gB is None: gB = gR |
| 58 | + if inPlace: |
| 59 | + n[0] = applyGamma_video(n[0], gR) |
| 60 | + n[1] = applyGamma_video(n[1], gG) |
| 61 | + n[2] = applyGamma_video(n[2], gB) |
| 62 | + else: |
| 63 | + return [ |
| 64 | + applyGamma_video(n[0], gR), |
| 65 | + applyGamma_video(n[1], gG), |
| 66 | + applyGamma_video(n[2], gB) ] |
| 67 | + |
| 68 | + |
| 69 | +# In-place version of above (to match FastLED function name) |
| 70 | +# This is for RGB tuples and tuple lists (not the above's integer case) |
| 71 | +def napplyGamma_video(n, gR=gfactor, gG=None, gB=None): |
| 72 | + return applyGamma_video(n, gR=gfactor, gG=None, gB=None, inPlace=True) |
| 73 | + |
| 74 | + |
| 75 | +# Sort-of-approximation of FastLED full_gradient_rgb() function. |
| 76 | +# Fills subsection of palette with RGB color range. |
| 77 | +# ACCEPTS: Palette to modify (must be a preallocated list with a suitable |
| 78 | +# number of elements), index of first entry to fill, RGB color of |
| 79 | +# first entry (as RGB tuple), index of last entry to fill, RGB |
| 80 | +# color of last entry (as RGB tuple). |
| 81 | +# RETURNS: Nothing; palette list is modified in-place. |
| 82 | +def fill_gradient_rgb(pal, startpos, startcolor, endpos, endcolor): |
| 83 | + if endpos < startpos: |
| 84 | + startpos , endpos = endpos , startpos |
| 85 | + startcolor, endcolor = endcolor, startcolor |
| 86 | + |
| 87 | + dist = endpos - startpos |
| 88 | + if dist == 0: |
| 89 | + pal[startpos] = startcolor |
| 90 | + return |
| 91 | + |
| 92 | + deltaR = endcolor[0] - startcolor[0] |
| 93 | + deltaG = endcolor[1] - startcolor[1] |
| 94 | + deltaB = endcolor[2] - startcolor[2] |
| 95 | + |
| 96 | + for i in range(dist + 1): |
| 97 | + scale = i / dist |
| 98 | + pal[int(startpos + i)] = [ |
| 99 | + int(startcolor[0] + deltaR * scale), |
| 100 | + int(startcolor[1] + deltaG * scale), |
| 101 | + int(startcolor[2] + deltaB * scale) ] |
| 102 | + |
| 103 | + |
| 104 | +# Kindasorta like FastLED's loadDynamicGradientPalette() function, with |
| 105 | +# some gotchas. |
| 106 | +# ACCEPTS: Gradient palette data as a 'bytes' type (makes it easier to copy |
| 107 | +# over gradient palettes from existing FastLED Arduino sketches)... |
| 108 | +# each palette entry is four bytes: a relative position (0-255) |
| 109 | +# within the overall resulting palette (whatever its size), and |
| 110 | +# 3 values for R, G and B...and a destination palette list which |
| 111 | +# must be preallocated to the desired length (e.g. 16, 32 or 256 |
| 112 | +# elements if following FastLED conventions, but can be other |
| 113 | +# lengths if needed, the palette lookup function doesn't care). |
| 114 | +# RETURNS: Nothing; palette list data is modified in-place. |
| 115 | +def loadDynamicGradientPalette(src, dst): |
| 116 | + palettemaxindex = len(dst) - 1 # Index of last entry in dst list |
| 117 | + startpos = 0 |
| 118 | + startcolor = [ src[1], src[2], src[3] ] |
| 119 | + n = 4 |
| 120 | + while True: |
| 121 | + endpos = src[n] * palettemaxindex / 255 |
| 122 | + endcolor = [ src[n+1], src[n+2], src[n+3] ] |
| 123 | + fill_gradient_rgb(dst, startpos, startcolor, endpos, endcolor) |
| 124 | + if src[n] >= 255: break # Done! |
| 125 | + n += 4 |
| 126 | + startpos = endpos |
| 127 | + startcolor = endcolor |
| 128 | + |
| 129 | + |
| 130 | +# Approximates the FastLED ColorFromPalette() function |
| 131 | +# ACCEPTS: color palette (list of ints or tuples), palette index (x16) + |
| 132 | +# blend factor of next index (0-15) -- e.g. pass 32 to retrieve palette |
| 133 | +# index 2, or 40 for an interpolated value between palette index 2 and 3, |
| 134 | +# optional brightness (0-255), optiona blend flag (True/False) |
| 135 | +# RETURNS: single RGB tuple (3 values 0-255, no gamma correction) |
| 136 | +def ColorFromPalette(pal, index, brightness=255, blend=False): |
| 137 | + lo4 = index & 0xF # 0-15 blend factor to next palette entry |
| 138 | + hi4 = (index >> 4) % len(pal) |
| 139 | + c = pal[hi4] |
| 140 | + hi4 = (hi4 + 1) % len(pal) |
| 141 | + if isinstance(pal[0], int): |
| 142 | + # Color palette is in packed integer format |
| 143 | + r1 = (c >> 16) & 0xFF |
| 144 | + g1 = (c >> 8) & 0xFF |
| 145 | + b1 = c & 0xFF |
| 146 | + c = pal[hi4] |
| 147 | + r2 = (c >> 16) & 0xFF |
| 148 | + g2 = (c >> 8) & 0xFF |
| 149 | + b2 = c & 0xFF |
| 150 | + else: |
| 151 | + # Color palette is in RGB tuple format |
| 152 | + r1 = c[0] |
| 153 | + g1 = c[1] |
| 154 | + b1 = c[2] |
| 155 | + c = pal[hi4] |
| 156 | + r2 = c[0] |
| 157 | + g2 = c[1] |
| 158 | + b2 = c[2] |
| 159 | + |
| 160 | + if blend and lo4 > 0: |
| 161 | + a2 = (lo4 * 0x11) + 1 # upper weighting 1-256 |
| 162 | + a1 = 257 - a2 # lower weighting 1-256 |
| 163 | + if brightness == 255: |
| 164 | + return [(r1 * a1 + r2 * a2) >> 8, |
| 165 | + (g1 * a1 + g2 * a2) >> 8, |
| 166 | + (b1 * a1 + b2 * a2) >> 8] |
| 167 | + else: |
| 168 | + brightness += 1 # 1-256 |
| 169 | + return [(r1 * a1 + r2 * a2) * brightness >> 16, |
| 170 | + (g1 * a1 + g2 * a2) * brightness >> 16, |
| 171 | + (b1 * a1 + b2 * a2) * brightness >> 16] |
| 172 | + else: |
| 173 | + if brightness == 255: |
| 174 | + return [ r1, g1, b1 ] |
| 175 | + else: |
| 176 | + brightness += 1 |
| 177 | + return [(r1 * brightness) >> 8, |
| 178 | + (g1 * brightness) >> 8, |
| 179 | + (b1 * brightness) >> 8] |
| 180 | + |
| 181 | + |
| 182 | +# This is named the same thing as FastLED's simpler HSV to RGB function |
| 183 | +# (spectrum, vs rainbow) but implementation is a bit different for the |
| 184 | +# sake of getting something running (adapted from some NeoPixel code). |
| 185 | +# ACCEPTS: HSV color as a 3-element tuple [hue, saturation, value], each |
| 186 | +# in the range 0 to 255. |
| 187 | +# RETURNS: RGB color as a 3-element tuple [R, G, B] |
| 188 | +def hsv2rgb_spectrum(hsv): |
| 189 | + h = hsv[0] * 6.0 / 256.0 # 0.0 to <6.0 |
| 190 | + s = int(h) # Sextant number; 0 to 5 |
| 191 | + n = int((h - s) * 255) # 0-254 within sextant (NOT 255!) |
| 192 | + |
| 193 | + if s == 0: r, g, b = 255 , n , 0 # R to Y |
| 194 | + elif s == 1: r, g, b = 254-n, 255 , 0 # Y to G |
| 195 | + elif s == 2: r, g, b = 0 , 255 , n # G to C |
| 196 | + elif s == 3: r, g, b = 0 , 254-n, 255 # C to B |
| 197 | + elif s == 4: r, g, b = n , 0 , 255 # B to M |
| 198 | + else: r, g, b = 255 , 0 , 254-n # M to R |
| 199 | + |
| 200 | + v1 = 1 + hsv[2] # value 1 to 256; allows >>8 instead of /255 |
| 201 | + s1 = 1 + hsv[1] # saturation 1 to 256; same reason |
| 202 | + s2 = 255 - hsv[1] # 255 to 0 |
| 203 | + |
| 204 | + return [ (((((r * s1) >> 8) + s2) * v1) >> 8) & 0xFF, |
| 205 | + (((((g * s1) >> 8) + s2) * v1) >> 8) & 0xFF, |
| 206 | + (((((b * s1) >> 8) + s2) * v1) >> 8) & 0xFF ] |
0 commit comments