I'm not really getting the glBlendFunc() function and how it works.
Is there a blend mode that lets destination black be black while overriding any other dst colour?
I have a black and white canvas containing colouring edges and shapes. I'd like to draw a brush anywhere over the canvas but not overriding the edges so they remain black once the image is fully coloured. Ideally, since the destination has some grey around the edges for smoothness, the grey shades would be tinted with the brush colour.
Thank you!
I believe what you need is glBlendFunc(GL_DST_COLOR, GL_ZERO).
To explain you how the blend function works:
When blending you have 2 colours, source and destination. Source is the colour you are trying to apply (a textel, a solid colour...). Destination is the colour currently on the buffer. The 2 parameters in the glBlendFunc tell how to multiply the source and destination colour before adding them together to get the result colour.
So in the case of glBlendFunc(GL_DST_COLOR, GL_ZERO) and having a black and white destination buffer (as you described) will act like this:
if destination colour is white (1,1,1,1) and you will apply some colour for instance (.1, .2, .3, 1) the result will be R = (.1, .2, .3, 1)*(1,1,1,1) + (1,1,1,1)*(0,0,0,0) = (.1, .2, .3, 1)
if destination colour is black (0,0,0,0) and you will apply some colour for instance (.1, .2, .3, 1) the result will be R = (.1, .2, .3, 1)*(0,0,0,0) + (0,0,0,0)*(0,0,0,0) = (0, 0, 0, 0)
if destination colour is grey (.5,.5,.5,1.0) and you will apply some colour for instance (.1, .2, .3, 1) the result will be R = (.1, .2, .3, 1)*(.5,.5,.5,1.0) + (.5,.5,.5,1.0)*(0,0,0,0) = (.05, .1, .15, 1)
This case of yours is a bit specific but in most cases the alpha channel is only used. For instance most common blending is glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) which means the source colour will be multiplied with source alpha while the destination colour will be multiplied with 1.0-source alpha, so a white shape with alpha of .75 over a grey background RGB .5 (any alpha) will result in RGB all 1.0*.75 + .5*(1.0-.75) = .75 + .125 = .875.
Note that this procedure of yours might cost you if you will need some other blending type on the overlay. In that case you will need to look into stencil buffers. Another issue is you might not want to see the black and white part on places you do not draw the overlay. In this case you can simply use the destination alpha to do this blending for you: glColorMask can help you draw only to the alpha channel of your buffer where you can use alpha .0 for "black" areas and 1.0 for "white" areas, when drawing a shape over the buffer you would then use glBlendFunc(GL_DST_ALPHA, GL_ZERO).
Related
I have need to convert rgb/gbr color code to closest the color name (red, yellow, black). I have found the color code by kmeans algoritms from opencv from central crop image (there are car images with central position on photo and good quality (no dark, light, noisy etc))
https://stackoverflow.com/a/50900494/5558021
and calculate distance to main color code:
black = (-25,-25,-25)
blue_cyan = (255,255,0)
green = (0,255,0)
red = (0,0,255)
white = (255,255,255)
yellow = (0, 255, 255)
colors = [black, blue_cyan, green, red, white, yellow]
diff_c = []
for c in colors:
# dist = sqrt(((color[2]-c[2])*0.3)**2 + ((color[1]-c[1])*0.59)**2 + ((color[0]-c[0])*0.11)**2)
b_ = abs(color[0] - c[0])
g_ = abs(color[1] - c[1])
r_ = abs(color[2] - c[2])
diff_c.append(b_+g_+r_)
color_index = np.argmin(np.array(diff_c))
But my algorithm fails in most times, it's predict blue as black, red as white and etc.
Please recommend a good algorithm for this purpose. Probably I need to convert color in another color space and find the range instead a calculate a single value, or find the mean of color from images?
I have studied other answers on SO, but they also get unsatisfactory result.
Or there is no solution and I should use the CNN approach?
I'm generating a falloff texture by adding gradient part to the white image I have. If implementation is relevant, I'm doing it with HTML5 canvas. For some reason I'm getting weird ray like while artifacts where it's supposed to be gradient smooth. I couldn't find any way to take care of that on implementation level, so I have to get rid of them after generating. Question is, if I have per pixel access to the image, how do I recognize those white pixels and replace with pixels to keep the gradient smooth?
The rays are caused by overlaps and rounding errors. They can be removed or at least reduced by using a Gaussian blur filter (which in effect act as a low-pass filter).
To avoid new problems such as the inner shape's black pixels leaking into the gradient, I'd suggest these steps:
Fill inner shape in the same color as the start color of the gradient.
Produce gradients
Apply Gaussian blur using either the filter property of context (f.ex context.filter = "blur(7px)";, reset by setting it to none), or by using a manual implementation
Redraw the inner shape in the destination color.
Now it's a simple matter of experimenting with the blur radius to find an optimal value. Note that blurring will add to the gradient so you might want to link the two so that the radius of the gradient is reduced when blur radius is increased.
Pro-tip: you can also drop the gradient production all together and simply make the glow effect using Gaussian blur (run example below).
var ctx = c.getContext("2d");
ctx.moveTo(300, 50);
ctx.quadraticCurveTo(325, 300, 550, 550);
ctx.quadraticCurveTo(300, 500, 50, 550);
ctx.quadraticCurveTo(250, 300, 300, 50);
ctx.closePath();
// blur next drawings
ctx.filter = "blur(20px)"; // glow radius
// produce a full base using fill and heavy stroke
ctx.fillStyle = ctx.strokeStyle = "#fff";
ctx.fill();
ctx.lineWidth = 40; // thicker = stronger spread
ctx.stroke();
// final, fill center in destination color
ctx.filter = "none";
ctx.fillStyle = "#000";
ctx.fill();
#c {background:#000}
<canvas id=c width=600 height=600></canvas>
I'm trying to blend two circles, one black and the other white. The parts where they intersect must be in grey.
I tried to use blend functions but I don't get the expected results.
The objective is to mix ONLY this two elements, other elements (like the background) can't mix there, and I don't know how to do this maintaining the 100% of the alpha channel.
This is my current render code, circle1 and circle2 are TextureRegion.
public void draw(Batch batch, float parentAlpha) {
super.draw(batch, parentAlpha);
batch.enableBlending();
batch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE);
batch.draw(circle1, c1.getX(), getY(), getWidth(), getHeight());
batch.draw(circle2, c2.getX(), getY(), getWidth(), getHeight());
}
This is an example of the color mix.
EDIT: Problem solved.
I did a test using Spritebatch, I drew a third circle with reduced opacity, this is the code used to test and the result:
Gdx.gl20.glEnable(GL20.GL_BLEND);
Gdx.gl20.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
shapeRenderer.setColor(1, 1, 1, 1f);
shapeRenderer.circle(15, 5, 5);
shapeRenderer.setColor(0, 0, 0, 1f);
shapeRenderer.circle(19, 5,5);
shapeRenderer.setColor(1, 1, 1, 0.5f);
shapeRenderer.circle(15, 5, 5);
shapeRenderer.end();
Gdx.gl20.glDisable(GL20.GL_BLEND);
I've never used libGDX so I'm not sure how the specific implementation goes, but I'll give you the general outline of what you're trying to achieve.
The most basic blending you can achieve is a linear blend between two colors with a single parameter t. Consider this example - let's say each color is represented by a single channel value for simplicity - black is 0.0 and white is 1.0. What you want to achieve is the mid range gray that is 0.5, to do this you would blend by scaling each channel appropriately ( black * 0.5 + white * 0.5 = 0.0 * 0.5 + 1.0 * 0.5 = 0.5)
A more sophisticated way of blending colors is by using the source alpha channel to govern the blend weight, so say you have a color black and a color white with 0.4 alpha, or opacity. You would blend using the following method black * 0.6 + white * 0.4 (your alpha sums up to 1.0), which would give you 0.4 a slightly "darker" than mid range gray.
To tie this back to your question, you specify the blend method as ONE, ONE, i.e, you're doing black * 1 + white * 1 which should result in 1 (all white).
Looking over the docs for libGDX, you have either the GL_ONE_MINUS_CONSTANT_ALPHA option for constant weight blend, or if you want to do "alpha blending" you could go for GL_ALPHA, GL_ONE_MINUS_SRC_ALPHA.
Hope this helps and clears up blending a bit :)
Explanation
I have a semi-transparent color of unknown value.
I have a sample of this unknown color composited over a black background and another sample over a white background.
How do I find the RGBA value of the unknown color?
Example
Note: RGB values of composites are calculated using formulas from the Wikipedia article on alpha compositing
Composite over black:
rgb(103.5, 32.5, 169.5)
Composite over white:
rgb(167.25, 96, 233.25)
Calculated value of unknown color will be:
rgba(138, 43, 226, 0.75)
What I've Read
Manually alpha blending an RGBA pixel with an RGB pixel
Calculate source RGBA value from overlay
It took some experimentation, but I think I figured it out.
Subtracting any of the color component values between the black and white composite should give you the inverse of the original color's alpha value, eg:
A_original = 1 - ((R_white_composite - R_black_composite) / 255) // in %, 0.0 to 1.0
It should yield the same value whether you use the R, G, or B component. Now that you have the original alpha, finding the new components is as easy as:
R_original = R_black_composite / A_original
G_original = G_black_composite / A_original
B_original = B_black_composite / A_original
Using HTML5 Canvas, I basically have painted a colored rectangle. Now I am painting a black, wide stroke for the same rectangle but using the global composite operator Destination-In. My expected result was to have a half-width, red stroke and nothing else as painting the stroke via Destination-In should only paint the parts in the canvas where the red rectangle "hits" the stroke.. but the result is that the red rectangle's fill makes it through outside the stroke as well..
Did I missunderstand something? Here's the actual code in use:
var ctx2 = document.getElementById('canvas2').getContext('2d');
ctx2.fillStyle = "red";
ctx2.fillRect (10, 10, 200, 100);
ctx2.strokeStyle = "rgba(0,0,0,1)";
ctx2.lineWidth = 10;
ctx2.globalCompositeOperation='destination-in';
ctx2.strokeRect (10, 10, 200, 100);
EDIT: I've added a jsFiddle sample which shows what I'd expect / want and below the actual result I am getting:
http://jsfiddle.net/nLGct/1
The result of destination-in compositing will always be:
Some/none of your original red-filled rect will survive.
None of your new black stroke will ever survive.
Specifically:
The destination (the red rect) survives only where it's overlapped by the source (the black stroke).
All non-overlapping areas are made transparent.
Why you're getting your results:
In canvas, a stroke is always half-inside and half-outside its path.
Therefore, the inside-half of your black stroke overlaps the original red fill.
So what survives destination-in is the part of the original red fill where it overlaps the inside of the black stroke.
If your black stroke is 10px wide then the inner 5 pixels overlap the red filled rect and those red pixels will survive.