I am using clearRect on a HTML5 canvas to redraw a rectangle. When using floating point coordinates the clearRect leaves a border from my rectangle on the canvas.
The following code demonstrates the problem with the rectangle using integer coordinates being fully cleared while the one using floating point leaves a border.
<html>
<head>
</head>
<body>
<script type="text/javascript" >
var canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
canvas.style.border = "1px solid";
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillRect(20.1,20.1,30,30);
ctx.clearRect(20.1,20.1,30,30);
ctx.fillRect(50,50,30,30);
ctx.clearRect(50,50,30,30);
</script>
</body>
</html>
The resulting canvas looks like this:
I can fix this by clearing a larger region, but that increases the risk of clearing and having to redraw adjacent shapes. This is for example suggested here: I can't completely clear the transformed rectangle in <canvas>
I can fix it by using integer coordinates, but that is not an option in this application.
Are there other ways to make clearRect actually clear all of the drawn rectangle without clearing a larger region or using integer coordinates?
All points in canvas are in fact centered in their middle coordinates (0.5, 0.5).
If you want to draw a black line one pixel thick, you'll have to draw it with centered coordinates.
If you draw it on an integer boundary, you'll in fact draw a two pixel thick lines both with lower opacity, leading to a thicker line drawn in dark gray instead of black :
Here's a picture showing this, zoomed 3 times :
More generally, any coordinates off the 0.5 boundary will be drawn with an opacity proportional to its distance to mid point.
Here's a set of horizontal line segments starting on an integer boundary, then shifted 1/10th of a pixel every 20 pixels :
zoomed 4 times :
We can see that we really have a 1 pixel line only when centered.
For your issue, there's no way you 'partially' clear a pixel : pixel is the ultimate unit here, so since colors have already been mixed, you can only either clear whole pixel, or just attenuate its intensity (which is the result you see).
I can think of two solutions :
rather than clearing, redraw everything except what you don't want any more. For this you have to handle some kind of scene graph, meaning : you need to have a collection of all the objects that needs drawing (held within an array for instance), and at draw time, you erase everything, redraw everything except the rectangle.
handle a bigger canvas behind the scene, that will have a higher resolution than the user canvas. This is were you draw, with better quality, and after drawing you copy it to the low-resolution user canvas.
Draw on 0.5 boundaries with integer sizes (width/height of your rect for instance). This main canvas might be 4 or 8 times bigger. The maximum size of the canvas is limited, so watch out for this if you target all browsers, they do not all allow the same max size (below 6400X6400 should be fine, but not sure about it). You can handle multiples backstage canvas to go beyond that limit, with a little bit of extra work.
(Rq for solution 2 : be sure to disable image smoothing before copying to avoid artifacts).
(( the fiddle for the drawings is here : jsbin.com/xucuxoxo/1/ ))
Edit : it is a good practice to translate the context from (0.5;0.5) right after you created it. Then you will always draw integer coordinates. This way, you ensure that all, say, 1 pixel thick line will actually be drawn one pixel thick. Test rounding with floor or ceil, and choose the one you prefer.
Html canvas always applies anti-aliasing to "cure the jaggies".
Anti-aliasing visually smooths lines by adding semi-transparent pixels along the line so the eye is fooled into seeing a less-jagged line.
When you draw your rectangles, these semi-transparent pixels are automatically being applied outside the 30,30 area of your rectangles.
This means your 30x30 rectangle is actually slightly larger than 30x30.
When you do context.clearRect the browser does not clear those extra semi-transparent pixels.
That's why the uncleared pixels appear "ghostly" -- they are semi-transparent.
Unfortunately, there is no way currently to turn off anti-aliasing for html canvas primitive drawing (lines, etc).
You have discovered the 2 fastest solutions:
round pixel drawing coordinates to integers
clear an area slightly larger than the original drawing
You can draw without anti-aliasing by drawing pixels manually using getImageData/putImageData. This manual method works but is costly to performance. The decreased performance defeats the purpose of clearing just the drawn area.
Bottom line: You've already discovered the best solutions canvas currently has to offer :-(
Related
I noticed a weird outline on my canvas lines. Below is an example:
http://jsfiddle.net/0Lzd562x/6/
The blue stroked rectangle is drawn after the red one but it looks like they mix together or there is a glow on the lines. I've tried setting the lineWidth to a larger value and it fixes the issue, but I want slim lines. Also tried using ctx.lineTo() to draw the rectangles but with same results. I want the latter (blue) rectangle to be on top overriding the right side of the red one.
What's happening is that you're drawing lines along the splits between pixels, not in the middle of the pixels themselves. This causes the line to be rendered at half opacity on each of the pixels. Because of the opacity, the colors from both squares are being added to 2 pixels, resulting in a thick 2px purple line.
The reason for the splitting of the line is antialiasing. You can find a fantastic talk about it by Steve Wittens
You can't turn antialiasing off, but you can draw the lines in the middle of the pixels by adding .5 to the starting x/ys of the rectangles, as long as the sizes are integers
Here's the resulting fiddle: http://jsfiddle.net/0Lzd562x/11/
I am building a tool which will ultimately leverage the Google Maps API v3 to build up an area on a map constructed of squares of a fixed edge length (e.g. 10 metres) on a fixed “grid” system (e.g., co-ordinates spaced out every 0.0001 latlong units starting at earth’s 0,0 point).
I have written code where users can click on an area on the map and the code draws an outline and fill of the square where it's found. Users can click on other adjacent locations to that square to build up a larger and larger “blocky” polygon, and can click on individual squares to delete them. I have tested all this myself in both HTML5 canvas/JavaScript as well as the Google Maps API.
Now I want to write code that removes any internal edges/vertices so that it only draws the outermost boundaries of this polygon so that it is actually drawn as one large polygon, rather than a collection of squares. Think of it this way: even though we know countries like Australia, USA etc., are comprised of a number of states, when we draw the border around the country we are not usually interested in the borders of all the states and can delete those lines in between and just draw the outer boundary. This is the same thing I want to accomplish, just using a grid of squares rather than complex polygons.
My current code is here:
https://jsfiddle.net/wyxcvmdf/14/
HTML:
<canvas id="myCanvas" width="500" height="250" style="border:1px solid #000000;"></canvas>
<!--etc.-->
JavaScript:
// don't forget to set load type in jsfiddle to no wrap in <body>
// define the global variable and some helper variables to make the code shorter
var gv = {};
gv.o = function(id) {
return document.getElementById(id)
};
gv.i = 'innerHTML';
// etc.
A couple of explanatory notes about my code:
• The “origin point” for every square is the vertex at the bottom left corner of that square. No particular reason for this.
• The “drawing direction” in terms of how HTML5 canvas draws the outline is counter-clockwise from the origin point. Again, no particular reason for this.
• You can’t “click” to add squares yet as it’s just a proof of concept, so you add squares by entering the x and y co-ordinates in the relevant text entry boxes
The use cases/tests required to prove my code which I have thought of are:
Square added to polygon with 1 duplicate vertex (working)
Square added to polygon with 2 and 3 duplicate vertices in all cases: adjacent edges, non-adjacent edges, non-sequential vertices (currently working for first case only)
Square added to polygon with 4 duplicate vertices in all cases: plugging a hole, plugging part of a hole, joining multiple polygons (currently working for first case only)
Square removed from polygon with 1 duplicate vertex in cases described above (not developed yet, but should effectively be “reverse” of addition code)
Square removed from polygon with 2 and 3 duplicate vertices in cases described above (not developed yet, but should effectively be “reverse” of addition code)
Square removed from polygon with 4 duplicate vertices in cases described above (not developed yet, but should effectively be “reverse” of addition code)
Square addition/removal on outside of polygon with multiple inner borders, i.e., holes (not developed yet, may be tricky)
Square addition/removal on inside of polygon with multiple inner borders, i.e., holes (not developed yet, may be tricky)
Note 1: My use of “squares”, “edge” etc., instead of "polygons", etc., is just for simplicity of explanation.
Note 2: I have performed quite a bit of research on similar problems and possible solutions but haven’t really found anything which will meet my needs. The research I’ve done is on:
Travelling Salesman Problem. However, this is not about optimising a path – it is about making sure a path is “drawable” and hence heading in one direction. Overlapping vertices are totally fine as long as the resulting shape looks like what a user would expect it to.
Convex hull algorithm. Not really applicable as the hull could be convex, concave or even non-contiguous! Also, I think that by simplifying to a grid system I have removed the problem of having many scattered vertices where you need to determine how far they are from a centre point, use trigonometry etc.
Concave hull solutions. This gets closer to solving my problem, however what I have seen is that there are many plug-ins for commercial tools (e.g. ArcGIS) to do this, but no generic code (irrespective of programming language) which covers all of my use cases.
Tile-based games. You would think that any tile-based game which requires drawing boundaries around tiles (e.g. A player’s territory in a real-time strategy game) would have solved this problem, but not from what I can see.
You say "draw" rather than calculate the outside vertices, so ...
You can use clipping plus compositing to "hollow out" your set of squares.
Assume you have determined that these squares are inside your desired boundary (either partially or fully inside):
var aInside=[ {x:60,y:60},{x:80,y:60},{x:40,y:60},{x:60,y:40},{x:60,y:80} ];
An illustration of squares that are inside your desired boundary.
Then, to draw just the boundary of the set of squares, you can:
Stroke (not fill) each of your inside squares: context.rect
Restrict futher drawing to the stroked rects: context.clip
Cause all new drawing to erase existing pixels: context.globalCompositeOperation = 'destination-out'
Fill the entire canvas with a solid color: context.fillRect(0,0,canvas.width,canvas.height).
The trick: Stroking a rectangle actually draws a stroke half-inside & half-outside the rectangle, so step#4 will erase the inside of the set of rectangles but (importantly!) will leave the half outside stroke.
So you end up with this:
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var aInside=[ {x:60,y:60},{x:80,y:60},{x:40,y:60},{x:60,y:40},{x:60,y:80} ];
// stroke all inside squares
ctx.save();
ctx.beginPath();
for(var i=0;i<aInside.length;i++){
var s=aInside[i];
ctx.rect(s.x,s.y,20,20);
}
ctx.stroke();
// clip to cause all new drawing to be inside the stroked squares
ctx.clip();
// set compositing to use new drawings to "erase" existing drawings
ctx.globalCompositeOperation='destination-out';
// Fill (===erase!) the entire canvas
// Clipping causes only the clipping area to be erased
// so the inside of the rects set is "hollowed out"
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.restore();
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=150 height=150></canvas>
An Algorithmic Note: If you want a set of the surviving vertices rather than a drawing, you can modify the Marching Squares Algorithm to return only the inflection points. Those inflection points are the vertices of your outside boundary.
This method addresses only drawing/appearance - it does not produce any new polygons. But it allow you to use a collection of polygons (any shape, here rectangles) and merge them visually to produce a merged outline. I base this answer on one of my earlier answers, but modified and adopted to fit the scenario here:
Draw all the rectangles as solids
Re-draw them offset around all edges and corners extruded to the thickness you want
Redraw the original rectangles but with global composite mode set to destination-outand centered on top
There are a few steps, but it works pretty fast.
A couple of notes:
If you have an existing background it would be necessary to use an off-screen canvas as a temporary stage. Not shown here, though the steps would be the same except that you would do these steps on the off-screen context and at the end you would copy the content from the off-screen canvas on top of the existing content of your display canvas.
If you have a lot of rectangles it can be optimized by drawing each single rectangle to a separate off-screen canvas without redrawing anything else. Then you just use this off-screen canvas as a source when you do the extrusion process shown below (see link above for example, just replace image with off-screen canvas itself as source).
It can be further optimized by checking if a rectangle is embedded and if so remove it from the collection.
Demo
var ctx = c.getContext("2d"),
rw = 50, rh = 50, // some demo size
rectangles = []; // rectangle collection
function render(ctx) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = "#a00";
ctx.globalCompositeOperation = "source-over"; // draw using standard mode3
// we will draw the same rects on top of each other eight times
// this will extrude the edge so we can in the next step punch a
// hole in the drawing and leave only the extrusion -
// offset array (x,y) pairs
var i, d = 2, // d = number of pixels to offset
offsets = [-d, -d, 0, -d, d, -d, d, 0, d, d, 0, d, -d, d, -d, 0];
for(i = 0; i < offsets.length; i += 2) {
ctx.setTransform(1,0,0,1, offsets[i], offsets[i+1]);
drawRects()
}
// punch hole in the center
ctx.setTransform(1,0,0,1,0,0); // reset transformatons
ctx.globalCompositeOperation = "destination-out"; // "erase" mode
drawRects(); // draw a final time, wo/ extrusion
function drawRects() {
ctx.beginPath();
rectangles.forEach(function(r) {
ctx.rect(r.x, r.y, r.w, r.h)
}); // loop through collection and draw
ctx.fill()
}
}
// demo stuff --
c.onclick = function(e) {
var r = this.getBoundingClientRect(), // for demo, get mouse position
x = e.clientX - r.left,
y = e.clientY - r.top;
// add rectangle to list
rectangles.push({ // generate a rect. from center
x: x - rw*0.5,
y: y - rh*0.5,
w: rw,
h: rh
});
render(ctx); // the key process
};
canvas {border:1px solid #000}
Click on the canvas below to place rectangles:<br>
<canvas width=600 height=600 id=c></canvas>
I have problem with canvas createPattern. I have two boxes, both will move after pressing a keyarrow:
Example:
http://jsfiddle.net/wA73R/1/
The problem is that the box background filled by createPattern also is moving. How to avoid that? Is there any solution? The big box is only an example (drawImage is not the good solution for me, I need something that will repeat background image).
Thank you for help
The problem is that the box background filled by createPattern also is moving.
Actually your problem is that the background is not moving - it is static, while you are drawing your rectangle to different positions.
How to avoid that?
The pattern will always be drawn at the coordinate origin, whose actual position is defined by the current transformation. In future you will be able to transform the pattern itself with the setTransform method, but since that currently is not implemented anywhere you instead will have to change the global transformation matrix.
In your case it means, that instead of drawing your rectangle at x/y, you translate the whole context to x/y and draw your rectangle at 0/0 then:
ctx.fillStyle=pattern;
ctx.save();
ctx.translate(boxes[i].x - left , boxes[i].y);
ctx.fillRect(0, 0, boxes[i].width, boxes[i].height);
ctx.restore();
(updated demo)
I'm using canvas of HTML5 to create a "preview" image which mainly consists of some rectangles and simple lines. Works fine so far, but there's one problem I cannot fix somehow. Presume the following situation:
context.fillStyle = "rgba(0,0,0,0.75)";
context.fillRect(100.64646,100,50.94967,20);
context.fillRect(100.64646+50.94967,100,100,20);
So I'm drawing 2 rectangles with some opacity. The x-starting coordinate plus the x-length of the first rect is equal to the x-starting coordinate of the second rect, so in theory they should collide without any margin between. Sadly, the result is different:
(see http://files.clemensfreitag.de/thin_spacing.jpg)
There's a very tiny spacing between the boxes, and the background color is visible. But:
This problem doesn't occur if the coordinates and lengths are integer values.
Is there any way to get it done by using float values? Converting them to integers before drawing might be acceptable in my application, but I'm just wondering why this should not work with floats.
Best,
Clemens
What you're seeing is the result of overlaying two opaque colors. When the first rectangle ends at 151.59613, the rectangle is automatically antialiased, filling in the rightmost column with rgba(0,0,0,0.4470975). When the second rectangle starts at the same x coordinate, it is also antialiased, filling in the leftmost column (the same as the first rectangle's rightmost) with rgba(0,0,0,0.3029025). The two values do add up to rgba(0,0,0,0.75), but that's not how they are blended. Instead, the second color (rgba(0,0,0,.3029025)) is drawn on top of the first, resulting in rgba(0,0,0,0.4470975+(1-0.4470975)*0.3029025) = rgba(0,0,0,0.61457305). So there isn't actually a gap between the two rectangles, but rather a 1px column that is a slightly lighter shade of grey.
Similarly, if you were using solid colors then the second rectangle's antialiased column would overwrite the first's, resulting in an even lighter shade of grey in the "gap".
The issue does not show up with integer values because no antialiasing is required - each rectangle ends at the edge of a pixel.
It looks like none of the globalCompositeOperation settings fix this, and turning off antialiasing would sometimes result in a 1px gap, so I think your simplest solution is to force integer values (alternatively, you could clear that column then fill it in with the desired color).
This problem is related to the way objects are drawn on a float based grid (especially vertical and horizontal lines and thus rects).
See there for an explanation and a schema : http://canop.org/blog/?p=220
Depending on the size of your objects, you need to use integer or mid-integer coordinates and sizes for your shapes, the goal being to fill complete pixels in both dimensions.
For example :
use a mid-integer for a thin line (one pixel width)
use an integer coordinate for a 2 pixels wide line
(and extend the logic for rects)
I'm using canvas for a project and I have a number of elements that I'm skewing. I'm only skewing on the y value and just want to know what the new width of the image is after skewing (so I can align it with another canvas element). Check out the code below to see what I mean
ctx.save();
//skew the context
ctx.transform(1,0,1.3,0,0,0);
//draw two images with different heights/widths
ctx.drawImage(image,0,0,42,60);
ctx.drawImage(image,0,0,32,25);
The goal would be to know that the 42 by 60 image was now a X by 60 image so I could do some translating before drawing it at 0,0. It's easy enough to measure each image individually, but I have different skew values and heights/widths throughout the project that need to be align. Currently I use this code (works decently for images between 25 and 42 widths):
var skewModifier = imageWidth*(8/6)+(19/3);
var skewAmount = 1.3; //this is dynamic in my app
var width = (skewModifier*skewAmount)+imageWidth;
As images get wider though this formula quickly falls apart (I think it's a sloping formula not a straight value like this one). Any ideas on what canvas does for skews?
You should be able to derive it mathematically. I believe:
Math.atan(skewAmount) is the angle, in radians, that something is skewed with respect to the origin.
So 1.3 would skew the object by 0.915 radians or 52 degrees.
So here's a red unskewed object next to the same object skewed (painted green). So you have a right triangle:
We know the origin angle (0.915 rads) and we know the adjacent side length, which is 60 and 25 for your two images. (red's height).
The hypotenuse is the long side thats being skewed.
And the opposite side is the triangle bottom - how much its been skewed!
Tangent gets us opposite / adjacent if I recall, so for the first one:
tan(0.915) = opposite / 60, solving for the opposite in JavaScript code we have:
opposite = Math.tan(0.915)*60
So the bottom side of the skewed object starts about 77 pixels away from the origin. Lets check our work in the canvas:
http://jsfiddle.net/LBzUt/
Looks good to me!
The triangle in question of course is the canvas origin, that black dot I painted, and the bottom-left of the red rectangle, which is the original position that we're searching for before skewing.
That was a bit of a haphazard explanation. Any questions?
Taking Simon's fiddle example one step further, so you can simply enter the degrees:
Here's the fiddle
http://jsfiddle.net/LBzUt/33/