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.
Related
I'm looking at drawing a custom theme element onto a device content.
For example's sake, i will use the HeaderItem from the Windows XP header/listview:
(18×18 px)
Which we can blow up to see a little easier:
Note: I am not using the Theme API, nor am i asking about using the Theme API.
If i have my bitmap, like the one above, how can i draw it in practice?
Stretch draw ruins the style
The important problem that needs solving is how to maintain the important details. You can see the actual Windows XP Header draws the right-edge vertical line nice and crisp:
But if i were to blindly StretchBlt the image, the details become fuzzy:
The issue also happens with theme elements with crisp horizontal feature when the image is stretched vertically. In this case it also messes up the vertical gradient. But some other element have it even more pronounced.
So what is the technique that can be used to address this?
Should i cut 6 px off the top, left, bottom, and right?:
And then rather than drawing 1 image, i draw nine?:
And draw them with various horizontal or vertical stretch rules depending where it is?:
Unstretched
Horizontally stretched
Unstretched
Vertically stretched
Horizontally and vertically stretched
Vertically stretched
Unstretched
Horizontally stretched
Unstretched
This must be a solved problem already; since Windows already solved it, and who knows how many more Widget libraries that support themes.
Microsoft's solution to this problem can be reverse engineered by looking at the NormalBlue.ini file inside Luna.msstyles. Looking at the entry for Header.HeaderItem:
NormalBlue.ini:
[Header.HeaderItem]
bgtype = imagefile
SizingMargins = 8, 8, 3, 4
ContentMargins = 3, 0, 0, 0
ImageFile = Blue\ListViewHeader.bmp
imageCount=5
imageLayout=vertical
sizingType = tile
transparent=true
transparentcolor=255 0 0
FillColorHint = 250 248 243; Average fill color (light beige)
AccentColorHint = 252 194 71; Rollover hilite color (orange)
First we see it references the \Blue\ListViewHeader.bmp:
ImageFile = Blue\ListViewHeader.bmp
Which is:
And then there's the magic piece:
SizingMargins = 8, 8, 3, 4
This corresponds to TMT_SIZINGMARGINS:
TMT_SIZINGMARGINS: The margins used for sizing a non-true-size image.
where you can see some more hints in TmSchema.h:
//---- rendering MARGIN properties ----
TM_PROP(3601, TMT, SIZINGMARGINS, MARGINS) // margins used for 9-grid sizing
"Margins used for 9-grid sizing". This is a reference to the idea that you split up the image into 3x3 grid, and size the chunks independently as appropriate.
And the final piece is the documentation of the MARGINS type in UxTheme.h:
typedef struct _MARGINS
{
int cxLeftWidth; // width of left border that retains its size
int cxRightWidth; // width of right border that retains its size
int cyTopHeight; // height of top border that retains its size
int cyBottomHeight; // height of bottom border that retains its size
} MARGINS, *PMARGINS;
and its documentation:
cxLeftWidth: int - Width of the left border that retains its size.
cxRightWidth: int - Width of the right border that retains its size.
cyTopHeight: int - Height of the top border that retains its size.
cyBottomHeight: int - Height of the bottom border that retains its size.
Chop and Paint
The Luna theme is telling us that when we draw ListViewHeader.bmp, we need to use the sizing margins:
SizingMargins = 8, 8, 3, 4
And cut the image up into 9 pieces (3x3). But rather than using 6px all around (like i said in my question), we need to use the sizes that the designer of the image intended:
Left: 8
Right: 8
Top: 3
Bottom: 4
So given the 18×18 theme element image created in Photoshop by a designer:
The person who created the image said that my drawing code needs to cut off:
left 8 pixels
right 8 pixels
top 3 pixels
bottom 4 pixels
Meaning i then have to draw each of the nine images:
And then stretch draw some parts in certain directions:
Top-left: draw unstretched
Left: stretch vertically
Bottom-left: draw unstretched
Top: draw stretch horizontally
Middle: draw stretched horizontally and vertically
Bottom: draw stretched horizontally
Top-right: draw unstretched
Right: draw stretched vertically
Bottom-right: draw unstretched
how would a go about drawing the inner blue slice of this circle, to simulate varying stroke weight.
I have tried a approach where i draw the stroke by drawing small circles on each angle of the circle and increasing the radius on certain parts of the circle. But this doesnt give the right result because the circle gets "pixelated" in the edge, and it skews the circle outwards.
There is no easy way to accomplish this. Part of the difficulty is that Canvas, the underlying technology that p5.js uses to draw graphics, doesn't support variable stroke weights either. In Scalable Vector Graphics, which has similar limitations, the best way to accomplish this would be to describe the shape as the outer perimeter, and the perimeter of the inner void, and then fill the shape without any stroke. I think Canvas would support this approach, but I don't think it can be done easily with p5.js because there's now way to jump to a new position when drawing bezier curves with beginShape()/bezierVertex(). However, one way you could do this in p5.js would be to fill the outer shape and then "remove" the inner void. If you want to draw this on top of other existing graphics then the best way is to draw this shape to a separate p5.Graphics object which you then draw to your main canvas with image():
let sprite;
function setup() {
createCanvas(windowWidth, windowHeight);
sprite = createGraphics(100, 100);
sprite.noStroke();
sprite.fill('black');
sprite.angleMode(DEGREES);
sprite.circle(50, 50, 100);
// switch to removing elements from the graphics
sprite.erase();
// Translate and rotate to match the shape you showed in your question
sprite.translate(50, 50);
sprite.rotate(-45);
// Remove a perfect semi circle from one half, producing regular 5px stroke circle
sprite.arc(0, 0, 90, 90, -90, 90);
// Remove a half-ellipse from the other side of the circle, but this time the
// height matches the previous arc, but the width is narrower.
// Note: the angles for this arc overlap the previous removal by a few degrees
// to prevent there from being a visible seam in between the two removed shapes.
sprite.arc(0, 0, 70, 90, 85, 275, OPEN);
}
function draw() {
background('lightgray');
image(sprite, mouseX - 50, mouseY - 50);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
How is context.shadowBlur defined? It seems that it has something todo with the line width:
var c=document.getElementById("myCanvas1");
var ctx=c.getContext("2d");
ctx.shadowBlur=20;
ctx.shadowColor="black";
ctx.strokeStyle="red";
ctx.strokeRect(20,20,100,80);
var c=document.getElementById("myCanvas2");
var ctx=c.getContext("2d");
ctx.lineWidth=5;
ctx.shadowBlur=20;
ctx.shadowColor="black";
ctx.strokeStyle="blue";
ctx.strokeRect(20,20,100,80);
<canvas id="myCanvas1" width="200" height="200"></canvas>
<canvas id="myCanvas2" width="200" height="200"></canvas>
I want to draw only the shadow but no Line and a don't get the relationship and the docs doesn't explain it's value.
The exact definition can be found here: https://html.spec.whatwg.org/multipage/canvas.html#shadows
Shadows are only drawn if the opacity component of the alpha component of the color of shadowColor is nonzero and either the shadowBlur is nonzero, or the shadowOffsetX is nonzero, or the shadowOffsetY is nonzero.
When shadows are drawn, they must be rendered as follows:
Let A be an infinite transparent black bitmap on which the source image for which a shadow is being created has been rendered.
Let B be an infinite transparent black bitmap, with a coordinate space and an origin identical to A.
Copy the alpha channel of A to B, offset by shadowOffsetX in the positive x direction, and shadowOffsetY in the positive y direction.
If shadowBlur is greater than 0:
Let σ be half the value of shadowBlur.
Perform a 2D Gaussian Blur on B, using σ as the standard deviation.
User agents may limit values of σ to an implementation-specific maximum value to avoid exceeding hardware limitations during the Gaussian blur operation.
Set the red, green, and blue components of every pixel in B to the red, green, and blue components (respectively) of the color of shadowColor.
Multiply the alpha component of every pixel in B by the alpha component of the color of shadowColor.
The shadow is in the bitmap B, and is rendered as part of the drawing model described below.
If the current composition operation is copy, then shadows effectively won't render (since the shape will overwrite the shadow).
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 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).