I am unable to clear a rectangle completely using clearRect method which is being translated on canvas across X direction. The problem can be seen live at JS Bin - Link to demo
JS code
(function() {
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillStyle = '#ccc';
context.fillRect(0, 0, 100, 50); //x,y,w,h
translate(canvas, context, 0, 0, 100, 50, 'x', 5);
function translate(canvas, context, x, y, w, h, direction, interval) {
context.fillRect(x, y, w, h);
if (direction == 'x') {
if ((x + interval + w >= canvas.width) || (x + interval < 0)) interval = -1 * interval;
setTimeout(function() {
context.clearRect(x, y, w, h);
translate(canvas, context, x + interval, y, w, h, direction, interval);
}, 1000);
}
}
}());
It is leaving traces before moving forward/backward. And I have used the same dimensions to clear the rectangle which were used to draw it.
Please observe the complete path for observing the problem.
IE10 and FF21 work fine, Chrome gives the "remnants".
Even then, I can't consistently reproduce the misbehaving effect.
Also notice that when you scroll your jsbin results panel the remnants go away. So Chrome becomes aware that these remnant pixels shouldn't be there.
Looks like yet another canvas bug in Chrome (possibly, but not necessarily, related to anti-aliasing).
You could hack at it by extending your clear area to erase the remnants. Alternatively you could leave out the translate and increment x by interval to move your rect.
Hacky fix:
context.clearRect(x+((interval>0)?-.5:.5), y, w, h);
Related
I've been working on a Typescript based touch screen client for our CQC home automation platform, and ran across something odd. There are lots of places where various graphical elements are layered over images. When it's time to update some area of the screen, I set a clip area and update.
But I always ended up with a line around everything, which was the color of the underlying color fill behind the image. I of course blamed myself. But, in the end, instead of committing suicide, I did a little test program.
It seems to indicate that drawImage() does NOT include the clip path boundary, while a color fill does. So blitting over the part of the images that underlies the area I'm updating doesn't completely fill the target area, leaving a line around the area.
After that simple program demonstrated the problem, I went back and for image updates I inflated the clip area by one, but left it alone for everything else, and now it's all working. I tested this in Chrome and Edge, just to make sure it wasn't some bug, and they both act exactly the same.
Strangely, I've never see any statement in the docs about whether clip paths are intended to be exclusive or inclusive of the boundary, but surely it shouldn't be one way for one type of primitive and another way for others, right?
function drawRect(ctx, x, y, w, h) {
ctx.moveTo(x, y);
ctx.lineTo(x + w, y);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x, y + h);
ctx.lineTo(x, y);
}
function init()
{
var canvas = document.getElementById("output");
canvas.style.width = 480;
canvas.style.height = 480;
canvas.width = 480;
canvas.height = 480;
var drawCtx = canvas.getContext("2d");
drawCtx.translate(0.5, 0.5);
var img = new Image();
img.src = "test.jpg";
img.onload = function() {
// DRaw the image
drawCtx.drawImage(img, 0, 0);
// SEt a clip path
drawCtx.beginPath();
drawRect(drawCtx, 10, 10, 200, 200);
drawCtx.clip();
// Fill the whole area, which fills the clip area
drawCtx.fillStyle = "black";
drawCtx.fillRect(0, 0, 480, 480);
// Draw the image again, which should fill the area
drawCtx.drawImage(img, 0, 0);
// But it ends up with a black line around it
}
}
window.addEventListener("load", init, false);
I think they behave same.
Clip region are not inclusive of the border, but they can use anti aliasing.
Chrome was not using this techinque and was giving jagged lines on clipping. ( probably they changed recently ).
The thin black border is the side effect of a compositing operation.
The clip region is across a pixel. so the fillRect will draw black everywhere, but the border will be 50% black and 50% transparent, compositing with the first image draw.
The second draw image get clpped, at the border with 50% opacity to simulate the half pixel. at this point at the clip border you have:
image 100%
black fill 50%
image 50%
This will make a small dark border appear.
function drawRect(ctx, x, y, w, h) {
ctx.moveTo(x, y);
ctx.lineTo(x, y + h);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x + w, y);
ctx.closePath();
}
function init()
{
var canvas = document.getElementById("output");
canvas.style.width = 480;
canvas.style.height = 480;
canvas.width = 480;
canvas.height = 480;
var drawCtx = canvas.getContext("2d");
drawCtx.translate(0.5, 0.5);
var img = new Image();
img.src = "http://fabricjs.com/assets/printio.png";
img.onload = function() {
// DRaw the image
drawCtx.drawImage(img, 0, 0);
// SEt a clip path
drawCtx.beginPath();
drawRect(drawCtx, 10, 10, 200, 200);
drawCtx.clip();
// Fill the whole area, which fills the clip area
drawCtx.fillStyle = "black";
drawCtx.fillRect(0, 0, 480, 480);
// Draw the image again, which should fill the area
drawCtx.drawImage(img, 0, 0);
// But it ends up with a black line around it
}
}
init();
<canvas id="output" />
How to show part of element outside of canvas from opposite side canvas. Illustration:
You need to draw twice when the shape is outside canvas' boundaries. Draw the main part first, then the same part offset by width so it gives the illusion of showing on the other side.
Manually Draw twice
This draws a shape going from right to left, when the shape is outside the left edge it will be redrawn at the right edge representing the part that is non-visible on the left side. For the opposite way (left to right) the principle is just the same, just use x with canvas' width instead of 0.
var ctx = document.querySelector("canvas").getContext("2d"),
x = 100, // start position
w = 200; // shape width
ctx.fillStyle = "#777";
(function loop() {
ctx.clearRect(0, 0, 300, 150); // clear canvas
ctx.fillRect(x, 0, w, 150); // draw main part/image/shape
if (x < 0) { // should rotate? draw secondary
ctx.fillRect(ctx.canvas.width + x, 0, w, 150); // use canvas width + x (x<0)
}
x -= 7; // animate
if (x <= -w) x = ctx.canvas.width + x; // at some point reset x
requestAnimationFrame(loop)
})();
<canvas></canvas>
Translated Pattern
To simplify this a CanvasPattern can be used. The later version of canvas allows local transforms on the pattern itself, but since this is not currently widely spread I'll show an example using normal transforms and compensated x position:
var ctx = document.querySelector("canvas").getContext("2d"),
pattern,
x = 100, // start position
w = 200; // shape width
// create pattern
ctx.fillStyle = "#777";
ctx.fillRect(x, 0, w, 150); // draw main part/image/shape
pattern = ctx.createPattern(ctx.canvas, "repeat"); // use current canvas as pattern
ctx.fillStyle = pattern; // set pattern as fillStyle
(function loop() {
ctx.setTransform(1,0,0,1,0,0); // reset transforms
ctx.clearRect(0, 0, 300, 150); // clear canvas
ctx.setTransform(1,0,0,1,x,0); // translate absolute x
ctx.fillRect(-x, 0, 300, 150); // fill using pattern, compensate transform
x -= 7; // animate
requestAnimationFrame(loop)
})();
<canvas></canvas>
There is a known bug in Chrome's Canvas implementation where successive calls to lineTo will sometimes drop pixels at the corner: http://jsfiddle.net/rPJBr/2/
var cx = document.getElementById('c').getContext('2d');
cx.beginPath();
cx.moveTo(200.5, 200.5);
cx.lineTo(200.5, 100.5);
cx.lineTo(100.5, 100.5);
cx.stroke();
Here's an image and a zoom-in of the behavior on chrome 24 on win7
The bug has been logged here: http://code.google.com/p/chromium/issues/detail?id=137465
Is there a known workaround for this bug?
if(windows.chrome) {
var p = CanvasRenderingContext2D.prototype;
p.origLineTo = p.lineTo;
p.lineTo = function(x, y) {
this.fillRect(x - 0.5, y - 0.5, 1, 1);
return this.origLineTo(x, y);
};
p.origMoveTo = p.moveTo;
p.moveTo = function(x, y) {
this.fillRect(x - 0.5, y - 0.5, 1, 1);
return this.origMoveTo(x, y);
};
}
I have implemented a basic Isometric tile engine that can be explored by dragging the map with the mouse. Please see the fiddle below and drag away:
http://jsfiddle.net/kHydg/14/
The code broken down is (CoffeeScript):
The draw function
draw = ->
requestAnimFrame draw
canvas.width = canvas.width
for row in [0..width]
for col in [0..height]
xpos = (row - col) * tileHeight + width
xpos += (canvas.width / 2) - (tileWidth / 2) + scrollPosition.x
ypos = (row + col) * (tileHeight / 2) + height + scrollPosition.y
context.drawImage(defaultTile, Math.round(xpos), Math.round(ypos), tileWidth, tileHeight)
Mouse drag-scrolling control
scrollPosition =
x: 0
y: 0
dragHelper =
active: false
x: 0
y: 0
window.addEventListener 'mousedown', (e) =>
handleMouseDown(e)
, false
window.addEventListener 'mousemove', (e) =>
handleDrag(e)
, false
window.addEventListener 'mouseup', (e) =>
handleMouseUp(e)
, false
handleDrag = (e) =>
e.preventDefault()
if dragHelper.active
x = e.clientX
y = e.clientY
scrollPosition.x -= Math.round((dragHelper.x - x) / 28)
scrollPosition.y -= Math.round((dragHelper.y - y) / 28)
handleMouseUp = (e) =>
e.preventDefault()
dragHelper.active = false
handleMouseDown = (e) =>
e.preventDefault()
x = e.clientX
y = e.clientY
dragHelper.active = true
dragHelper.x = x
dragHelper.y = y
The Problem
As you can see from the fiddle the dragging action is ok but not perfect. How would I change the code to make the dragging action more smooth? What I would like is the point of the map you click on to stay under the mouse point whilst you drag; the same as they have done here: http://craftyjs.com/demos/isometric/
Lots of libraries help with things like this. I would recommend using the data manipulation abilities of d3 to help, for several reasons.
First, in d3, there is a drag behavior where the origin of the object is stored and a mouse position relative to the origin is computed when the drag starts. Then, you can use the absolute position of the mouse to determine where the object should be and avoid the incremental errors that occur when you use relative changes - which get far worse when you start rounding them, as above.
dragMap = (d) ->
d.x = d3.event.x # d3.event.x, y are computed relative to the origin for you!
d.y = d3.event.y
dragBehavior = d3.behavior.drag()
.origin(Object) # equivalent to (d) -> d
.on("drag", dragMap)
d3.select(canvas)
.datum(x: 0, y: 0) # Load your canvas with an arbitary origin
.call(dragBehavior) # And now you can drag it!
Second, by using d3's linear or other numerical scales you can avoid doing typical drawing math yourself which is error-prone especially when you have to do it all over the place. Before you were scaling the drag by 28. In my current approach it's unnecessary, but if you change your drawing algorithm to use tiles instead of pixels, you can change this scale which will automatically convert mouse pixels into tile sizes.
pixelToTile = d3.scale.linear()
.domain([0, 1])
.range([0, 1])
Here's your fiddle re-done with d3 help. No dragHelper and all that extraneous code necessary. All the Math.round calls are also unnecessary except the one for canvas draw, which prevents antialiasing.
http://jsfiddle.net/kHydg/23/
Isn't that much shorter and sweeter?
P.S. Isometric real-time browser games are an awesome idea. I will definitely try making one when I have time.
This seems like a simple trig question, but for whatever reason, things aren't working out.
I'm trying to simply have an object rotate around a given point when the user presses the A/D keys (strafing around the mouse in a circular motion, while still facing the mouse).
Here's the code I've tried so far (all Math functions take and return radians):
if (_inputRef.isKeyDown(GameData.KEY_LEFT))
{
x += 2 * Math.cos(Math.atan2(mouseY - y, mouseX - x) - Math.PI * 0.5);
y += 2 * Math.sin(Math.atan2(mouseY - y, mouseX - x) - Math.PI * 0.5);
}
else if (_inputRef.isKeyDown(GameData.KEY_RIGHT))
{
x += 2 * Math.cos(Math.atan2(mouseY - y, mouseX - x) + Math.PI * 0.5);
y += 2 * Math.sin(Math.atan2(mouseY - y, mouseX - x) + Math.PI * 0.5);
}
And a more elegant method which accomplishes the same thing:
if (_inputRef.isKeyDown(GameData.KEY_LEFT))
{
x += 2 * Math.sin(Math.atan2(mouseY - y, mouseX - x));
y -= 2 * Math.cos(Math.atan2(mouseY - y, mouseX - x));
}
else if (_inputRef.isKeyDown(GameData.KEY_RIGHT))
{
x -= 2 * Math.sin(Math.atan2(mouseY - y, mouseX - x));
y += 2 * Math.cos(Math.atan2(mouseY - y, mouseX - x));
}
Now, they both kind of work, the object rotates around the mouse while always facing the mouse, but given enough time of holding down the strafe button, it becomes increasingly apparent that the object is also rotating AWAY from the mouse, as if its being pushed away.
I have no idea why this is and how to fix it.
Any insight is appreciated!
I think your current approach would only work if you take 'infinitely small' steps. As it is now, each movement is perpendicular to the "to-mouse vector" and thus increases the distance between mouse and object.
A solution would be to calculate the new position while keeping the distance to the mouse unchanged, by rotating the position around the mouse:
// position relative to mouse
var position:Point = new Point(
x - mouseX,
y - mouseY);
var r:Number = position.length; // distance to mouse
// get rotation angle around mouse that moves
// us "SPEED" unit in world space
var a:Number = 0;
if (/* LEFT PRESSED */) a = getRotationAngle( SPEED, r);
if (/* RIGHT PRESSED */) a = getRotationAngle(-SPEED, r);
if (a > 0) {
// rotate position around mouse
var rotation:Matrix = new Matrix();
rotation.rotate(a);
position = rotation.transformPoint(position);
position.offset(mouseX, mouseY);
x = position.x;
y = position.y;
}
// elsewhere...
// speed is the distance to cover in world space, in a straight line.
// radius is the distance from the unit to the mouse, when rotating.
private static function getRotationAngle(speed:Number, radius:Number):Number {
return 2 * Math.asin(speed / (2 * radius));
}
The above uses a Matrix to rotate the (x, y) position around the mouse position. Ofcourse you can apply the same principle without using Matrix if so desired.
I had to do some trig to come up with the right equation for getting the correct angle. The angle depends on the radius of the movement arc, since a larger radius but constant angle would increase the movement distance (undesired behavior). My earlier solution (before edits) was to scale the angle by the radius, but that would still result in slightly more movement with larger radii.
The current approach ensures that radius and speed remain constant in all cases.