I am drawing flat colors and textures into WebGL canvas. My colors and textures have varying alpha values and I want them to be blended correctly. I want to have transparent background (they should be blended with HTML content, which is under canvas).
In WebGL, I use
gl.clearColor(0, 0, 0, 0);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
It works correctly, when HTML background is black. But when I set a JPG pattern as a background of , I draw black triangle (alpha=1) and white triangle (alpha=0.5), I can see the background pattern in place where triangles intersect each other. Is this correct behavior?
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) means that the resulting alpha is
A_final = A_s * A_s + (1 - A_s) * A_d
In your example, after the black triangle (with alpha=1) is drawn, a drawn pixel in the framebuffer will have an alpha of
1 * 1 + (1 - 1) * 0 == 1 + 0 == 1
So it will be fully opaque. Next, after the white triangle (with alpha=.5) is drawn, a pixel in the intersection of the two triangles will have an alpha of
.5 * .5 + (1 - .5) * 1 == .25 + .5 == .75
That means the final color will be treated as partially transparent, and, when it is composited with the page, the page background will show through.
This is a somewhat uncommon problem in regular OpenGL, since content is usually composited against an empty background. It does come up when you draw to an FBO and have to composite the results with other content in your context, though. There are a few ways to deal with this.
One way is to have your alpha blend with gl.ONE, gl.ONE_MINUS_SRC_ALPHA so you get
A_final = A_s + (1 - A_s) * A_d
which is what you usually want with alpha blending. However, you want your colors to still blend with gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA. You can use gl.blendFuncSeparate instead of gl.blendFunc to set your color blending and alpha blending functions separately. In this case, you would call
gl.BlendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
Another option is to take advantage of colors with premultiplied alpha (which WebGL actually already assumes you are using, for instance, when sampling a color from a texture). If you draw the second triangle with the alpha already multiplied through the color (so a half transparent white triangle would have gl_FragColor set to vec4(.5, .5, .5, .5)), then you can use the blend mode
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
and it will act as you want for both color and alpha.
The second option is what you'll commonly see in WebGL examples, since (as mentioned), WebGL already assumes your colors are premultiplied (so vec4(1., 1., 1., .5) is out of gamut, the rendering output is undefined, and the driver/GPU can output any crazy color it wants). It's far more common to see gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) in regular OpenGL examples, which leads to some confusion.
Related
The problem
I'm trying to create a brush tool with opacity jitter (like in Photoshop). The specific problem is:
Draw a stroke on an HTML canvas with different levels of opacity. Pixels with higher opacity should replace pixels with lower opacity; otherwise, pixels are left unchanged.
Transparency should not be lost in the process. The stroke is drawn on a separate canvas and merged with a background canvas afterwards.
The result should look like this. All code and the corresponding output can be found here (JSFiddle).
Because you can't stroke a single path with different levels of opacity (please correct me if I'm wrong) my code creates a path for each segment with different opacity.
Non-solution 1, Using the 'darken' blend mode
The darken blend mode yields the desired result when using opaque pixels but doesn't seem to work with transparency. Loosing transparency is a dealbreaker.
With opaque pixels:
With transparent pixels:
Non-solution 2, Using the 'destination-out' compositing operator
Before drawing a new stroke segment, subtract its opacity from subjacent pixels by using the 'destination-out' compositing operator. Then add the new stroke segment with 'source-over'. This works almost but it's a little bit off.
Looking for a solution
I want to avoid manipulating each pixel by hand (which I have done in the past). Am I missing something obvious? Is there a simple solution to this problem?
"Links to jsfiddle.net must be accompanied by code."
Because you can't stroke a single path with different levels of opacity (please correct me if I'm wrong)
You're wrong =)
When you use globalCompositeOperation = 'destination-out' (which you are in lineDestinationOut) you need to set the strokeStyle opacity to 1 to remove everything.
However, simply changing that in your fiddle doesn't have the required effect due to the order of your path build. Build the 10% transparent one first, the whole length, then delete and draw the two 40% transparent bits.
Here's a jsfiddle of the code below
var canvas = document.getElementById('canvas');
var cx = canvas.getContext('2d');
cx.lineCap = 'round';
cx.lineJoin = 'round';
cx.lineWidth = 40;
// Create the first line, 10% transparency, the whole length of the shape.
cx.strokeStyle = 'rgba(0,0,255,0.1)';
cx.beginPath();
cx.moveTo(20,20);
cx.lineTo(260,20);
cx.lineTo(220,60);
cx.stroke();
cx.closePath();
// Create the first part of the second line, first by clearing the first
// line, then 40% transparency.
cx.strokeStyle = 'black';
cx.globalCompositeOperation = 'destination-out';
cx.beginPath();
cx.moveTo(20,20);
cx.lineTo(100,20);
cx.stroke();
cx.strokeStyle = 'rgba(0,0,255,0.4)';
cx.globalCompositeOperation = 'source-over';
cx.stroke();
cx.closePath();
// Create the second part of the second line, same as above.
cx.strokeStyle = 'black';
cx.globalCompositeOperation = 'destination-out';
cx.beginPath();
cx.moveTo(180,20);
cx.lineTo(260,20);
cx.stroke();
cx.strokeStyle = 'rgba(0,0,255,0.4)';
cx.globalCompositeOperation = 'source-over';
cx.stroke();
cx.closePath();
Use two layers to draw to:
First calculate the top layer opacity 40% - 10% and set this as alpha on top layer
Set bottom layer to 10%
Set top layer with dashed lines (lineDash) (calculate the dash-pattern size based on size requirements)
Draw lines to both layers and the bottom layer will be a single long line, the top layer will draw a dashed line on top when stroked.
Copy both layers to main canvas when done.
#HenryBlyth's answer is probably the best you're going to get; there's no native API to do what you're being asked to do (which, in my opinion, is kinda weird anyways... opacity isn't really supposed to replace pixels).
To spell out the solution in one paragraph: Split up your "stroke" into individual paths with different opacities. Draw the lowest opacity paths as normal. Then, draw the higher opacities with "desitination-out" to remove the low-opacity paths that overlap. Then, draw the high opacity paths as usual, with "source-over", to create the effect desired.
As suggested in the comments to that answer, #markE's comment about making each path an object that is pre-sorted before drawing is a great suggestion. Since you want to perform manual drawing logic that the native API can't do, turning each path into an object and dealing with them that way will be far easier than manually manipulating each pixel (though that solution would work, it could also drive you mad.)
You mention that each stroke is being done on another canvas, which is great, because you can record the mouseevents that fire as that line is being drawn, create an object to represent that path, and then use that object and others in your "merged" canvas without having to worry about pixel manipulation or anything else. I highly recommend switching to an object-oriented approach like #markE suggested, if possible.
I'm trying to draw a path using HTML Canvas. It consists of several Bezier curves linked together. For some reason, I cannot draw the whole path and then stroke. Instead, I need to stroke for each Bezier curve. I'm using a light purple color as the stroke color, but at the intersection of the curves, I seem to get something like white instead of the light purple I expect. Like this (sorry I can't post an image since I'm new on Stack Overflow):
I'm using stroke style with opacity 1, so I believe it's not a transparency issue. So what might be causing this problem?
FYI, I'm drawing each Bezier curve with code like this, where a is the drawing context of the canvas, and this.bloom.c is something like "rgba(xxx,xxx,xxx,1)":
a.strokeStyle = this.bloom.c;
a.beginPath();
a.moveTo(e.x, e.y);
a.bezierCurveTo(c.x, c.y, b.x, b.y, d.x, d.y);
a.stroke();
Thanks very much!
Use the appropriate "blend modes" natively supported by HTML5 Canvas context for Composite Operations. In your case you may use 'source-over'
For example:
var context = document.getElementById('myCanvas').getContext('2d');
context.globalCompositeOperation = 'source-over';
See Compositing and Blending 1.0
source-atop
source-in
source-out
source-over
destination-atop
destination-in
destination-out
destination-over
lighter
xor
copy
I would like to be able to create bar charts with JFreeChart that looks similar to the following picture.
It is a very basic mono-colored bar chart, but with one "fancy" detail: the diagonal stripes. I was thinking that this could be made possible by overlaying another picture on top of the normal bar. This picture would have the same dimensions as the bar, have diagonal white stripes and a transparent background. I am not quite sure how to do this though, as I have very little GUI experience, but I found a very useful article that deals with overlaying images on top of graphics from JFreeChart, so I am quite certain I should be able to pull that of.
But how should I create the diagonal stripes?
I see how I could distribute the lines from the lower left corner to the upper right corner, but not the capped lines in the upper left and lower right corner. Can I somehow paint outside the rectangle (and not have it included in the picture)?
edit: After some searching I cannot see that my suggestion of overlaying an image with a transparent background would work, as I cannot find any examples on how to do this. On the other hand, merely painting the lines on the rectangle is probably easier.
Using a gradient fill to draw lines
On trashgod's tip I tried filling a shape with a gradient that had sharp edges to simulate line drawing. This would prevent a lot of calculations and could potentially be a lot simpler. It worked quite ok for thick lines, but not for thinner lines. Using the following code produces the fill in the first picture:
rect.setSpace(spaceBetweenLines);
Color bg = Color.YELLOW;
Color fg = Color.BLUE;
rect.setPaint(new LinearGradientPaint(
(float) startX, (float) startY, (float) (startX + spaceBetweenLines), (float) (startY + spaceBetweenLines),
new float[] {0,.1f,.1001f}, new Color[] {fg,fg,bg}, MultipleGradientPaint.CycleMethod.REPEAT)
);
Drawing lines using graphic primitives
Although simpler it did not work in my case. The more elaborate, but to me, more natural way of doing it, is simply drawing lines on top of the shape (rectangle, cirle, ...). The following code was used in producing the second image. Observe the use of the clip(Shape s) to restrict the line drawing to the shape underneath. The reason for not simply drawing a rectangle and using clip() to limit the shape is that the clip() operation is not aliased, thus producing jaggies. Therefore I have to draw the shape first to get smooth edges, then set the clip to prevent overflow in the forthcoming line drawing, and finally draw the lines.
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(getBackground());
g2.fill(getShape());
g2.setClip(getShape());
// draw diagonal lines
g2.setPaint(getLineColor());
for (int x = (int) this.x, y = (int) (this.y); y - height < (this.y + height + getSpace()); ) {
g2.drawLine(x, y , x + (int) width , y - (int) width);
y += getSpace();
}
The source code for BarChartDemo1 shows how to apply a GradientPaint, but you may want to experiment with LinearGradientPaint to get the diagonal effect.
I want to paint the bars, not the background.
If you already have a suitable image, TexturePaint may be an alternative.
I am trying to understand the method transition that falls in the Matrix Class. I am using it to copy pieces of a bitMapData. But I need to better understand what transitions do.
I have a tilesheet that has 3 images on it. all 30x30 pixels. the width of the total bitmap is 90pxs.
The first tile is green, the second is brown, and the third is yellow. If I move over 30pxs using the matrix that transitions, instead of getting brown, I get yellow, if I move over 60px, I get brown.
If I move -30 pixels, then the order is correct. I am confused on what is going on.
tileNum -= (tileNumber * tWidth);
theMatrix = new Matrix();
theMatrix.translate(tileNum,0);
this.graphics.beginBitmapFill(tileImage,theMatrix);
this.graphics.drawRect(0, 0,tWidth ,tHeight );
this.graphics.endFill();
Can someone tell me how transitions work, or some resources that show how they work. I ultimately want to know a good way to switch back and forth between each tile.
First of all, don't confuse translation with transition. The latter is a general English word for "change", whereas to translate in geometry and general math is to "move" or "offset" something.
A transformation matrix defines how to transform, i.e. scale, rotate and translate, an object, usually in a visual manner. By applying a transformation matrix to an object, all pixels of that object are rotated, moved and scaled/interpolated according to the values stored inside the matrix. If you'd rather not think about matrix math, just think of the matrix as a black box which contains a sequence of rotation, scaling, and translation commands.
The translate() method simply offsets the bitmap that you are about to draw a number of pixels in the X and Y dimensions. If you use the default ("identity") matrix, which contains no translation, the top left corner of your object/bitmap will be in the (0,0) position, known as the origin or registration point.
Consider the following matrix:
var mtx : Matrix = new Matrix; // No translation, no scale, no rotation
mtx.translate(100, 0); // translated 100px on X axis
If you use the above matrix with a BitmapData.draw() or Graphics.beginBitmapFill(), that means that the top left corner of the original bitmap should be at (x=100; y=0) in the target coordinate system. Sticking to your Graphics example, lets first consider drawing a rectangle without a matrix transformation.
var shape : Shape = new Shape;
shape.graphics.beginBitmapFill(myBitmap);
shape.graphics.drawRect(0, 0, 200, 200);
This will draw a 200x200 pixels rectangle. Since there is no transformation involved in the drawing method (we're not supplying a transformation matrix), the top left corner of the bitmap is in (x=0; y=0) of the shape coordinate system, i.e. aligned with the top left corner of the rectangle.
Lets look at a similar example using the matrix.
var shape : Shape = new Shape;
shape.graphics.beginBitmapFill(myBitmap, mtx);
shape.graphics.drawRect(0, 0, 200, 200);
This again draws a rectangle that is 200px wide and 200px high. But where inside this rectangle will the top left corner of myBitmap be? The answer is at (x=100, y=0) of the shape coordinate system. This is because the matrix defines such a translation.
But what then will be to the left of (x=100; y=0)? With the above code, the answer is that the bitmap repeats to fill the entire rectangle, and hence you will see the rightmost side of the bitmap, to the left of the leftmost side, as if there was another instance of the bitmap right next to it. If you want to disable the repeating image, set the third attribute of beginBitmapFill() to false:
shape.graphics.beginBitmpFill(myBitmap, mtx, false);
Lets take a look at one last example that might help your understanding. Remember that the translation matrix defines the position of the top left corner of an image, in the coordinate system of the shape. With this in mind, consider the following code, using the same matrix as before.
var shape : Shape = new Shape;
shape.graphics.beginBitmapFill(myBitmap, mtx);
shape.graphics.drawRect(100, 0, 100, 100);
Notice that this will draw the rectangle 100px in on the X axis. Not coincidentally, this is the same translation that we defined in our matrix, and hence the position of the top left corner of the bitmap. So even though repeating is enabled, we will not see a repeating image to the left of our rectangle, because we only start drawing at the point where the bitmap starts.
So the bottom line is, I guess, that you could think of the transform matrix as a series of transformation commands that you apply to your image as you draw it. This will offset, scale and rotate the image as it's drawn.
If you are curious about the inner workings of the matrix, Google transformation matrices, or read up on Linear Algebra!
The exact background color is known.
Imagine an anti-aliased font on a green background. Now we want to change the background color of the image to red.
So.. How do I extract the "non-background" portion of the color from the pixel and add it (or "blend") on top of another bg color afterwards?
We also know the font color, so we know which pixels are fully opaque.
Every pixel in the image is either the background color, the font color, or some blend of the two. So you could specify the whole image with the background RGB values, the font RGB values, and an array of values between 0 and 1, where, say, 0 means the pixel is the background color, 1 means the pixel is the font color. A value like 0.1 means that the pixel is mostly background color with a little font color, etc.
An expression like:
blend_value = ((pixel.R - bg.R) / (font.R - bg.R)
+ (pixel.G - bg.G) / (font.G - bg.G)
+ (pixel.B - bg.B) / (font.B - bg.B)) / 3
will give you these values for any pixel in the image. To construct the new RGB values using a new background color, you use this value to blend the font color with the new background color.
new_pixel.R = blend_value * font.R + (1 - blend_value) * new_bg.R
new_pixel.G = blend_value * font.G + (1 - blend_value) * new_bg.G
new_pixel.B = blend_value * font.B + (1 - blend_value) * new_bg.B