flowing_colors.py (6961B)
1 #!/usr/bin/env python3 2 3 from rpi_ws281x import Color 4 import random 5 import math 6 import time 7 8 import patterns 9 10 # Utilitiy classes and functions 11 12 13 class RandomWalker(): 14 """Walk towards random target values 15 16 Returns fixed value if min==max.""" 17 18 def __init__(self, min, max, step): 19 self.min = min 20 self.max = max 21 self.step = step 22 if self.min == self.max: 23 self.val = self.min 24 self.target = self.min 25 self.val = random.uniform(self.min, self.max) 26 self.target = random.uniform(self.min, self.max) 27 28 def next(self): 29 if self.min != self.max: 30 if abs(self.val - self.target) <= self.step*2: 31 self.target = random.uniform(self.min, self.max) 32 if self.val <= self.target: 33 self.val += self.step 34 else: 35 self.val -= self.step 36 return self.val 37 38 39 sin_norm_cache = {} 40 def sin_norm(x, m_in, m_out): 41 """Normalized sinus function: x in range [0..m_in] mapped to [0..m_out] 42 43 x values > min are reduced mod m_in+1. 44 """ 45 if (x % (m_in + 1), m_in, m_out) in sin_norm_cache: 46 return(sin_norm_cache[(x % (m_in + 1), m_in, m_out)]) 47 else: 48 rad = (float(x % (m_in + 1)) / (m_in + 1) * (2 * math.pi)) - math.pi 49 out_percent = (math.sin(rad) + 1) / 2.0 50 res = int(out_percent * m_out) 51 sin_norm_cache.update({(x % (m_in + 1), m_in, m_out): res}) 52 return(res) 53 54 55 def hsv2rgb(h, s, v): 56 if h == None: 57 return (0, 0, 0) 58 else: 59 hd = h / 60.0 60 c = s * v 61 x = c * (1 - abs((hd % 2) - 1)) 62 if 0 <= hd <= 1: 63 (r, g, b) = (c, x, 0) 64 elif 1 < hd <= 2: 65 (r, g, b) = (x, c, 0) 66 elif 2 < hd <= 3: 67 (r, g, b) = (0, c, x) 68 elif 3 < hd <= 4: 69 (r, g, b) = (0, x, c) 70 elif 4 < hd <= 5: 71 (r, g, b) = (x, 0, c) 72 elif 5 < hd <= 6: 73 (r, g, b) = (c, 0, x) 74 m = v - c 75 return map(lambda x: x*0xFF, (r+m, g+m, b+m)) 76 77 78 # Pattern classes 79 80 81 class FlowingColorsRGB(patterns.Pattern): 82 83 def __init__(self, strip, led_matrix_width, led_matrix_height, led_strip_snake, opts=None): 84 patterns.Pattern.__init__(self, strip=strip, led_matrix_width=led_matrix_width, led_matrix_height=led_matrix_height, led_strip_snake = led_strip_snake, opts=opts) 85 self.max_period = int(min(self.led_matrix_width, self.led_matrix_height)*2.0) 86 self.min_period = int(min(self.led_matrix_width, self.led_matrix_height)*1.0) 87 self.min_offset = 0 88 self.max_offset = int(max(self.led_matrix_width, self.led_matrix_height)*1.0) 89 # For each color channel, the period and the offset of the sinus function 90 # mapping (x/y) values to color values changes in a random walk. 91 self.channels = [ 92 # Hue x period 93 [RandomWalker(min=self.min_period, 94 max=self.max_period, 95 step=self.max_period/100.0), 96 # y period 97 RandomWalker(min=self.min_period, 98 max=self.max_period, 99 step=self.max_period/100.0), 100 # x offset 101 RandomWalker(min=self.min_offset, 102 max=self.max_offset, 103 step=self.max_offset/100.0), 104 # y offset 105 RandomWalker(min=self.min_offset, 106 max=self.max_offset, 107 step=self.max_offset/100.0)] for _ in range(3)] 108 109 def step(self): 110 # Change period and offset 111 for c in self.channels: 112 for w in c: 113 w.next() 114 r = self.sin_x_y_avg(period_offset=self.channels[0], m_out=255) 115 g = self.sin_x_y_avg(period_offset=self.channels[1], m_out=255) 116 b = self.sin_x_y_avg(period_offset=self.channels[2], m_out=255) 117 for x in range(math.ceil(self.led_matrix_width)): 118 for y in range((self.led_matrix_height)): 119 self.strip.setPixelColor(self.xy2i(x, y), 120 Color(int(r[x][y]), int(g[x][y]), int(b[x][y]))) 121 self.strip.show() 122 # time.sleep(.1) 123 124 def sin_x_y_avg(self, period_offset, m_out): 125 # period_offset are passed as instances of RandomWalker. We have to get the 126 # values. 127 (x_period, y_period, x_offset, y_offset) = map(lambda x: x.val, period_offset) 128 pixel = [[None for _ in range(math.ceil(self.led_matrix_height))] for _ in range(math.ceil(self.led_matrix_width))] 129 for x in range(math.ceil(self.led_matrix_width)): 130 for y in range(math.ceil(self.led_matrix_height)): 131 pixel_x = sin_norm(x=x+x_offset, m_in=1.0*x_period, m_out=m_out) 132 pixel_y = sin_norm(x=y+y_offset, m_in=1.0*y_period, m_out=m_out) 133 pixel[x][y] = (pixel_x + pixel_y) / 2 134 return pixel 135 136 137 class FlowingColorsHSV(FlowingColorsRGB): 138 def __init__(self, strip, led_matrix_width, led_matrix_height, led_strip_snake, opts=None): 139 patterns.Pattern.__init__(self, strip=strip, led_matrix_width=led_matrix_width, led_matrix_height=led_matrix_height, led_strip_snake = led_strip_snake, opts=opts) 140 self.max_period = int(min(self.led_matrix_width, self.led_matrix_height)*3.0) 141 self.min_period = int(min(self.led_matrix_width, self.led_matrix_height)*0.5) 142 self.min_offset = 0 143 self.max_offset = int(max(self.led_matrix_width, self.led_matrix_height)*20.0) 144 self.hue_sin = [RandomWalker(min=self.min_period, 145 max=self.max_period, 146 step=self.max_period/1000.0), 147 # y period 148 RandomWalker(min=self.min_period, 149 max=self.max_period, 150 step=self.max_period/1000.0), 151 # x offset 152 RandomWalker(min=self.min_offset, 153 max=self.max_offset, 154 step=self.max_offset/10000.0), 155 # y offset 156 RandomWalker(min=self.min_offset, 157 max=self.max_offset, 158 step=self.max_offset/10000.0)] 159 self.hue_offset = RandomWalker(min=0, max=360, step=1) 160 161 def step(self): 162 for w in self.hue_sin: 163 w.next() 164 self.hue_offset.next() 165 h = self.sin_x_y_avg(period_offset=self.hue_sin, m_out=360) 166 for x in range(math.ceil(self.led_matrix_width)): 167 for y in range((self.led_matrix_height)): 168 (r, g, b) = hsv2rgb((h[x][y]+self.hue_offset.val) % 360, 1, 1) 169 self.strip.setPixelColor(self.xy2i(x, y), Color(int(r), int(g), int(b))) 170 self.strip.show() 171 # time.sleep(.1)