Ich suche nach einem Algorithmus zum additiven Farbmischen für RGB-Werte.
Ist es so einfach wie das Addieren der RGB-Werte zu maximal 256?
(r1, g1, b1) + (r2, g2, b2) =
(min(r1+r2, 256), min(g1+g2, 256), min(b1+b2, 256))
Ich suche nach einem Algorithmus zum additiven Farbmischen für RGB-Werte.
Ist es so einfach wie das Addieren der RGB-Werte zu maximal 256?
(r1, g1, b1) + (r2, g2, b2) =
(min(r1+r2, 256), min(g1+g2, 256), min(b1+b2, 256))
Antworten:
Es hängt davon ab, was Sie wollen, und es kann hilfreich sein, die Ergebnisse verschiedener Methoden zu sehen.
Falls Sie es wollen
Rot + Schwarz = Rot Rot + Grün = Gelb Rot + Grün + Blau = Weiß Rot + Weiß = Weiß Schwarz + Weiß = Weiß
dann funktioniert das Hinzufügen mit einer Klammer (z. B. min(r1 + r2, 255)
) Dies ähnelt eher dem Lichtmodell, auf das Sie sich bezogen haben.
Falls Sie es wollen
Rot + Schwarz = Dunkelrot Rot + Grün = Dunkelgelb Rot + Grün + Blau = Dunkelgrau Rot + Weiß = Pink Schwarz + Weiß = Grau
Dann müssen Sie die Werte mitteln (z. B. (r1 + r2) / 2
). Dies funktioniert besser zum Aufhellen / Abdunkeln von Farben und zum Erstellen von Verläufen.
Zum Mischen mit Alphakanälen können Sie die folgenden Formeln verwenden:
r = new Color();
r.A = 1 - (1 - fg.A) * (1 - bg.A);
if (r.A < 1.0e-6) return r; // Fully transparent -- R,G,B not important
r.R = fg.R * fg.A / r.A + bg.R * bg.A * (1 - fg.A) / r.A;
r.G = fg.G * fg.A / r.A + bg.G * bg.A * (1 - fg.A) / r.A;
r.B = fg.B * fg.A / r.A + bg.B * bg.A * (1 - fg.A) / r.A;
fg
ist die Lackfarbe. bg
ist der Hintergrund. r
ist die resultierende Farbe. 1.0e-6
ist nur eine sehr kleine Zahl, um Rundungsfehler auszugleichen.
HINWEIS: Alle hier verwendeten Variablen liegen im Bereich [0.0, 1.0]. Sie müssen durch 255 dividieren oder multiplizieren, wenn Sie Werte im Bereich [0, 255] verwenden möchten.
Zum Beispiel 50% Rot über 50% Grün:
// background, 50% green
var bg = new Color { R = 0.00, G = 1.00, B = 0.00, A = 0.50 };
// paint, 50% red
var fg = new Color { R = 1.00, G = 0.00, B = 0.00, A = 0.50 };
// The result
var r = new Color();
r.A = 1 - (1 - fg.A) * (1 - bg.A); // 0.75
r.R = fg.R * fg.A / r.A + bg.R * bg.A * (1 - fg.A) / r.A; // 0.67
r.G = fg.G * fg.A / r.A + bg.G * bg.A * (1 - fg.A) / r.A; // 0.33
r.B = fg.B * fg.A / r.A + bg.B * bg.A * (1 - fg.A) / r.A; // 0.00
Die resultierende Farbe ist: (0.67, 0.33, 0.00, 0.75)
oder 75% braun (oder dunkelorange).
Sie können diese Formeln auch umkehren:
var bg = new Color();
if (1 - fg.A <= 1.0e-6) return null; // No result -- 'fg' is fully opaque
if (r.A - fg.A < -1.0e-6) return null; // No result -- 'fg' can't make the result more transparent
if (r.A - fg.A < 1.0e-6) return bg; // Fully transparent -- R,G,B not important
bg.A = 1 - (1 - r.A) / (1 - fg.A);
bg.R = (r.R * r.A - fg.R * fg.A) / (bg.A * (1 - fg.A));
bg.G = (r.G * r.A - fg.G * fg.A) / (bg.A * (1 - fg.A));
bg.B = (r.B * r.A - fg.B * fg.A) / (bg.A * (1 - fg.A));
oder
var fg = new Color();
if (1 - bg.A <= 1.0e-6) return null; // No result -- 'bg' is fully opaque
if (r.A - bg.A < -1.0e-6) return null; // No result -- 'bg' can't make the result more transparent
if (r.A - bg.A < 1.0e-6) return bg; // Fully transparent -- R,G,B not important
fg.A = 1 - (1 - r.A) / (1 - bg.A);
fg.R = (r.R * r.A - bg.R * bg.A * (1 - fg.A)) / fg.A;
fg.G = (r.G * r.A - bg.G * bg.A * (1 - fg.A)) / fg.A;
fg.B = (r.B * r.A - bg.B * bg.A * (1 - fg.A)) / fg.A;
Die Formeln berechnen, dass die Hintergrund- oder Lackfarbe sein muss, um die gegebene resultierende Farbe zu erzeugen.
Wenn Ihr Hintergrund undurchsichtig ist, ist das Ergebnis auch undurchsichtig. Die Vordergrundfarbe könnte dann einen Wertebereich mit unterschiedlichen Alpha-Werten annehmen. Für jeden Kanal (rot, grün und blau) müssen Sie überprüfen, welcher Bereich von Alphas zu gültigen Werten führt (0 - 1).
Unterhaltsame Tatsache: Computer-RGB-Werte werden aus der Quadratwurzel des Photonenflusses abgeleitet. Als allgemeine Funktion sollte Ihre Mathematik dies berücksichtigen. Die allgemeine Funktion hierfür für einen bestimmten Kanal lautet:
blendColorValue(a, b, t)
return sqrt((1 - t) * a^2 + t * b^2)
Wobei a und b die zu mischenden Farben sind und t eine Zahl von 0-1 ist, die den Punkt in der Mischung darstellt, den Sie zwischen a und b wünschen.
Der Alpha-Kanal ist anders; es repräsentiert nicht die Photonenintensität, sondern nur den Prozentsatz des Hintergrunds, der durchscheinen sollte; Wenn Sie also Alpha-Werte mischen, reicht der lineare Durchschnitt aus:
blendAlphaValue(a, b, t)
return (1-t)*a + t*b;
Um das Mischen von zwei Farben mit diesen beiden Funktionen zu handhaben, sollte der folgende Pseudocode Ihnen gut tun:
blendColors(c1, c2, t)
ret
[r, g, b].each n ->
ret[n] = blendColorValue(c1[n], c2[n], t)
ret.alpha = blendAlphaValue(c1.alpha, c2.alpha, t)
return ret
Im Übrigen sehne ich mich nach einer Programmiersprache und einer Tastatur, die es ermöglichen, Mathematik (oder mehr) sauber darzustellen (das kombinierte Overline-Unicode-Zeichen funktioniert nicht für hochgestellte Zeichen, Symbole und eine Vielzahl anderer Zeichen) und sie richtig zu interpretieren. sqrt ((1-t) * pow (a, 2) + t * pow (b, 2)) liest sich einfach nicht so sauber.
Einige Punkte:
Dies wird geben:
(r1, g1, b1) + (r2, g2, b2) = (min (r1 + r2, 255), min (g1 + g2, 255), min (b1 + b2, 255))
Die "natürliche" Art, Farben zu mischen, besteht darin, den Durchschnitt zu verwenden, und dann brauchen Sie nicht die min:
(r1, g1, b1) + (r2, g2, b2) = ((r1 + r2) / 2, (g1 + g2) / 2, (b1 + b2) / 2)
c1, c2 und Ergebnis - JSONs wie c1 = {r: 0,5, g: 1, b: 0, a: 0,33}
var rgbaSum = function(c1, c2){
var a = c1.a + c2.a*(1-c1.a);
return {
r: (c1.r * c1.a + c2.r * c2.a * (1 - c1.a)) / a,
g: (c1.g * c1.a + c2.g * c2.a * (1 - c1.a)) / a,
b: (c1.b * c1.a + c2.b * c2.a * (1 - c1.a)) / a,
a: a
}
}
rgbaSum(rgbaSum(c1,c2),rgbaSum(c3,c4))
. Wäre das richtig?
PYTHON COLOR MIXING DURCH ADDITION IN CMYK SPACE
Eine Möglichkeit, dies zu tun, besteht darin, zuerst die Farben in das CMYK- Format zu konvertieren, sie dort hinzuzufügen und dann wieder in RGB zu konvertieren .
Hier ist ein Beispielcode in Python:
rgb_scale = 255
cmyk_scale = 100
def rgb_to_cmyk(self,r,g,b):
if (r == 0) and (g == 0) and (b == 0):
# black
return 0, 0, 0, cmyk_scale
# rgb [0,255] -> cmy [0,1]
c = 1 - r / float(rgb_scale)
m = 1 - g / float(rgb_scale)
y = 1 - b / float(rgb_scale)
# extract out k [0,1]
min_cmy = min(c, m, y)
c = (c - min_cmy)
m = (m - min_cmy)
y = (y - min_cmy)
k = min_cmy
# rescale to the range [0,cmyk_scale]
return c*cmyk_scale, m*cmyk_scale, y*cmyk_scale, k*cmyk_scale
def cmyk_to_rgb(self,c,m,y,k):
"""
"""
r = rgb_scale*(1.0-(c+k)/float(cmyk_scale))
g = rgb_scale*(1.0-(m+k)/float(cmyk_scale))
b = rgb_scale*(1.0-(y+k)/float(cmyk_scale))
return r,g,b
def ink_add_for_rgb(self,list_of_colours):
"""input: list of rgb, opacity (r,g,b,o) colours to be added, o acts as weights.
output (r,g,b)
"""
C = 0
M = 0
Y = 0
K = 0
for (r,g,b,o) in list_of_colours:
c,m,y,k = rgb_to_cmyk(r, g, b)
C+= o*c
M+=o*m
Y+=o*y
K+=o*k
return cmyk_to_rgb(C, M, Y, K)
Das Ergebnis Ihrer Frage wäre dann (unter der Annahme einer halben Mischung Ihrer beiden Farben:
r_mix, g_mix, b_mix = ink_add_for_rgb([(r1,g1,b1,0.5),(r2,g2,b2,0.5)])
wo die 0,5er sind, um zu sagen, dass wir 50% der ersten Farbe mit 50% der zweiten Farbe mischen.
Ja, so einfach ist das. Eine andere Möglichkeit besteht darin, den Durchschnitt zu ermitteln (zum Erstellen von Verläufen).
Es hängt wirklich nur von dem Effekt ab, den Sie erzielen möchten.
Wenn Alpha hinzugefügt wird, wird es jedoch kompliziert. Es gibt verschiedene Methoden zum Mischen mit einem Alpha.
Ein Beispiel für eine einfache Alpha-Mischung: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
Hier finden Sie die von Fordi und Markus Jarderot vorgeschlagenen Mischmethoden in einer Python-Funktion, die zwei Farben A und B allmählich mischt oder mischt.
Der "Mix" -Modus ist nützlich, um zwischen zwei Farben zu interpolieren. Der "Misch" -Modus (mit t=0
) ist nützlich, um die resultierende Farbe zu berechnen, wenn eine durchscheinende Farbe auf eine andere (möglicherweise durchscheinende) Farbe gemalt wird. gamma
Korrektur führt zu schöneren Ergebnissen, da berücksichtigt wird, dass die physikalische Lichtintensität und die wahrgenommene Helligkeit (vom Menschen) nicht linear zusammenhängen.
import numpy as np
def mix_colors_rgba(color_a, color_b, mode="mix", t=None, gamma=2.2):
"""
Mix two colors color_a and color_b.
Arguments:
color_a: Real-valued 4-tuple. Foreground color in "blend" mode.
color_b: Real-valued 4-tuple. Background color in "blend" mode.
mode: "mix": Interpolate between two colors.
"blend": Blend two translucent colors.
t: Mixing threshold.
gamma: Parameter to control the gamma correction.
Returns:
rgba: A 4-tuple with the result color.
To reproduce Markus Jarderot's solution:
mix_colors_rgba(a, b, mode="blend", t=0, gamma=1.)
To reproduce Fordi's solution:
mix_colors_rgba(a, b, mode="mix", t=t, gamma=2.)
To compute the RGB color of a translucent color on white background:
mix_colors_rgba(a, [1,1,1,1], mode="blend", t=0, gamma=None)
"""
assert(mode in ("mix", "blend"))
assert(gamma is None or gamma>0)
t = t if t is not None else (0.5 if mode=="mix" else 0.)
t = max(0,min(t,1))
color_a = np.asarray(color_a)
color_b = np.asarray(color_b)
if mode=="mix" and gamma in (1., None):
r, g, b, a = (1-t)*color_a + t*color_b
elif mode=="mix" and gamma > 0:
r,g,b,_ = np.power((1-t)*color_a**gamma + t*color_b**gamma, 1/gamma)
a = (1-t)*color_a[-1] + t*color_b[-1]
elif mode=="blend":
alpha_a = color_a[-1]*(1-t)
a = 1 - (1-alpha_a) * (1-color_b[-1])
s = color_b[-1]*(1-alpha_a)/a
if gamma in (1., None):
r, g, b, _ = (1-s)*color_a + s*color_b
elif gamma > 0:
r, g, b, _ = np.power((1-s)*color_a**gamma + s*color_b**gamma,
1/gamma)
return tuple(np.clip([r,g,b,a], 0, 1))
Sehen Sie unten, wie dies verwendet werden kann. Im "Mix" -Modus stimmen die linken und rechten Farben genau color_a
und überein color_b
. Im "Mischmodus" ist die linke Farbe bei t=0
die Farbe, die sich ergibt, wenn sie color_a
überblendet wird color_b
(und ein weißer Hintergrund). Im Beispiel wird color_a
dann zunehmend durchscheinend gemacht, bis man ankommt color_b
.
Beachten Sie, dass Mischen und Mischen gleichwertig sind, wenn die Alpha-Werte 1,0 betragen.
Der Vollständigkeit halber hier der Code zur Reproduktion des obigen Diagramms.
import matplotlib.pyplot as plt
import matplotlib as mpl
def plot(pal, ax, title):
n = len(pal)
ax.imshow(np.tile(np.arange(n), [int(n*0.20),1]),
cmap=mpl.colors.ListedColormap(list(pal)),
interpolation="nearest", aspect="auto")
ax.set_xticks([])
ax.set_yticks([])
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.set_title(title)
_, (ax1, ax2, ax3, ax4) = plt.subplots(nrows=4,ncols=1)
n = 101
ts = np.linspace(0,1,n)
color_a = [1.0,0.0,0.0,0.7] # transparent red
color_b = [0.0,0.0,1.0,0.8] # transparent blue
plot([mix_colors_rgba(color_a, color_b, t=t, mode="mix", gamma=None)
for t in ts], ax=ax1, title="Linear mixing")
plot([mix_colors_rgba(color_a, color_b, t=t, mode="mix", gamma=2.2)
for t in ts], ax=ax2, title="Non-linear mixing (gamma=2.2)")
plot([mix_colors_rgba(color_a, color_b, t=t, mode="blend", gamma=None)
for t in ts], ax=ax3, title="Linear blending")
plot([mix_colors_rgba(color_a, color_b, t=t, mode="blend", gamma=2.2)
for t in ts], ax=ax4, title="Non-linear blending (gamma=2.2)")
plt.tight_layout()
plt.show()
Formulas:
Linear mixing (gamma=1):
r,g,b,a: (1-t)*x + t*y
Non-linear mixing (gama≠1):
r,g,b: pow((1-t)*x**gamma + t*y**gamma, 1/gamma)
a: (1-t)*x + t*y
Blending (gamma=1):
a: 1-(1-(1-t)*x)*(1-y)
s: alpha_b*(1-alpha_a)*a
r,g,b: (1-s)*x + s*y
Blending (gamma≠1):
a: 1-(1-(1-t)*x)*(1-y)
s: alpha_b*(1-alpha_a)/a
r,g,b: pow((1-s)*x**gamma + s*y**gamma, 1/gamma)
Und zum Schluss hier eine nützliche Lektüre über die Gammakorrektur.
Als ich hierher kam, fand ich nicht den Algorithmus "Additive Color Mixing", den ich tatsächlich suchte, der auch in Photoshop verfügbar ist und auf Wikipedia als "Bildschirm" beschrieben wird . (Aka "aufhellen" oder "invertieren multiplizieren" .) Es wird ein Ergebnis erzeugt, das zwei kombinierten Lichtquellen ähnelt.
Im Bildschirmüberblendungsmodus werden die Werte der Pixel in den beiden Ebenen invertiert, multipliziert und dann erneut invertiert. Dies ergibt den gegenteiligen Effekt der Multiplikation. Das Ergebnis ist ein helleres Bild.
Hier ist es:
// (rgb values are 0-255)
function screen(color1, color2) {
var r = Math.round((1 - (1 - color1.R / 255) * (1 - color2.R / 255)) * 255);
var g = Math.round((1 - (1 - color1.G / 255) * (1 - color2.G / 255)) * 255);
var b = Math.round((1 - (1 - color1.B / 255) * (1 - color2.B / 255)) * 255);
return new Color(r, g, b);
}
b3 = {r: (b1.r + b2.r) / 2, g: (b1.g + b2.g) /2, ...
Geschrieben / gebrauchte etwas wie @Markus Jarderot ‚s sRGB
Mischungs Antwort (die nicht Gamma da die behoben wird , ist die Standard - legacy) mit C ++
//same as Markus Jarderot's answer
float red, green, blue;
alpha = (1.0 - (1.0 - back.alpha)*(1.0 - front.alpha));
red = (front.red * front.alpha / alpha + back.red * back.alpha * (1.0 - front.alpha));
green = (front.green * front.alpha / alpha + back.green * back.alpha * (1.0 - front.alpha));
blue = (front.blue * front.alpha / alpha + back.blue * back.alpha * (1.0 - front.alpha));
//faster but equal output
alpha = (1.0 - (1.0 - back.alpha)*(1.0 - front.alpha));
red = (back.red * (1.0 - front.alpha) + front.red * front.alpha);
green = (back.green * (1.0 - front.alpha) + front.green * front.alpha);
blue = (back.blue * (1.0 - front.alpha) + front.blue * front.alpha);
//even faster but only works when all values are in range 0 to 255
int red, green, blue;
alpha = (255 - (255 - back.alpha)*(255 - front.alpha));
red = (back.red * (255 - front.alpha) + front.red * front.alpha) / 255;
green = (back.green * (255 - front.alpha) + front.green * front.alpha) / 255;
blue = (back.blue * (255 - front.alpha) + front.blue * front.alpha) / 255;
Weitere Informationen: Was jeder Codierer über Gamma wissen sollte
Ich habe an einem ähnlichen Problem gearbeitet und bin hier gelandet, musste aber am Ende meine eigene Implementierung schreiben. Ich wollte im Grunde die neue Vordergrundfarbe über die vorhandene Hintergrundfarbe "überlagern". (Und ohne einen beliebigen Mittelpunkt wie zu verwenden t
. Ich glaube, meine Implementierung ist immer noch "additiv".) Dies scheint auch in allen meinen Testfällen sehr sauber zu passen.
Hier new_argb
konvertiert man einfach das int
in eine Struktur mit 4, unsigned char
damit ich die Anzahl der Bitverschiebungen reduzieren kann.
int blend_argb(int foreground, int background)
{
t_argb fg;
t_argb bg;
t_argb blend;
double ratio;
fg = new_argb(foreground);
bg = new_argb(background);
// If background is transparent,
// use foreground color as-is and vice versa.
if (bg.a == 255)
return (foreground);
if (fg.a == 255)
return (background);
// If the background is fully opaque,
// ignore the foreground alpha. (Or the color will be darker.)
// Otherwise alpha is additive.
blend.a = ((bg.a == 0) ? 0 : (bg.a + fg.a));
// When foreground alpha == 0, totally covers background color.
ratio = fg.a / 255.0;
blend.r = (fg.r * (1 - ratio)) + (bg.r * ratio);
blend.g = (fg.g * (1 - ratio)) + (bg.g * ratio);
blend.b = (fg.b * (1 - ratio)) + (bg.b * ratio);
return (blend.a << 24 | blend.r << 16 | blend.g << 8 | blend.b);
}
In meiner Umgebung schreibe ich Farben int
in ein 1D-Pixel-Array, das mit 0 Bytes initialisiert wird. Durch Erhöhen des Alphas tendiert das Pixel zu Schwarz. ( 0
0 0 0
wäre undurchsichtiges Schwarz und 255
255 255 255
wäre transparentes Weiß ... auch bekannt als Schwarz.)
Hier ist eine hochoptimierte, eigenständige C ++ - Klasse, gemeinfrei, mit Gleitkomma und zwei unterschiedlich optimierten 8-Bit-Mischmechanismen in Funktions- und Makroformaten sowie einer technischen Diskussion sowohl des vorliegenden Problems als auch der Vorgehensweise und der Bedeutung von, Optimierung dieses Problems:
Vielen Dank an Markus Jarderot, Andras Zoltan und hkurabko; Hier ist der Python-Code zum Mischen einer Liste von RGB-Bildern.
Mit Markus Jarderots Code können wir RGBA-Farben erzeugen, dann verwende ich die Methode von Andras Zoltan und hkurabko, um RGBA in RGB zu übertragen.
Vielen Dank!
import numpy as np
def Blend2Color(C1,C2):
c1,c1a=C1
c2,c2a=C2
A = 1 - (1 - c1a) * (1 - c2a);
if (A < 1.0e-6):
return (0,0,0) #Fully transparent -- R,G,B not important
Result=(np.array(c1)*c1a+np.array(c2)*c2a*(1-c1a))/A
return Result,A
def RGBA2RGB(RGBA,BackGround=(1,1,1)):# whilt background
A=RGBA[-1]
RGB=np.add(np.multiply(np.array(RGBA[:-1]),A),
np.multiply(np.array(BackGround),1-A))
return RGB
def BlendRGBList(Clist,AlphaList=None,NFloat=2,ReturnRGB=True,
RGB_BackGround=(1,1,1)):
N=len(Clist)
if AlphaList==None:
ClistUse=Clist.copy()
else:
if len(AlphaList)==N:
AlphaListUse=np.multiply(AlphaList,10**NFloat).astype(int)
ClistUse=np.repeat(np.array(Clist), AlphaListUse, axis=0)
else:
raise('len of AlphaList must equal to len of Clist!')
while N!=1:
temp=ClistUse.copy()
ClistUse=[]
for C in temp[:-1]:
c1,a1=C
c2,a2=temp[-1]
ClistUse.append(Blend2Color(C1=(c1,a1*(1-1/N)),C2=(c2,a2*1/N)))
N=len(ClistUse)
Result=np.append(ClistUse[0][0],ClistUse[0][1])
if ReturnRGB:
Result=RGBA2RGB(Result,BackGround=RGB_BackGround)
return Result
Prüfung
BlendRGBList([[(1,0,0),1],[(0,1,0),1]],ReturnRGB=True)
#array([0.75, 0.5 , 0.25])
BlendRGBList([[(1,0,0),1],[(0,1,0),1]],ReturnRGB=False)
#array([0.66666667, 0.33333333, 0. , 0.75 ])