Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explain how rainbowCycle works? #40

Open
emdeex opened this issue Mar 5, 2020 · 1 comment
Open

Explain how rainbowCycle works? #40

emdeex opened this issue Mar 5, 2020 · 1 comment
Labels
notice Issues that are solved/do not require input, but preserved and marked of interest to users.

Comments

@emdeex
Copy link

emdeex commented Mar 5, 2020

I'm loving playing with rainbowCycle, and its working fine.... but can anybody explain (for dummies) exactly what's going on with the formulas in the code?

I see it iterates through each neopixel, and uses the wheel function to set the color... but can you breakdown the formula like at strip.setPixelColor(i, wheel((int(i * 256 / strip.numPixels()) + j) & 255))? And how do the wait_ms and iterations variables interact with it?

def rainbowCycle(strip, wait_ms=20, iterations=5):
    """Draw rainbow that uniformly distributes itself across all pixels."""
    for j in range(256 * iterations):
        for i in range(strip.numPixels()):
            strip.setPixelColor(i, wheel(
                (int(i * 256 / strip.numPixels()) + j) & 255))
        strip.show()
        time.sleep(wait_ms / 1000.0)
@Gadgetoid
Copy link
Member

This is probably the worst code you could learn from, since it's fairly contrived, ported from what I assume was code originally written for very limited microcontrollers, and thus completely disconnected from how any normal Python code might work.

The majority of noteworthy code lives in the wheel function, which demonstrates a crude method of turning a number from 0-255 into an RGB colour value roughly approximating an imaginary position in a "rainbow".

def wheel(pos):
    """Generate rainbow colors across 0-255 positions."""
    if pos < 85:
        return Color(pos * 3, 255 - pos * 3, 0)
    elif pos < 170:
        pos -= 85
        return Color(255 - pos * 3, 0, pos * 3)
    else:
        pos -= 170
        return Color(0, pos * 3, 255 - pos * 3)

This is crude because colour theory already gives us the HSV (Hue, Saturation, Value) colourspace where H or "Hue" maps roughly to what you might consider a colour in a rainbow. I wrote something about this years ago which you might find useful- https://learn.pimoroni.com//tutorial/unicorn-hat/making-rainbows-with-unicorn-hat

Python has a colorsys library that makes converting from RGB to HSV and back again pretty trivial. On a microcontroller the code above might be significantly more efficient, but on desktop Python there is no earthly reason why you'd use code as contrived as that above, in lieu of just doing colourspace conversions.

Broadly any "formula" for lighting a single LED should only ever accept the input values: "LED Index" and "Time" and only ever output one colour value. A rainbow would thus be produced either physically across the LED strip by driving the "Hue" (and thus the RGB value) from the "LED Index" or temporally over a period of time by deriving the "Hue" from "Time".

Things like the rainbow example in our Plasma library do this: https://github.com/pimoroni/plasma/blob/master/examples/rainbow.py

And a very extreme example from Unicorn HAT HD shows a range of functions which use just the X/Y coordinate from a square RGB display, and the "step" (which is just time, effectively) to create detailed demonscene style effects: https://github.com/pimoroni/unicorn-hat-hd/blob/master/examples/demo.py

This is extremely rough and untested and includes no strip setup, but the concise, Python way of doing rainbowCycle would look something like this:

import colorsys
import time

NUM_PIXELS = 10

def hue_to_rgb(h):
    """Convert a hue into an r, g, b color"""
    return [int(c * 255) for c in colorsys.hsv_to_rgb(h, 1.0, 1.0)]

while True:
    t = time.time() / 100  # Get the current timestep

    # Iterate through each pixel
    for i in range(NUM_PIXELS):
        # Spread the Hue range over the pixels, but also cycle it over time
        r, g, b = hue_to_rgb(t + i / NUM_PIXELS)
        strip.setPixelColor(i, Color(r, g, b))

    strip.show()
    time.sleep(1.0 / 60)  # Aim for ~60FPS

@Gadgetoid Gadgetoid added the notice Issues that are solved/do not require input, but preserved and marked of interest to users. label May 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
notice Issues that are solved/do not require input, but preserved and marked of interest to users.
Projects
None yet
Development

No branches or pull requests

2 participants