Reputation: 41
I'm trying to fade the background color of a Curses subwindow between 2 arbitrary RGB values passed to the setupColor() function in my code.
In some cases, the code behaves as expected and can satisfactorily fade between the colors, but most of the time it performs strangely.
setupColor((0,0,0),(0,255,55))
This will fade the subwindow between black and aqua, and works nicely.
However, if I try to fade between say, yellow and purple, like so:
setupColor((255,200,0),(200,0,200))
This will cycle between the 2 colors for the first few cycles, but seems to lose sync and eventually produces noticeably different colors then those passed to the function.
As I based the original code on the Arduino LED Fade sketch, I searched to see if anyone had tried to do something similar with physical RGB LED's, which turned up this thread: c++ Fade between colors? (Arduino).
The solution posted on the thread seems like it would be ideal for my needs, but I don't know C++ or JavaScript well enough to be able to follow, understand and port the code to Python.
Is it possible to adapt my code in order for the colors to fade properly, or is it worth scrapping it and starting over from scratch?
import curses,time
from curses import wrapper
def setupColor((red,green,blue),(red1,green1,blue1)):
global color1,color2 #initialise globals for use later on in the program.
global incrFloatRed,incrFloatGreen,incrFloatBlue
global minRed,minGreen,minBlue,maxRed,maxGreen,maxBlue
stepNumber = 60.00 # number of steps within each color transition.
color1 = (red,green,blue) # clone the input tuples for use later on...
color2 = (red1,green1,blue1)
differenceRed = red - red1 # subtract each channel of color1 from color2,
differenceGreen = green - green1 # this will return either a positive or negative float.
differenceBlue = blue - blue1
incrFloatRed = differenceRed / stepNumber # divide the difference between the 2 colors by the
incrFloatGreen = differenceGreen / stepNumber # step rate to obtain the color increments.
incrFloatBlue = differenceBlue / stepNumber
if red > red1: # if the red channel value of the 1st color is greater than
incrFloatRed = -incrFloatRed # that of the 2nd, invert the increment (to allow
maxRed = red # color subtraction), then set the top end of the range as
minRed = red1 # red 1st channel and the bottom as red 2nd channel.
else: # Else, perform the inverse operation.
incrFloatRed = abs(incrFloatRed)
maxRed = red1
minRed = red
if green > green1:
incrFloatGreen = -incrFloatGreen
maxGreen = green
minGreen = green1
elif green < green1:
incrFloatGreen = abs(incrFloatGreen)
maxGreen = green1
minGreen = green
if blue > blue1:
incrFloatBlue = -incrFloatBlue
maxBlue = blue
minBlue = blue1
else:
incrFloatBlue = abs(incrFloatBlue)
maxBlue = blue1
minBlue = blue
def main(stdscr):
global incrFloatRed,incrFloatGreen,incrFloatBlue
setupColor((0,0,0),(0,255,255))
red = color1[0] #red,green and blue are the variables used to control the fade.
green = color1[1] #To begin with, they is set to the colors contained in the first
blue = color1[2] #tuple that is passed to setupColor()
label = stdscr.subwin(10,50,1,4) # create a subwindow, draw a box around it, then add the string
label.box() # "Hello, World!" to the subwindow at position row 1, column 1.
label.addstr(1,1,"Hello, World!")
curses.curs_set(0) # Disable cursor visibility
while True:
red = red + incrFloatRed # color = colorValue + colorIncrement
green = green + incrFloatGreen
blue = blue + incrFloatBlue
if red <= minRed or red >= maxRed: # if color is less than the start of the color range or
incrFloatRed = -incrFloatRed # greater than end of the color range, invert the color increment
if green <= minGreen or green >= maxGreen:
incrFloatGreen = -incrFloatGreen
if blue <= minBlue or blue >= maxBlue:
incrFloatBlue = -incrFloatBlue
# curses.init_color takes a tuple of rgb values as it's argument,
cursesRed = int(int(red) / 0.255) # but uses funky 1000-point intensity values, instead of the usual
cursesGreen = int(int(green) / 0.255) # 255. e.g. rgb(1000,0,0) for full intensity red, instead of
cursesBlue = int(int(blue) / 0.255) # rgb(255,0,0).
# To convert between them, divide the integer of the color value float
# by 0.255, then obtain the integer of the resulting float.
if cursesRed >=1000: # Sometimes a color value is set to greater
cursesRed = 1000 # than 1k or less than 0. When a negative value or a value greater
if cursesGreen >=1000: # than 1k is passed to curses.init_color(), it will return ERR and
cursesGreen = 999 # Curses will crash.
if cursesBlue >=1000:
cursesBlue = 999
if cursesRed <=0:
cursesRed = 0
if cursesGreen <=0:
cursesGreen = 0
if cursesBlue <=0:
cursesBlue = 0
curses.init_color(1,cursesRed,cursesGreen,cursesBlue) # reassign Curses color (1) to the RGB1K color of the current step...
curses.init_pair(1,255,1) # then create a color pair with the dynamic value (1) as
# the BG color, and white (255) as the FG.
label.bkgd(curses.color_pair(1)) # set the background of the label subwindow to the current color pair..
label.refresh() # then refresh, so that we can see the change.
time.sleep(0.02) # Take a little snooze, then do it all again.
wrapper(main)
Upvotes: 3
Views: 1154
Reputation: 41
I figured this one out myself in the end, it was a relatively simple (but hacky and inelegant) fix. I printed the output of each of the color channels (variables red,green and blue in the code above) at this point in the program:
if red <= minRed or red >= maxRed:
outFile.write("Red : " + str(int(red)) + "|Green : " + str(int(green)) + "|Blue : " + str(int(blue)) + "\n")
incrFloatRed = -incrFloatRed
This is the output for the first three cycles of the program:
Red : 199|Green : 3 |Blue : 196
Red : 255|Green : 193|Blue : 0
Red : 199|Green : 9 |Blue : 196
Red : 255|Green : 186|Blue : 0
Red : 199|Green : 16 |Blue : 196
Red : 255|Green : 179|Blue : 0
As you can see, the green channel gradually drifts out of sync compared to the others, and the values for red and blue are slightly different to those passed to the setupColor() function.
The color value inaccuracy can be fixed by using if statements to directly set the values:
while True:
red = red + incrFloatRed # color = colorValue + colorIncrement
green = green + incrFloatGreen
blue = blue + incrFloatBlue
##### Add the if statements after this ####
if red < minRed:
red = minRed
if red > maxRed:
red = maxRed
if blue < minBlue:
blue = minBlue
if blue > maxBlue:
blue = maxBlue
if green < minGreen:
green = minGreen
if green > maxGreen:
green = maxGreen
And the timing/synchronization issue can be fixed by using a single if statement to change the color fade direction. Instead of using a statement for each color like this...
if green <= minGreen or green >= maxGreen:
incrFloatGreen = -incrFloatGreen
...set all of them at once like this:
if green <= minGreen or green >=maxGreen:
incrFloatRed = -incrFloatRed
incrFloatGreen = -incrFloatGreen
incrFloatBlue = -incrFloatBlue
The eagle eyed will have probably spotted that if both maxGreen and minGreen are both set to 0 (e.g, rgb(230,0,100) and rgb(100,0,200)), nothing will happen. If you change green to a different color, it'll work fine.
I doubt that it would be hard to add some logic to detect which color channels would work, but considering that this can be avoided simply by passing 1 instead of 0, I've not bothered.
Another good move (efficiency wise) would be to dump all of the fade values into an array, and set the colors based on that, instead of going through the overhead of calculating the values each time.
Upvotes: 1