5 Replies - 8499 Views - Last Post: 20 December 2017 - 05:32 PM

#1 DK3250   User is offline

  • Pythonian
  • member icon

Reputation: 507
  • View blog
  • Posts: 1,597
  • Joined: 27-December 13

Color Gradient depending of Single Value input

Posted 29 January 2017 - 09:07 AM

Color Gradient depending of Single Value input

Very often I have needed to assign different colors based on a single integer value. You may have a values in the range 0-100 (or any other range) and you want to distribute rainbow colors (or any other color palete) depending of the actual value.

To do this easily, I have made this little Gradient class:
import pygame, math, sys
pygame.init()

X = 900  # screen width
Y = 600  # screen height

screen = pygame.display.set_mode((X, Y))

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 50, 50)
YELLOW = (255, 255, 0)
GREEN = (0, 255, 50)
BLUE = (50, 50, 255)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)


class Gradient():
    def __init__(self, palette, maximum):
        self.COLORS = palette
        self.N = len(self.COLORS)
        self.SECTION = maximum // (self.N - 1)

    def gradient(self, x):
        """
        Returns a smooth color profile with only a single input value.
        The color scheme is determinated by the list 'self.COLORS'
        """
        i = x // self.SECTION
        fraction = (x % self.SECTION) / self.SECTION
        c1 = self.COLORS[i % self.N]
        c2 = self.COLORS[(i+1) % self.N]
        col = [0, 0, 0]
        for k in range(3):
            col[k] = (c2[k] - c1[k]) * fraction + c1[k]
        return col


COLORS = [MAGENTA, RED, YELLOW, GREEN, CYAN, BLUE]
xxcolor = Gradient(COLORS, 900).gradient
xcolor = Gradient([BLACK, WHITE], 100).gradient

def distance():
    x, y = pygame.mouse.get_pos()
    for j in range(-100, 100):
        for i in range(-100, 100):
            d = (j**2 + i**2)**0.5
            if d < 100:
                p = (x + i, y + j)
                color = xcolor(int(d))
                screen.set_at(p, color)


def vertigo():
    for x in range(X):
        pygame.draw.rect(screen, xxcolor(x), (x, 0, 1, Y), 0)


vertigo()
pygame.display.flip()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            vertigo()
            distance()
            pygame.display.flip()



Explanation

The Gradient is instantiated using a list of color-peaks and a maximum value. The maximum value is the largest value we expect to assign to a color.

First the color-peaks are chosen, two or more colors in a list. We will later interpolate between the color-peaks depending of the actual value. The color-peaks are saved in: self.COLORS.
self.N is just a count of color-peaks.
self.SECTION is the length between color-peaks, depending of the declared maximum value.

NOTE:
If later a value higher than the declared maximum is evaluated, no error occurs but the color-palette starts over when actual value exceeds the declared maximum value.
If the real maximum value is smaller than the declared maximum, you don't get to use the full color spectrum.

In the gradient() method first 'i' is calculated, this represents a color peak lower than the value at hand.
Then the 'fraction' is calculated representing the decimal fraction distance from the lower color peak to the next color peak.
The two colors to interpolate between are identified.
In a small loop the three color components (r, g, b ) are interpolated and the result is returned.

Neat, isn't it?

In the code example is shown how two instances of Gradient are made using two different color palettes and different maximum values.

A local variable is assigned to the gradient method of the instance; in this way the local variable can be used similarly as a local function.

The rest of the code is for demonstration purpose only. Run the code and click your mouse randomly in the window.

Is This A Good Question/Topic? 2
  • +

Replies To: Color Gradient depending of Single Value input

#2 baavgai   User is offline

  • Dreaming Coder
  • member icon


Reputation: 7397
  • View blog
  • Posts: 15,330
  • Joined: 16-October 07

Re: Color Gradient depending of Single Value input

Posted 20 December 2017 - 06:56 AM

The gradient thing didn't work for me, in either 2 or 3.

The following bug fix seemed to work:
# fraction = (x % self.SECTION) / self.SECTION
fraction = (x % self.SECTION) / float(self.SECTION)


Was This Post Helpful? 1
  • +
  • -

#3 DK3250   User is offline

  • Pythonian
  • member icon

Reputation: 507
  • View blog
  • Posts: 1,597
  • Joined: 27-December 13

Re: Color Gradient depending of Single Value input

Posted 20 December 2017 - 07:10 AM

Hm, strange, I've just ran the snippet code on 3.4.2 without any problem.
Anyway, thank you for the bug fix; it may help others :bigsmile:
Was This Post Helpful? 1
  • +
  • -

#4 baavgai   User is offline

  • Dreaming Coder
  • member icon


Reputation: 7397
  • View blog
  • Posts: 15,330
  • Joined: 16-October 07

Re: Color Gradient depending of Single Value input

Posted 20 December 2017 - 10:38 AM

Apologies, it did work in Python35, which I thought was my default path. It only tanked on Python27.

I'll admit, I didn't really care for the class when all you're really doing a generating a single function. Rather, you could do the same thing with a function and some closure magic:
def grandient_calc(colors, maximum):
    def f(n, section):
        def g(x):
            rat = (x % section) / float(section)
            chunk = x // section
            c = colors[chunk % n], colors[(chunk+1) % n]
            return ([ (c[1][i] - c[0][i]) * rat + c[0][i] for i in range(3) ])
        return g
    n = len(colors)
    return f(n, maximum // (n - 1))


Was This Post Helpful? 1
  • +
  • -

#5 DK3250   User is offline

  • Pythonian
  • member icon

Reputation: 507
  • View blog
  • Posts: 1,597
  • Joined: 27-December 13

Re: Color Gradient depending of Single Value input

Posted 20 December 2017 - 11:28 AM

Oh, Jesus; is it a private sport of yours to push people to their limits?
I do understand your code - and I have it executing!! But it's certainly not something I would have made on my own.
Until now I have only used closures with generators. I was quite proud that I can do generators on a (semi-)routine basis.
Generating a function in this way I have never seen before.

I still think the gradient is easier to explain with the original code...

Anyway, - thank you for the push. I do appreciate. Really...!
Was This Post Helpful? 0
  • +
  • -

#6 baavgai   User is offline

  • Dreaming Coder
  • member icon


Reputation: 7397
  • View blog
  • Posts: 15,330
  • Joined: 16-October 07

Re: Color Gradient depending of Single Value input

Posted 20 December 2017 - 05:32 PM

Heh, I'm actually doing a whole lot of Javascript / TypeScript stuff lately. When it comes to first-class functions, JS really shines. It's also a rather wonky OO language, so a nice function is where you tend to spend your time.

So, in JS it might look like:
const grandientCalc = (colors, maximum) => {
  const n = colors.length;
  const section = Math.floor(maximum / (n - 1));
  return (x) => {
    const rat = (x % section) / section;
    const f = (x,y) => (y - x) * rat + x;
    const chunk = Math.floor(x / section);
    const c1 = colors[chunk % n];
    const c2 = colors[(chunk + 1) % n];
    return rgb(f(c1.r,c2.r),f(c1.g,c2.g),f(c1.b,c2.B)/>/>).style();
  };
}



I played with this on fiddle: https://jsfiddle.net/xa65p1yj/

It looks oddly low res there, I had better results on a native page. Functions and closures are quite addictive in the right context. They can kind of become immutable classes.

This post has been edited by baavgai: 20 December 2017 - 05:33 PM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1