Jak obliczyć gradient kolorów

1

Dzień dobry,

próbuje napisać skrypt w pythonie który narysuje zadany gradient.
Pisząc skrypt natrafiłem na trudności związane z tym że moim językiem macierzystym jest C++, w nim zrobiłem mapkę wysokości i kilka innych zdań.
Niestety w tym zadaniu jest wymóg aby zrobić skrypt w Pythonie.
Z samym Pythonem radzę sobie mimo trudności jednak mam problem natury merytorycznej.

Nie ummiem wymyślić wzoru, formuły którą pobierjąc wartość v z zakresu od 0.0 do 1.0 wyilicząła by jaki należy teraz podać kolor pixela do rysowania tak aby uszyskać żadany gradient Siedziałem długo na color pickerami, patrzyłem różne modele barw, próbowałem wplatać w funkcje wyliczająca kolory r,b,g pixela funkcje matematyczne typu sinus a teraz nawet próbowałem robić to na masie instrukcji warunkowych. Oczywiście wszystko zawiodło, dodatkowym problemem jest to że w C++ stosuje obiektowe podejście do programowania a tutaj wymuszono na mnie podejście funkcyjne - sprawia mi to dodatkowa trudność bo nie jestem przyzwyczajony do tego sposobu myślenia o kodzie.

Jakiej pomocy oczekuje ale wezmę wszystko:
Chciałbym abyście naprowadzili mnie na wzór matematyczny który pozwoli uzyskać taki gradient jaki jest na obrazku w załączniku.
Jeśli w systemie RGB jest taki wzór problemem to napisałem w c++ funkcje która przelicza HSL czy HSV do systemu RGB, mogę ją zaimplementować w Python.

Cel - jak ma wyglądać drugi gradient od góry:
gradient cel.png

Obecny "efekt"
eket.png

Kod źródłowy:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import division             
import matplotlib
import math
import os
matplotlib.use('Agg')                      
import matplotlib.pyplot as plt
from matplotlib import rc
import numpy as np

from matplotlib import colors
krok = 0
def plot_color_gradients(gradients, names):

    rc('legend', fontsize=10)

    column_width_pt = 400        
    pt_per_inch = 72
    size = column_width_pt / pt_per_inch

    fig, axes = plt.subplots(nrows=len(gradients), sharex=True, figsize=(size, 0.75 * size))
    fig.subplots_adjust(top=1.00, bottom=0.05, left=0.25, right=0.95)


    for ax, gradient, name in zip(axes, gradients, names):
       
        img = np.zeros((2, 1024, 3))
        for i, v in enumerate(np.linspace(0, 1, 1024)):
            img[:, i] = gradient(v)

        im = ax.imshow(img, aspect='auto')
        im.set_extent([0, 1, 0, 1])
        ax.yaxis.set_visible(False)

        pos = list(ax.get_position().bounds)
        x_text = pos[0] - 0.25
        y_text = pos[1] + pos[3]/2.
        fig.text(x_text, y_text, name, va='center', ha='left', fontsize=10)

    fig.savefig('my-gradients.pdf')

def hsv2rgb(h, s, v):
    #
    return (h, s, v)

def gradient_rgb_bw(v):
    #gotowe
    return (v,v ,v)

r = 0
g = 255
b = 0
def gradient_rgb_gbr(v):
    
    global r
    global g 
    global b 

    g = g - 0.5
    if(g <= 0):g=0

    if(v >0.2 and r==0):
        b = b+ 0.5

    if( b > 255):b=255

    if (v > 0.55):r = r +0.5 #to niest na ifach juz z desperacji:(
    print(r,g,b)

    


    return (  int(r),  int(g),  int(b) )

def gradient_rgb_gbr_full(v):
    
    return (v, v, 0)


def gradient_rgb_wb_custom(v):
    
    return (0, 0, 0)


def gradient_hsv_bw(v):
    
    return hsv2rgb(0, 0, 0)


def gradient_hsv_gbr(v):
    
    return hsv2rgb(0, 0, 0)

def gradient_hsv_unknown(v):
    
    return hsv2rgb(0, 0, 0)


def gradient_hsv_custom(v):
    
    return hsv2rgb(0, 0, 0)


if __name__ == '__main__':
    def toname(g):
        return g.__name__.replace('gradient_', '').replace('_', '-').upper()

    gradients = (gradient_rgb_bw, gradient_rgb_gbr, gradient_rgb_gbr_full, gradient_rgb_wb_custom,
                 gradient_hsv_bw, gradient_hsv_gbr, gradient_hsv_unknown, gradient_hsv_custom)

    plot_color_gradients(gradients, [toname(g) for g in gradients])

Pozdrawiam

1

Z tego co widzę, żadnych sinusów ani sztuczek tutaj nie potrzeba - taki gradient to zwyczajna liniowa interpolacja (osobno dla każdego kanału) :-)

Czyli gradient z jednego koloru na drugi to będzie coś w stylu (pisane z palca):

fn lerp(Color from, Color to, float progress) -> Color
  return Color {
      r: lerp(from.r, to.r, progress),
      g: lerp(from.g, to.g, progress),
      b: lerp(from.b, to.b, progress),
  }

fn lerp(float from, float to, float progress) -> float
  return from + (to - from) * progress

Podobnie będzie to wyglądało dla n kolorów.

Btw, z tymi zmiennymi globalnymi to tak średnio funkcyjne Twoje podejście :-P

1
Patryk27 napisał(a):
fn lerp(float from, float to, float progress) -> float
  return min(from, to) + abs(from - to) * progress

lerp(1.0, 0.0, 0.3) == lerp(0.0, 1.0, 0.3), a nie powinno ;)
Bo jak idziemy od 1.0 do 0.0 o 0.3, to mamy 0.7.
A jak idziemy od 0.0 do 1.0 o 0.3, to mamy 0.3.

0

Dziękuje za odpowiedzi.

Zaimplementowałem funkcje @Patryk27

def lerp(_from, _to,  _progress):
  return _from + (_to - _from) * _progress

Opracowałem wcześniej sposób "interpolacji" opartej o dekrementacje :) ale twoja funkcja jest bardziej elegancka. Biere :P

Jak widać na obrazku z załącznika funkcja działa, wartości kanałów r,g,b zmniejszają się by w końcu osiągnąć zadana wartość.
Mimo to python cały blok maluje na biało.

Zawartość funkcji :

def gradient_rgb_gbr(v):
    

    r = lerp(255,128,v)
    g= lerp(255,128,v)
    b= lerp(255,128,v)
    
    print(r,g,b)

    return (int(r),int(g),int(b))

Wypisałem że funkcja zwraca w tym momencie odcienie szarości. Nie rozumiem dlaczego biblioteka rysująca koloruje pasek na biało.

Załóżmy że powyższy problem rozwiąże(my). Jestem w stanie uzyskać odcienie różnych kolorów (powyżej uzyskałem przejście z białego w czarne abstrahując od tego że nie rysuje mi python tego gradnietu).

W tym zadaniu będę musiał od początku do ok 1/3 boxa rysować odcienie zielonego, potem od 1/3 do 2/3 boxa odcienie niebieskiego i od 2/3 do końca odcienie czerwonego.
Moja funkcja przyjmuje parametr v (zakres wartości 0.f do 1.f) i na tej podstawie muszę wiedzieć czy zwracać odcień zielonego, niebieskiego czy czerwonego.

Mam pomysł aby zrobić to za pomocą instrukcji warunkowych i zaraz wypróbuje ten pomysł ale czy tak jest profesjonalnie?

#EDIT

dodałem astype('uint8')

do

im = ax.imshow(img, aspect='auto')

także teraz jest

im = ax.imshow(img.astype('uint8'), aspect='auto')
0
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import division             
import matplotlib
import math
import os
matplotlib.use('Agg')                      
import matplotlib.pyplot as plt
from matplotlib import rc
import numpy as np

from matplotlib import colors
krok = 0
def plot_color_gradients(gradients, names):

    rc('legend', fontsize=10)

    column_width_pt = 400        
    pt_per_inch = 72
    size = column_width_pt / pt_per_inch

    fig, axes = plt.subplots(nrows=len(gradients), sharex=True, figsize=(size, 0.75 * size))
    fig.subplots_adjust(top=1.00, bottom=0.05, left=0.25, right=0.95)


    for ax, gradient, name in zip(axes, gradients, names):
       
        img = np.zeros((2, 1024, 3))
        for i, v in enumerate(np.linspace(0, 1, 1024)):
            img[:, i] = gradient(v)

        im = ax.imshow(img.astype('uint8'), aspect='auto')
        #plt.imshow(out.astype('uint8'))
        im.set_extent([0, 1, 0, 1])
        ax.yaxis.set_visible(False)

        pos = list(ax.get_position().bounds)
        x_text = pos[0] - 0.25
        y_text = pos[1] + pos[3]/2.
        fig.text(x_text, y_text, name, va='center', ha='left', fontsize=10)

    fig.savefig('my-gradients.pdf')

def hsv2rgb(h, s, v):
    #
    return (h, s, v)

def gradient_rgb_bw(v):
    #gotowe
    return (v,v ,v)

def gradient_rgb_gbr(v):
    
    r = 0
    g = 0
    b = 0

    if(v >0 and v < 0.3):
        r = lerp(0,130,v*4)
        g= lerp(255,255,0)
        b= lerp(0,130,v*4)

    if(v >=0.3 and v < 0.5):
        r = lerp(200,0,(v-0.3)*4)
        g= lerp(200,0,(v-0.3)*4)
        b= lerp(255,255,1)

    if(v >=0.5 and v < 0.7):
        r = lerp(0,200,(v-0.5)*4)
        g= lerp(0,200,(v-0.5)*4)
        b= lerp(255,255,1)
    if(v >=0.7):
        r = lerp(255,255,1)
        g= lerp(200,0,(v-0.7)*3)
        b= lerp(200,0,(v-0.7)*3)

    

    return (int(r),int(g),int(b))

def gradient_rgb_gbr_full(v):
    
    return (v, v, 0)


def gradient_rgb_wb_custom(v):
    
    return (0, 0, 0)


def gradient_hsv_bw(v):
    
    return hsv2rgb(0, 0, 0)


def gradient_hsv_gbr(v):
    
    return hsv2rgb(0, 0, 0)

def gradient_hsv_unknown(v):
    
    return hsv2rgb(0, 0, 0)


def gradient_hsv_custom(v):
    
    return hsv2rgb(0, 0, 0)

def lerp(_from, _to,  _progress):
  return _from + (_to - _from) * _progress


if __name__ == '__main__':
    def toname(g):
        return g.__name__.replace('gradient_', '').replace('_', '-').upper()

    gradients = (gradient_rgb_bw, gradient_rgb_gbr, gradient_rgb_gbr_full, gradient_rgb_wb_custom,
                 gradient_hsv_bw, gradient_hsv_gbr, gradient_hsv_unknown, gradient_hsv_custom)

    plot_color_gradients(gradients, [toname(g) for g in gradients])

@Patryk27: @Spine

Uzyskałem mniej więcej porzadany efekt, Chciałbym zapytać o funkcje def gradient_rgb_gbr(v): czy w Pythonie można by ją zapisać jakoś lepiej. Pythona dopero się ucze więc nie znam wszystkich jego "technik" czy instrukcji. Jeśli można proszę o podpowiedź.

Pozdrawiam.

1

Na Pythonie znam się so-so, ale generalnie poleciłbym odejście od ręcznie hard-kodowanych ifów na rzecz bardziej abstrakcyjnego designu - w stylu (w pseudokodzie):

struct Color
  int r
  int g
  int b

fn gradient(f32 progress, Color[] colors)
  int idx = lerp(0, colors.len(), progress).roundDown()
  
  Color from = colors[idx]
  Color to = colors[idx + 1]

  ...

/* ... i potem: */

for ...:
  ...
  
  for i, v in enumerate(np.linspace(0, 1, 1024)):
    ...
    
    gradient(progress, [
      Color { ... },
      Color { ... },
      Color { ... },
    ])
0

Możesz porównywać v inaczej:

>>> v = 0.4
>>> 0 < v < 0.3
False
>>> 0.3 <= v < 0.5
True

1 użytkowników online, w tym zalogowanych: 0, gości: 1