Copying pixeldata between html canvases - javascript

I'm currently writing a small image editor to learn some javascript. One of the features is obviously a drawing tool. When that is used i'm drawing those pixels to another canvas on top of the actual canvas and then when the mouse is released i copy over the pixels from the top canvas to the underlying one.
This is the code that is used for the copying of the drawn pixels to the underlying canvas.
this.applySketch = function() {
this.revertStack.push(this.ctx.getImageData(0, 0, this.width, this.height));
var real = this.ctx.getImageData(0, 0, this.width, this.height);
var sketch = this.sketchctx.getImageData(0, 0, this.width, this.height);
for(var i = 0; i < sketch.data.length; i += 4) {
// check the alpha value to decide if to copy to the real canvas
if(sketch.data[i+3] > 0) {
real.data[i] = sketch.data[i];
real.data[i+1] = sketch.data[i+1];
real.data[i+2] = sketch.data[i+2];
real.data[i+3] = sketch.data[i+3];
}
}
this.ctx.putImageData(real, 0, 0);
this.sketchctx.clearRect(0, 0, this.width, this.height);
}
The problem i'm experiencing is that the pixels don't look the same when they have been copied to the other layer. Instead there is a thin contour of white pixels around whatever it is i'm copying.
Before mouse release (when the pixels are in the top layer)
http://i.imgur.com/xHvN1iF.jpg
After mouse release (when they have been copied)
http://i.imgur.com/P0sdybs.jpg
I don't really have any clue on what is going on here because it seems pretty straightforward. Could it be anything with antialiasing?
Thanks

I suppose this happens because of antialiasing. When you draw smooth line, some pixels near the line are almost transparent but not fully transparent. This fiddle illustrates the problem. So, you need to merge alpha channel more carefully. The simpliest solution is to add alpha channel value to original value instead of replacing it completely:
real.data[i+3] += sketch.data[i+3];
This fiddle shows the effect. Well, line looks thickly than original line. I suppose you should use some other formula for merging colors.

Related

HTML5 canvas line width less that 1 pixel

Is there any way to draw a rectangle whose composing lines have width thinner than 1 pixel?
This code works perfectly, as expected:
// context is a HTML5 canvas 2D context
context.lineWidth = 1;
context.strokeStyle = "black";
context.rect(0, 0, 20, 20);
context.stroke();
It draws a nice rectangle.
But, if I try to draw a rectangle with thinner lines:
// See line width
context.lineWidth = 0.5;
context.strokeStyle = "black";
context.rect(0, 0, 20, 20);
context.stroke();
It still draws a rectangle whose borders have 1 pixel width.
I'm dealing with the canvas object here, and not CSS, where you have ways to "simulate" this.
Although it doesn't make much sense, you can acheive that with using a regular 1-pixel line with a 50% scaled canvas (but again it's a 1-pixel rendition, read below). See this snippet:
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
function scale() {
context.scale(0.5, 0.5);
draw();
}
function draw() {
context.beginPath();
context.moveTo(100, 150);
context.lineTo(450, 50);
context.stroke();
}
draw()
<canvas width="400" height="150"></canvas>
<button onclick="scale()">Scale down</button>
But again, I wonder how you expect the half-pixel line to look on your screen, antialiasing?
Right :) I suppose I was thinking on some way of drawing thinner lines, like, for example, when you use CSS styles. I've looked around and I don't think I can use alternate units.
There's no way to make something that's smaller than the smallest component unit, in our case a pixel. You can mimic the thinner look by transparency, or opacity, or even some sort of antialiasing (which again relies on transparency or the colouring of the neighbouring pixels), but not by trying to go below one pixel.
I agree, there is a sub-pixel rendering mode in browsers, for example, when you work with percentages, but in the end, the browser just renders full pixels with some of the modification I've described above.
And you know if you could render unit smaller than pixels, you'd technically have infinite resolutions on displays. I wish it was possible. :)

fabric.js: move clipped Image behind fixed clipping-mask

I'm working on a small script that lets a user load a custom image into the canvas on the webpage. So far that works pretty neat. The canvas is initialized using the fabric.js script in order to let the user do some easy editing tasks.
The "uploaded" image is clipped by a simple rectangle. Now comes the tricky part: the user should then be able to move around, scale and rotate the image, whilst the rectangle stays in place; selecting the image section preferred. However, even
lockMovement = true;
or
lockMovementX = true;
lockMovementY = true;
do not keep that clipping mask in place. Any other way to achieve this?
Any help is greatly appreciated! Please find a demo here: http://jsfiddle.net/efmbrm4v/
I had the same problem and I solved it with following code:
image.clipTo = function (ctx) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset transformation to default for canvas
ctx.rect(
100, 100, // Just x, y position starting from top left corner of canvas
200, 200 // Width and height of clipping rect
);
ctx.restore();
};
You can try it out here: http://jsfiddle.net/Jagi/efmbrm4v/1/

How redraw on canvas resize without blurring?

When the canvas element is resized (via the style changing) I also want to scale the canvas' drawn image as well. I cannot just change the height/width as this causes the canvas to clear itself, so I do:
Create a temporary canvas element
Draw the current canvas' image onto that temporary canvas
Resize the current canvas
Draw the temp canvas' image back to the current canvas but scaled to the new size
This results in some blurring - very noticeable after many resizes (example: when dragging to resize). How would I do this without any blurring?
EDIT: Turning off image smoothing (context.webkitImageSmoothingEnabled = false;) does not fix the problem, it simply makes it redraw it more and more jagged until the image looks nothing like the original after a number of resizes.
Called on resize event:
var tmpCanvas = null;
//Make a temporary canvas
tmpCanvas = document.createElement( "canvas" );
//Set its size to be equal
tmpCanvas.height = originalCanvas.height;
tmpCanvas.width = originalCanvas.width;
//Draw our current canvas onto it
tmpCanvas.getContext( "2d" ).drawImage( originalCanvas, 0, 0 );
//Set new dimensions
originalCanvas.width = originalCanvas.offsetWidth;
originalCanvas.height = originalCanvas.offsetHeight;
var originalContext = originalCanvas.getContext( "2d" );
//Set background and colors
originalContext.fillStyle = "#ffffff";
originalContext.strokeStyle = "#000000";
//Set paintbrush
originalContext.lineWidth = 4;
originalContext.lineCap = "round";
//Fill background as white
originalContext.fillRect( 0, 0, originalCanvas.width, originalCanvas.height );
//We have a saved signature
if ( SignatureCanvas.hasSignature === true )
{
//Draw it back but scaled (results in blurred image)
originalContext.drawImage( tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height, 0, 0, originalCanvas.width, originalCanvas.height );
/**
* This results in a blurred image as well
//Draw it back but scaled
originalContext.scale( originalCanvas.width / tmpCanvas.width, originalCanvas.height / tmpCanvas.height );
originalContext.drawImage( tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height, 0, 0, tmpCanvas.width, tmpCanvas.height );
*/
}
Is there a way to get the strokes and "scale" all those points and redraw?
Instead of taking the rendered image from the original canvas, actually redraw the image. By that, I mean execute the same logic you executed against the original canvas, but with the points involved scaled to the new size.
If you can, think about using SVG instead. It scales well by its nature.
Edit: Another option I've thought of is to simply use a gigantic canvas to start with. Sizing down tends to look better than sizing up, especially with smoothing on.
Edit II: The original answer was irrelevant, though the comment I had made is relevant, and am now promoting it and editing it to be an answer...and the answer I had given was not all that great anyway **.
Of course if you scale up raster graphics, that is, from an image with a smaller pixel dimensions of pixels, create an image with higher pixel dimensions, you are going to get blurred images. By scaling up, you're making a low resolution picture high resolution, but without the high resolution details.
There's absolutely no way around that blurriness unless you make multiple additional assumptions about your raster image like the only gray you'd see is at an image edge, or corners can only occur at apparent inflection points where the angle between the tangents of the joined curves must be 100 degrees or less. Essentially, you'd have to give additional information so that your higher resolution image can have detail "filled in". It's not all that terribly different from reverse engineering an SVG from a raster.
So, you appear to want to emulate is scaling vector graphics, in which the only solution is to save the commands, draw a SVG, or draw to a bigger canvas like Stuart Branham suggested.
** I had originally proposed that invoking drawImage would distort the pixels even if it were not scaled, and that it would be better to work with the actual pixel data. If that's true, I can't find proof, but that's irrelevant, as he wanted his image scaled up, without blurring...which is impossible, as I just mentioned.

HTML5/Javascript - How to get the coordinates of a shape/image on the canvas?

Say I drew a rectangle on the canvas. Surely there is some sort of built in method to get the XY coordinates, and dimensions of that rectangle? But after some googling I came up with nothing. And just to clarify, I am not talking about the coordinates of the canvas element itself, but rather a shape/image that is drawn unto the canvas.
Any help is appreciated.
If you're talking about a 2D canvas drawing, then the drawing maps 1:1 with screen coordinates, so it is just location of <canvas> + location of the drawing.
To clarify, drawing on a <canvas> basically just changes the pixels of the canvas - after you draw to it, you can't reference the drawn object the same way you can reference an html element.
Canvas is 2D table (Array) of numbers (= pixels = colors). When drawing into canvas, you are just editing this table. When you draw into canvas (= change numbers in table), what should be the coordinates of your adjustment?
If you are drawing rectangles only and you can define the coordinates for your rectangle, you must know your coordinates inside a program, because you have just drawn it.
If you want your image to be separated into some "objects" (shapes), you should use SVG.
Basically, you should be using canvas as a means to output graphics to the screen and the rest of your logic goes straight into the JavaScript that powers your game/application. The best way to go about making something like this is to create objects and assign properties to them; in its simplest form that can look like this:
function Player(x, y)
{
this.x = x;
this.y = y;
}
var examplePlayerObject = new Player(20, 20);
By extending this object via prototyping you can create multiple copies of an object that has the exact same functions; such as draw. Drawing the player in this instance could just be a red square that is 20px*20px.
Player.prototype.draw = function()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'red';
context.fillRect(this.x, this.y, 20, 20);
}
Then, you should have an update step with some means of clearing what is on the screen and redrawing the parts which have changed.
function animationStep()
{
examplePlayerObject.x++;
examplePlayerObject.y++;
examplePlayerObject.draw();
}
This animation step should run each frame; look into requestAnimationFrame for smooth animation. Paul Irish has a good shim for older browsers. Add in requestAnimationFrame(animationStep) at the end of that function and you will have a red square moving slowly across the screen. Good luck!

Pixel perfect 2D mouse picking with Canvas

I'm writing a 2D game in html5 using Canvas which requires mouse click and hover events to be detected. There are 3 problems with this: detections must be pixel-perfect, objects are not rectangular (houses, weird-shaped UI buttons...), and it is required to be fast and responsive. (Obviously brute force is not an option)
So what I want to ask is how do I find out which object the mouse is on, and what are the possible optimizations.
P.S: I did some investigation and found a guy who used QuadTree here.
I have a (dated) tutorial that explains the concept of a ghost canvas which is decent for pixel-perfect hit detection. The tutorial is here. Ignore the warning about a newer tutorial, the newer one does not use the ghost canvas concept.
The idea is to draw the image in question to an in-memory canvas and then use getImageData to get the single pixel of the mouse click. Then you see if that single pixel is fully transparent or not.
If its not fully transparent, well, you've got your target.
If it is fully transparent, draw the next object to the in-memory canvas and repeat.
You only have to clear the in-memory canvas at the end.
getImageData is slow but it is your only option if you want pixel-perfect hit detection and aren't pre-computing anything.
Alternatively you could precompute a path or else an array of pixels with an offset. This would be a lot of work but might be faster. For instance if you have a 40x20 image with some transparency you'd compute an array[40][20] that would have true or false corresponding to transparent or not. Then you'd test that against the mouse position, with some offset, if the image is drawn at (25, 55) you'd want to subtract that from the mouse position and then test if the new position is true when you look at array[posx][posy].
That's my answer to your question. My Suggestion? Forget pixel-perfect detection if this is a game.
Seriously.
Instead make paths (not in canvas, in plain javascript code) that represent the objects but are not pixel perfect, for instance a house might be a square with a triangle on the top that is a very close approximation of the image but is used in its stead when it comes to hit testing. It is comparatively extremely fast to compute if a point is inside a path than it is to do pixel-perfect detection. Look up point in polygon winding number rule detection. That's your best bet, honestly.
The common solution in traditional game development is to build a click mask. You can re-render everything onto a separate off-screen canvas in a solid color (the rendering should be very quick). When you want to figure out what was clicked on, you simply sample the color at the x/y co-ordinate on the off-screen canvas. You end up building a color-->obj hash, akin to:
var map = {
'#000000' : obj1
, '#000001' : obj2
, ...
};
You can also optimize the rendering to the secondary canvas to only happen when the user clicks on something. And using various techniques, you can further optimize it to only draw the part of the canvas that the user has clicked on (for example, you can split you canvas into an NxN grid, e.g. a grid of 20x20 pixel squares, and flag all of the objects in that square -- you'd then only need to re-draw a small number of objects)
HTML5 Canvas is just a drawing plane, where you can set different transforms before calling each drawing API function. Objects cannot be created and there is no display list. So you have to build these features yourself or you can use different libraries available for this.
http://www.kineticjs.com/
http://easeljs.com/
A few months before I got interested in this and even wrote a library for this purpose. You can see it here : http://exsprite.com. Ended up facing a lot of performance issues, but because of lack of time I couldn't optimize it. It was really interesting, so waiting for some time to make it perfect.
I believe the comments should suffice. This is how I determine user intention in my 2d isometric scroller, currently located at http://untitled.servegame.com
var lastUp = 0;
function mouseUp(){
mousedown = false; //one of my program globals.
var timeNow = new Date().getTime();
if(mouseX == xmouse && mouseY == ymouse && timeNow > lastUp + 100){//if it was a centralized click. (mouseX = click down point, xmouse = mouse's most recent x) and is at least 1/10th of a second after the previous click.
lastUp = new Date().getTime();
var elem = document.elementFromPoint(mouseX, mouseY); //get the element under the mouse.
var url = extractUrl($(elem).css('background-image')); // function I found here: http://webdevel.blogspot.com/2009/07/jquery-quick-tip-extract-css-background.html
imgW = $("#hiddenCanvas").width(); //EVERY art file is 88px wide. thus my canvas element is set to 88px wide.
imgH = $(elem).css('height').split('p')[0]; //But they vary in height. (currently up to 200);
hiddenCanvas.clearRect(0, 0, imgW, imgH); //so only clear what is necessary.
var img = new Image();
img.src = url;
img.onload = function(){
//draw this elements image to the canvas at 0,0
hiddenCanvas.drawImage(img,0,0);
///This computes where the mouse is clicking the element.
var left = $(elem).css('left').split('p')[0]; //get this element's css absolute left.
var top = $(elem).css('top').split('p')[0];
offX = left - offsetLeft; //left minus the game rendering element's absolute left. gives us the element's position relative of document 0,0
offY = top - offsetTop;
offX = mouseX - offX; //apply the difference of the click point's x and y
offY = mouseY - offY;
var imgPixel = hiddenCanvas.getImageData(offX, offY, 1, 1); //Grab that pixel. Start at it's relative X and it's relative Y and only grab one pixel.
var opacity = imgPixel.data[3]; //get the opacity value of this pixel.
if(opacity == 0){//if that pixel is fully transparent
$(elem).hide();
var temp = document.elementFromPoint(mouseX, mouseY); //set the element right under this one
$(elem).show();
elem = temp;
}
//draw a circle on our hiddenCanvas so when it's not hidden we can see it working!
hiddenCanvas.beginPath();
hiddenCanvas.arc(offX, offY, 10, 0, Math.PI*2, true);
hiddenCanvas.closePath();
hiddenCanvas.fill();
$(elem).css("top", "+=1"); //apply something to the final element.
}
}
}
In conjunction with this:
<canvas id="hiddenCanvas" width="88" height="200"></canvas>
Set the CSS positioning absolute and x = -(width) to hide;

Categories