Hi I want to implement contrast filter in my application like this link or this contrast but with color matrix and track bar for value
I already found color matrix for it
float c = mytrackbar.value * 0.01f //maxTrackbarValue = 100, minTrackbarValue = -100
float t = 0.01f;
cmPicture = new ColorMatrix(new float[][] {
new float[] {c,0,0,0,0},
new float[] {0,c,0,0,0},
new float[] {0,0,c,0,0},
new float[] {0,0,0,1,0},
new float[] {t,t,t,0,1}
});
but the result is very different. I try to change 0.01f in ~c~ and 0.01f in ~t~ value but it only gives result like brightness
(ex : c = mytrackbar.value * 0.04f )
I wonder what ~c~ & ~t~ value and how many max and min range i should used for created contrast
Update #Nico
private void myTrackBar_ValueChanged(object sender, EventArgs e) {
imageHandle = imageHandleTemp.Bitmap;
myNumericUpDown.Text = myTrackBar.Value.ToString();
float value = myTrackBar.Value * 0.01f;
Bitmap bmpInverted = new Bitmap(imageHandle.Width, imageHandle.Height);
ImageAttributes ia = new ImageAttributes();
ColorMatrix cmPicture = new ColorMatrix();
float c = value;
float t = 0.01f;
cmPicture = new ColorMatrix(new float[][] {
new float[] {c,0,0,0,0},
new float[] {0,c,0,0,0},
new float[] {0,0,c,0,0},
new float[] {0,0,0,1,0},
new float[] {t,t,t,0,1}
});
ia.SetColorMatrix(cmPicture);
Graphics g = Graphics.FromImage(bmpInverted);
g.DrawImage(imageHandle, new Rectangle(0, 0, imageHandle.Width, imageHandle.Height), 0, 0, imageHandle.Width, imageHandle.Height, GraphicsUnit.Pixel, ia);
g.Dispose();
Image<Bgr, Byte> myImage = new Image<Bgr, byte>(bmpInverted);
imageBoxCamera.Image = myImage;
}
The t value must be derived from the new contrast value c. so change its assignment like this:
float t = (1.0f - c) / 2.0f;
According to this nice link to the ColorMatrix Guide the rest of the matrix code seems to be OK.
Note: I was wrong about the range of c!! The value of c is not an absolute value but it is the factor that should be applied! So to double the contrast it should be 2f.
Note 2: Your code is not quite clear about source and target; and since you are changing contrast on the fly with the trackbar it should be clear that until the change is applied,
The intermediate results must always be calculated from the same original bitmap.
Also, that the trackbar should be so intialized that it starts with a value of 1.
And finally, in order to reduce contrast values should translate to 0 < c < 1.
Nico's suggestion c = 1+ value; would work nicely with your original range -100 to +100 with an intial value of 0 and your factor 0.01f.
Here are some further explanations about the maths behind the process. If you are going to accept an answer, accept TaW's answer, he was quicker than me.
Adjusting the contrast basically does the following:
It shifts all pixel values by -0.5 (so that a medium-gray becomes 0)
It applies a factor to all pixel values
It reverts the shift
So in a formula:
newValue = factor * (oldValue - 0.5) + 0.5
= factor * oldValue - factor * 0.5 + 0.5
= factor * oldValue + 0.5 * (1 - factor)
For a single-channel image we can transform this into a matrix multiplication:
(newValue, 1) = (oldValue, 1) * / factor , 0\ <-- this is c, 0
\ 0.5 * (1 - factor) , 1/ <-- this is t, 1
For multi-channel images, the 2x2 matrix becomes a 5x5 (four channels + w-channel for translations) matrix with c and t at the positions you already specified.
Keep in mind that a factor of 1 does not change anything. A factor of 0 makes the whole image medium-gray. So you may want to adjust your calculation of c:
c = 1 + value;
t = 0.5f * (1.0f - c);
I'm looking for an algorithm to do additive color mixing for RGB values.
Is it as simple as adding the RGB values together to a max of 256?
(r1, g1, b1) + (r2, g2, b2) =
(min(r1+r2, 256), min(g1+g2, 256), min(b1+b2, 256))
To blend using alpha channels, you can use these formulas:
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 is the paint color. bg is the background. r is the resulting color. 1.0e-6 is just a really small number, to compensate for rounding errors.
NOTE: All variables used here are in the range [0.0, 1.0]. You have to divide or multiply by 255 if you want to use values in the range [0, 255].
For example, 50% red on top of 50% green:
// 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
Resulting color is: (0.67, 0.33, 0.00, 0.75), or 75% brown (or dark orange).
You could also reverse these formulas:
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));
or
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;
The formulas will calculate that background or paint color would have to be to produce the given resulting color.
If your background is opaque, the result would also be opaque. The foreground color could then take a range of values with different alpha values. For each channel (red, green and blue), you have to check which range of alphas results in valid values (0 - 1).
It depends on what you want, and it can help to see what the results are of different methods.
If you want
Red + Black = Red
Red + Green = Yellow
Red + Green + Blue = White
Red + White = White
Black + White = White
then adding with a clamp works (e.g. min(r1 + r2, 255)) This is more like the light model you've referred to.
If you want
Red + Black = Dark Red
Red + Green = Dark Yellow
Red + Green + Blue = Dark Gray
Red + White = Pink
Black + White = Gray
then you'll need to average the values (e.g. (r1 + r2) / 2) This works better for lightening/darkening colors and creating gradients.
Fun fact: Computer RGB values are derived from the square root of photon flux. So as a general function, your math should take that into account. The general function for this for a given channel is:
blendColorValue(a, b, t)
return sqrt((1 - t) * a^2 + t * b^2)
Where a and b are the colors to blend, and t is a number from 0-1 representing the point in the blend you want between a and b.
The alpha channel is different; it doesn't represent photon intensity, just the percent of background that should show through; so when blending alpha values, the linear average is enough:
blendAlphaValue(a, b, t)
return (1-t)*a + t*b;
So, to handle blending two colors, using those two functions, the following pseudocode should do you good:
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
Incidentally, I long for a programming language and keyboard that both permits representing math that (or more) cleanly (the combining overline unicode character doesn't work for superscripts, symbols, and a vast array of other characters) and interpreting it correctly. sqrt((1-t)*pow(a, 2) + t * pow(b, 2)) just doesn't read as clean.
Few points:
I think you want to use min instead of max
I think you want to use 255 instead of 256
This will give:
(r1, g1, b1) + (r2, g2, b2) = (min(r1+r2, 255), min(g1+g2, 255), min(b1+b2, 255))
However, The "natural" way of mixing colors is to use the average, and then you don't need the min:
(r1, g1, b1) + (r2, g2, b2) = ((r1+r2)/2, (g1+g2)/2, (b1+b2)/2)
Javascript function to blend rgba colors
c1,c2 and result - JSON's like
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
}
}
PYTHON COLOUR MIXING THROUGH ADDITION IN CMYK SPACE
One possible way to do this is to first convert the colours to CMYK format, add them there and then reconvert to RGB.
Here is an example code 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)
The result to your question would then be (assuming a half-half mixture of your two colours:
r_mix, g_mix, b_mix = ink_add_for_rgb([(r1,g1,b1,0.5),(r2,g2,b2,0.5)])
where the 0.5's are there to say that we mix 50% of the first colour with 50% of the second colour.
Find here the mixing methods suggested by Fordi and Markus Jarderot in one python function that gradually mixes or blends between two colors A and B.
The "mix" mode is useful to interpolate between two colors. The "blend" mode (with t=0) is useful to compute the resulting color if one translucent color is painted on top of another (possibly translucent) color. gamma correction leads to nicer results because it takes into consideration the fact that physical light intensity and perceived brightness (by humans) are related non-linearly.
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))
See below how this can be used. In "mix" mode the left and right colors match exactly color_a and color_b. In "blend" mode, the left color at t=0 is the color that results if color_a is blended over color_b (and a white background). In the example, color_a then is made increasingly translucent until one arrives at color_b.
Note that blending and mixing are equivalent if the alpha values are 1.0.
For completeness, here the code to reproduce the above plot.
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)
And finally, here a useful read about gamma correction.
Yes, it is as simple as that. Another option is to find the average (for creating gradients).
It really just depends on the effect you want to achieve.
However, when Alpha gets added, it gets complicated. There are a number of different methods to blend using an alpha.
An example of simple alpha blending:
http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
When I came here I didn't find the "additive color mixing" algorithm I was actually looking for, which is also available in Photoshop and is described as "Screen" on Wikipedia. (Aka "brighten" or "invert multiply".) It produces a result similar to two light sources being combined.
With Screen blend mode the values of the pixels in the two layers are inverted, multiplied, and then inverted again. This yields the opposite effect to multiply. The result is a brighter picture.
Here it is:
// (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);
}
Have written/used something like #Markus Jarderot's sRGB blending answer (which is not gamma corrected since that is the default legacy) using 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;
more info: what-every-coder-should-know-about-gamma
I was working on a similar problem and ended up here, but had to write my own implementation in the end. I wanted to basically "overlay" the new foreground color over the existing background color. (And without using an arbitrary midpoint like t. I believe my implementation is still "additive.") This also seems to blend very cleanly in all of my test-cases.
Here, new_argb just converts the int into a struct with 4 unsigned char so I can reduce the amount of bit-shifts.
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);
}
For context, in my environment I'm writing color ints into a 1D pixel array, which is initialized with 0-bytes and increasing the alpha will make the pixel tend towards black. (0 0 0 0 would be opaque black and 255 255 255 255 would be transparent white... aka black.)
Here's a highly optimized, standalone c++ class, public domain, with floating point and two differently optimized 8-bit blending mechanisms in both function and macro formats, as well as a technical discussion of both the problem at hand and how to, and the importance of, optimization of this issue:
https://github.com/fyngyrz/colorblending
Thank you Markus Jarderot, Andras Zoltan and hkurabko; here is the Python code for blending a list of RGB images.
Using Markus Jarderot's code we can generate RGBA color, then i use Andras Zoltan and hkurabko's method to trans RGBA to RGB.
Thank you!
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
Test
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 ])
What I'm trying to do now is to get my COLLADA importer working on some transparent COLLADA model. The model blending is specified by the COLLADA specification, at charapter 7, paragraph "Rendering/Determining Transparency".
In short, there are two inputs for blend equation: Trasparent and Trasparency; the former can be a RGBA color or a texture, and the latter can be only a floating-point value. Additionally, Transparent can specify two blending equations (ColladaFxOpaqueType.AlphaOne and ColladaFxOpaqueType.RgbZero):
Here are the two blending equations:
// AlphaOne
//
// result.r = fb.r * (1.0f - transparent.a * transparency) + mat.r * (transparent.a * transparency)
// result.g = fb.g * (1.0f - transparent.a * transparency) + mat.g * (transparent.a * transparency)
// result.b = fb.b * (1.0f - transparent.a * transparency) + mat.b * (transparent.a * transparency)
// result.a = fb.a * (1.0f - transparent.a * transparency) + mat.a * (transparent.a * transparency)
// RgbZero
//
// result.r = fb.r * (transparent.r * transparency) + mat.r * (1.0f -transparent.r * transparency)
// result.g = fb.g * (transparent.g * transparency) + mat.g * (1.0f -transparent.g * transparency)
// result.b = fb.b * (transparent.b * transparency) + mat.b * (1.0f -transparent.b * transparency)
// result.a = fb.a * (luminance(transparent.rgb) * transparency) + mat.a * (1.0f - luminance(transparent.rgb) * transparency)
where
- result: draw framebuffer
- fb: destination blend color
- mat: source blend color
- transparent: COLLADA parameter described above
- transparency: COLLADA parameter described above
- luminance: function to average color following ITU-R Recommendation BT.709-4
What I've implemented now, is to get the geometry blended in the case Transparent represent a color (and both blending equations). Below there is the peculiar code implementing this feature:
internal void CompileBlendStateParameters(ColladaShaderParameters shaderParameters, ColladaFxCommonContext commonContext)
{
if (shaderParameters == null)
throw new ArgumentNullException("shaderParameters");
if (commonContext == null)
throw new ArgumentNullException("commonContext");
// Apply alpha blending, if required
if ((Transparent != null) || (Transparency != null)) {
BlendState blendState = null;
ColorRGBAF blendFactors = new ColorRGBAF(1.0f); // No effect value
float trasparency = 1.0f; // No effect value
if (Transparency != null)
trasparency = Transparency.GetValue(commonContext);
if ((Transparent != null) && (Transparent.IsFixedColor(commonContext) == true)) {
switch (Transparent.Opaque) {
case ColladaFxOpaqueType.AlphaOne:
// Equation from COLLADA specification:
//
// result.r = fb.r * (1.0f - transparent.a * transparency) + mat.r * (transparent.a * transparency)
// result.g = fb.g * (1.0f - transparent.a * transparency) + mat.g * (transparent.a * transparency)
// result.b = fb.b * (1.0f - transparent.a * transparency) + mat.b * (transparent.a * transparency)
// result.a = fb.a * (1.0f - transparent.a * transparency) + mat.a * (transparent.a * transparency)
// Determine blend factor constant color
blendFactors = new ColorRGBAF(Transparent.GetFixedColor(commonContext).Alpha);
// Modulate constant color
blendFactors = blendFactors * trasparency;
// Create blend state
blendState = new BlendState(BlendState.BlendEquation.Add, BlendState.BlendFactor.ConstColor, BlendState.BlendFactor.ConstColorComplement, blendFactors);
break;
case ColladaFxOpaqueType.RgbZero:
// Equation from COLLADA specification:
//
// result.r = fb.r * (transparent.r * transparency) + mat.r * (1.0f -transparent.r * transparency)
// result.g = fb.g * (transparent.g * transparency) + mat.g * (1.0f -transparent.g * transparency)
// result.b = fb.b * (transparent.b * transparency) + mat.b * (1.0f -transparent.b * transparency)
// result.a = fb.a * (luminance(transparent.rgb) * transparency) + mat.a * (1.0f - luminance(transparent.rgb) * transparency)
// Determine blend factor constant color
blendFactors = new ColorRGBAF(Transparent.GetFixedColor(commonContext));
// Define alpha blend factor as luminance
blendFactors.Alpha = blendFactors.Red * 0.212671f + blendFactors.Green * 0.715160f + blendFactors.Blue * 0.072169f;
// Modulate constant color
blendFactors = blendFactors * trasparency;
// Create blend state
blendState = new BlendState(BlendState.BlendEquation.Add, BlendState.BlendFactor.ConstColorComplement, BlendState.BlendFactor.ConstColor, blendFactors);
break;
}
} else if ((Transparent != null) && (Transparent.IsTextureColor(commonContext) == true)) {
throw new NotSupportedException();
} else {
// Modulate constant color
blendFactors = blendFactors * trasparency;
// Create blend state
blendState = new BlendState(BlendState.BlendEquation.Add, BlendState.BlendFactor.ConstColor, BlendState.BlendFactor.ConstColorComplement, blendFactors);
}
if (blendState != null)
shaderParameters.RenderState.DefineState(blendState);
}
}
Rougly, the code above abstracts the OpenGL layer, being equivalent to:
// AlphaOne equation
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR);
glBlendColor(blendFactors.Red, blendFactors.Green, blendFactors.Blue, blendFactors.Alpha);
// RgbZero equation
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_COLOR);
glBlendColor(blendFactors.Red, blendFactors.Green, blendFactors.Blue, blendFactors.Alpha);
// Having calculated blendFactor appropriately!!
What I'd like is to support trasparencies based on texture (indeed removing that horrible NotSupportedException). Normally this would be implemented by attaching a texture to the outputted fragment alpha component, and setup blending as usual (Alpha and OneMinusAlpha blend factors), but sadly this is not possible with the above equations (alpha component wouln't be blended, isn't it?).
P.S. You can note that I've implemented blending by using a straightforward solution, but based on constant blend color (blendFactors variable in code) (indeed using GL_EXT_blend_color extension). How can I remove this dependency by using normal blending functions? I think that the solution to the last question could help me about blending based on texture...
I'm not quite sure I understand what you're going for, but I'll take a stab at it (feel free to follow up in the comments).
You want to implement the AlphaOne and RgbZero equations with standard opengl blending, and instead of using a constant color, you want your blend function to be evaluated for each texel of an image. The typical blend function for transparency (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) uses the alpha value of the incoming fragment, and is evaluated as:
result = dst * (1-src_alpha) + src * src_alpha
Looking one at a time at the two equations you want to implement (just red and alpha for brevity):
AlphaOne:
result.r = fb.r * (1.0f - transparent.a * transparency) + mat.r * (transparent.a * transparency);
result.a = fb.a * (1.0f - transparent.a * transparency) + mat.a * (transparent.a * transparency);
If we look at this equation, we see that it looks very similar to the initial equation posted. All we have to do is substitute transparent.a*transparency for src_alpha.
This means if you take a pixel shader, with the value of transparent.a coming from a texture sample, and transparency as a uniform float, it will implement the AlphaOne function:
sampler2D tex;
uniform transparency;
main() {
vec4 texel = texture2D(tex,uv);
vec3 out_rgb = texel.rgb;
float out_alpha = texel.a * transparency;
gl_FragColor = vec4(out_rgb, out_alpha);
}
This shader submits transparent.a * transparency as the src_alpha value to be used in the blend equation.
I believe that this shows that you can easily implement this algorithm with typical opengl blending.
However the RGBZero function looks tougher to me, I don't believe there is any blending function that will achieve that. Just one oddball idea I have would be to draw the four color channels one at a time (lock G,B,A for editing and just draw R, with output alpha as your R blend factor, then repeat for the other 3 color channels. That looks like somewhat of an odd blending function to me, I can't think of what it would be used for though.