Nixx
Nixx

Reputation: 15

Generate color fades via (B)ASH

I have a linux/bash script that sends multiple RGB color fades to multiple light. Every light can have it's own color but all colors should use the same amount of time to fade in/out. So it sends out a sequence of 3 differently targeted value-ranges depending on the initial values and based on a speed multiplier.

The problem I run into is that colors are defined by 3 channels Red, Green & Blue (0-255) And depending on the targeted color it could mean that one channel has a value of 10 and the other of 230. And for it to work smoothly I need them to start at 0 and finish at their maximum in the same amount of time/steps. To make it more problematic is the fact that I cannot use values as 0.112. It needs to be either 0 or 1.

At the moment I've been able to solve this problem by limiting the amount of colors I use and only set "half-range" values per channel. eg. R255 G128 B000. In this way I've been able to make it work (within an acceptable margin of inaccuracy.) for each channel I've made a separate base-multiplier that will influence the speed of each channel fading in/out (so for 255 2x, for 128 1x, for 000, 0x)

Since I have 3 lights and I only want to send out the value sequence to the lights that I need I also add up the 3 RGB values to see if the equal zero, if so, that assigned light will not be triggered.

[Q] Can somebody help me to optimise this script so it could work with all RGB values and I would also be able to make it fade between colors. The most important conditions will be that I need to be able to apply a global speed multiplier and the can not be values behind the comma.

Below is the script what I've made so far. I've taken out the duplicates for the other lights as it's basically copy-paste of the same lines, but with other names.

maxR=000, maxG=000, maxB=255, speed=10, 
toggle=$(($maxR + $maxG + $maxB))
subR=$(echo $maxR | cut -c-1), subG=$(echo $maxG | cut -c-1), subB=$(echo $maxB | cut -c-1), 
mulR=$(($subR*$speed)), mulG=$(($subG*$speed)), mulB=$(($subB*$speed))

count=0

while [ $count -lt 3 ] ; do
    count=$(($count +1))
    while [ $varR -lt $maxR ] || [ $valG -lt $maxG ] || [ $valB -lt $maxB ] ; do
        [[ $toggle -gt 0 ]] && $valR to red && $valG to green && $valB to blue
        varR=$(($varR +$mulR))
        valG=$(($valG +$mulG))
        valB=$(($valB +$mulB))
    done

    sleep 1

    while [ $varR -gt 0 ] || [ $valG -gt 0 ] || [ $valB -gt 0 ] ; do
        [[ $toggle -gt 0 ]] && $valR to red && $valG to green && $valB to blue
        varR=$(($varR -$mulR))
        valG=$(($valG -$mulG))
        valB=$(($valB -$mulB)) 
    done

    sleep 3
done

Upvotes: 0

Views: 1222

Answers (2)

Javier Larios
Javier Larios

Reputation: 312

Yesterday I implemented something similar to what you are asking for, for this I decomposed the RGB colors to independently handle the transition.

For the transition I use the vector equation of the line.

r = p0 + tv

Suppose I want to transition from green (00ff00) to magenta (ff00ff)

So the starting point is the green color, p0 = (0, 255, 0)

then you need to find the director vector (v), for that you simply make a vector subtraction between the end point and the starting point

v = pf - p0

Where pf es the magenta color (255, 0, 255) (the final point)

Finally, the value of "t" is a floating point number, which indicates the number of steps you want to perform between one color and the other, this value is between 0 and 1, for example if you want the transition to occur in 255 steps, then divide 1/255

You must take into account that bash scripting does not support floating point operations, for this, you must use tools that allow you to do this type of operations, such as: bc or python or another

t=$(echo 'print(1/255)' | python3)

To calculate the value of r, in a particular coordinate (R,G,B) I used the following function with the help of the bc command

ecVec(){
    echo $(echo "scale=2;$1+$2*$3" | bc)
}

Finally, you have to do the transition cycle (with a for loop for example), calling de ecVec function for every component (Red, Green, Blue)

for i in $(seq -f "%03g" 1 255)
do  
        tInc=$(bc <<< "$i * $t")
        tIncRounded=$(printf "%.3f\n" $tInc)

        qRed="$(ecVec $iR $vR $tIncRounded)"
        qRedRounded=$(printf "%.0f" $qRed;)

        qGreen="$(ecVec $iG $vG $tIncRounded)"
        qGreenRounded=$(printf "%.0f" $qGreen;)

        qBlue="$(ecVec $iB $vB $tIncRounded)"
        qBlueRounded=$(printf "%.0f" $qBlue;)

        setColor $qRedRounded $qGreenRounded $qBlueRounded $LED 

done

I use the setColor function to visualize (output) the color transition

Note that I also used rounding, since the RGB color model only supports Integer numbers

That was my result

DualShock4 rainbow

Upvotes: 1

Dennis Williamson
Dennis Williamson

Reputation: 360485

This script generates sequences of RGB values that represent smoothly fading colors from any arbitrary color to any other. It uses only eight bit integers (Ash and Bash can only do integer math, although they are capable of working with larger values, of course).

Fades can be in any direction. Red might go from a small value to a large value while blue might go large to small, for example.

The minimum number of steps is taken between two colors. A step value and a sleep value can be passed to control the rate at which the fade occurs.

There is a function to directly set colors.

Some additional information is contained in comments.

There is some demo code at the end. It outputs a lot of text (the set_color() function uses echo as a placeholder). Use less or another pager so you can see the output and scroll through it:

./fader | less

Replace the echo with the command that actually sets the color.

#!/usr/local/bin/ash
#
# by Dennis Williamson - 2012-07-26
# for http://stackoverflow.com/questions/11594670/generate-color-fades-via-bash
#
# fade smoothly from any arbitrary color to another
#
# no values are range checked, but this feature could be easily added
#

set_color () {
    local light=$1
    red=$2 green=$3 blue=$4 # global - see below
    # placeholder for the command that sets led color on a particular light
    echo "$light, $red, $green, $blue"
}

abs () {
    if [ $1 -lt 0 ]
    then
        echo "$(($1 * -1))"
    else
        echo "$1"
    fi
}

max () {
    local arg max=0
    for arg
    do
        if [ "$arg" -gt "$max" ]
        then
            max=$arg
        fi
    done
    echo "$max"
}

fade () {
    local light=$1 step=$2 sleep=$3
    local start_red=$4 start_green=$5 start_blue=$6
    local end_red=$7 end_green=$8 end_blue=$9
    local delta_red=$(abs $(($end_red - $start_red)) )
    local delta_green=$(abs $(($end_green - $start_green)) )
    local delta_blue=$(abs $(($end_blue - $start_blue)) )
    local i=0
    local max=$(max "$delta_red" "$delta_green" "$delta_blue")

    if [ "$delta_red" = 0 ] && [ "$delta_green" = 0 ] && [ "$delta_blue" = 0 ]
    then
        return
    fi

    if [ "$step" -lt 1 ]
    then
        step=1
    fi

    while [ "$i" -le "$max" ]
    do

        red=$(( $start_red + ($end_red - $start_red)  * $i / $max ))
        green=$(( $start_green + ($end_green - $start_green) * $i / $max ))
        blue=$(( $start_blue + ($end_blue - $start_blue) * $i / $max))
        set_color "$light" "$red" "$green" "$blue"
        sleep $sleep
        i=$(($i + $step))
    done
    # $red, $green and $blue are global variables and will be available to the
    # caller after this function exits. The values may differ from the input
    # end values if a step other than one is chosen. Because of that, these
    # values are useful for subsequent calls as new start values to continue
    # an earlier fade or to reverse fade back to a previous start value.
}

# demos (produces a lot of output)

# fade LED, step, sleep, start R G B, end R G B
fade one 3 0 100 200 15 154 144 200
fade one 3 0 "$red" "$green" "$blue" 100 200 15
fade two 1 1 30 40 50 70 20 10
fade three 1 0 0 255 0 0 0 255

i=0
while [ "$i" -lt 10 ]
do
    set_color one 255 0 0
    set_color two 0 255 0
    set_color three 0 0 255
    sleep 2
    set_color one 0 255 0
    set_color two 0 0 255
    set_color three 255 0 0
    sleep 2
    set_color one 0 0 255
    set_color two 255 0 0
    set_color three 0 255 0
    sleep 2
    i=$(($i + 1))
done

Upvotes: 1

Related Questions