How do I clip a image obtained from the getImageData() function? - javascript

I'm trying to get the contents of a source canvas, clip it, and then draw it on another canvas. Even though my code works like a charm using a src PNG / new Image() combo, it does not when the source content comes from another canvas.
the code is:
var imgData = src_ctx.getImageData(x, y, w, h);
dest_ctx.putImageData(imgData, x, y+h);
ctx.beginPath(); // Filled triangle
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.lineTo(x2,0);
ctx.lineTo(x1,0);
ctx.clip();

After defining the clipping region, draw the source canvas using drawImage, instead of setting the imagedata.
dest_ctx.beginPath(); // Filled triangle
dest_ctx.moveTo(x1,y1);
dest_ctx.lineTo(x2,y2);
dest_ctx.lineTo(x2,0);
dest_ctx.lineTo(x1,0);
dest_ctx.clip();
// You can control wich region to draw using all the arguments
// drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
dest_ctx.drawImage (srcCanvas, x, y);

getImageData is an almost useless function unless you know what you're doing (ie. checking for hit detection, filtering pixels) but even then it is painfully slow.
I created a JSfiddle example for you fiddle around with (see what I did there!)
The heart of the code is as follows:
1 canvas = document.getElementById('canvas');
2 ctx = canvas.getContext("2d");
3 _canvas=document.createElement('canvas');
4 _ctx = _canvas.getContext("2d");
5 _canvas.width = 200;
6 _canvas.height = 200;
7
8 _ctx.beginPath();
9 _ctx.arc(100, 100, 100,0,Math.PI*2,true);
10 _ctx.clip();
11 _ctx.drawImage(img1, 0, 0);
12
13 ctx.drawImage(_canvas, 1.25 * i * _canvas.width, 500);
Essentially what you are doing is clipping to a cache canvas (_canvas, lines 10 and 11) and drawing that to the main canvas (canvas, line 13).
Note: Ideally you would translate your image so it would be in the center of the clip, but I still can not get my head around translations, especially when coupled with other transformations such as clips.

Related

Why does javascript canvas2d clipping require a path?

I was pulling my hair out over this bug for a while. I wanted to render images in three sections of a canvas, without allowing them to overlap. Basically, I wanted to use canvas.getContext('2d').clip() to keep the images separated. However, the clip only works if I call canvas.getContext('2d').beginPath() after I draw the image.
So this does not work (no clip is applied):
this.draw=function(image, cx, cy, width, height, clip){
var ctx = this.canvas.getContext('2d');
ctx.save();
ctx.rect(clip.x, clip.y, clip.width, clip.height);
ctx.clip();
ctx.fillStyle = "black";
ctx.fillRect(clip.x, clip.y, clip.width, clip.height);
ctx.drawImage(image,cx-width/2,cy-height/2,width,height);
ctx.restore();
return this;
};
But this does:
this.draw=function(image, cx, cy, width, height, clip){
var ctx = this.canvas.getContext('2d');
ctx.save();
ctx.rect(clip.x, clip.y, clip.width, clip.height);
ctx.clip();
ctx.fillStyle = "black";
ctx.fillRect(clip.x, clip.y, clip.width, clip.height);
ctx.drawImage(image,cx-width/2, cy-height/2,width,height);
ctx.beginPath();// <------WITCHCRAFT
ctx.restore();
return this;
};
It was a total accident that I discovered that beginPath() fixes the problem, and I have no idea why. Can anyone explain this to me?
Because clipping requires a path? Perhaps you missed it in the documentation. Here's what MDN documentation says:
The CanvasRenderingContext2D.clip() method of the Canvas 2D API turns the path currently being built into the current clipping path.
(emphasis mine)
The reason it needs a path is because clipping mask can be any arbitrary shape from rectangles to circles to the outline of Pikachu.
For the sake of completeness, here's what the W3C spec says about .clip():
https://www.w3.org/TR/2dcontext/#drawing-paths-to-the-canvas
context . clip()
Further constrains the clipping region to the current path.

High Resolution HTML5 Canvas Screenshot

I have been trying to take a high resolution screenshot of an HTML5 canvas element that I have for a visualization consisting of rectangles and circles. The canvas.toDataURL() works great, except that the image produced is limited to the size of the original canvas. What I would really like is to take a screenshot that is 4 or 5 times that of the original canvas.
My strategy, however, has been to create a temporary canvas off-screen like the following:
function renderScreenshot(canvas, scaleFactor) {
var ctx = canvas.getContext('2d');
var screenshotCanvas = document.createElement('canvas');
var screenshotCtx = screenshotCanvas.getContext('2d');
screenshotCanvas.width = canvas.width * scaleFactor;
screenshotCanvas.height = canvas.height * scaleFactor;
screenshotCtx.drawImage(canvas, 0, 0, canvas.width * scaleFactor, canvas.height * scaleFactor);
return screenshotCanvas.toDataURL();
}
The problem now is that the scaled image is blurry and pixilated, and does not do me any good. What is the way around this?
Unfortunately, while you can draw paths like you would in a vector image, once they are drawn they become rasterised and cannot be scaled without interpolation.
Instead you would need to completely redraw everything, multiplying all coordinates by your scale factor, to render the path elements properly.

Transparency lost with getImageData - HTML5 2d Context

I noticed a strange problem with getImageData; transparency of the image seems to be ignored when the image data is fetched.
Since any image needs to be drawn on to a canvas before its image data can be obtained, I assumed this was a problem with the canvas in question being opaque. But I was wrong, since using the canvas as an argument in drawImage maintains transparency.
Here is how I loaded the image;
var load_image = function(name, url, holder, callback) {
var img = new Image();
img.src = url;
img.addEventListener('load', function(e) {
var canvas = make_canvas(e.target.width, e.target.height);
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(e.target, 0, 0);
holder[name] = {canvas: canvas, ctx: ctx};
delete e.target;
callback.call();
}, false);
};
The callback is simply the draw function, which invokes draw_image to draw the image.
The usual version;
var draw_image = function(ctx, img, sx, sy, w, h, dx, dy) {
ctx.drawImage(img.canvas, sx, sy, w, h, dx, dy, w, h);
};
It simply takes the canvas as an argument for drawImage, and the result is as intended with transparency maintained. Example.
The image data version;
var draw_image = function(ctx, img, sx, sy, w, h, dx, dy) {
var imagedata = img.ctx.getImageData(sx, sy, w, h);
ctx.putImageData(imagedata, dx, dy);
};
This one obtains the image data of the required rectangle from the same canvas as the one used in the usual version, and puts the image data on the canvas I want to draw to. I believe transparency should be maintained, but this is not so. Example. (This is a Dropbox link because of the origin-clean flag.)
Am I wrong in assuming that transparency should be maintained with getImageData? Or am I just using it in a wrong manner?
Either way, help would really be appreciated.
I believe your problem is that putImageData doesn't use a composite operation to blend the source and destination image data. Rather, it's doing a direct write of the red, green, blue, and alpha channels into the canvas. For fully transparent pixels, this means they may or may not appear in the color you expect.
Instead, you can create an intermediate canvas element, draw to that, and then use drawImage() to render that to your target canvas with the appropriate globalCompositeOperation being applied. Mask for putImageData with HTML5 canvas? discusses this issue in more detail.
Update: You can verify that the data you're writing into your "broken" example actually does have transparent data in it by doing ctx.getImageData(230,50,1,1).data. The result is [0,0,0,0] - i.e. a fully transparent pixel.

How to properly draw and update (special) raw image data on canvas without duplication of data ? (html5)

I don't think the title is very accurate but I can't think of any better.
I'm working with medical images, in these kind of images there isn't definitive pixels values, you have to extract some information and calculate yourself every pixel value. A second particularity is that image resolution can be very huge.
So once I loaded the image into memory I have an ArrayBuffer and some other attributes needed for calculation. Since these images aren't based on any common format I can't use image DOMElement to draw them on a canvas.
Here the 2 related issue I have :
1) I have to use ImageData to draw my image into a canvas, this means I have to iterate over my ArrayBuffer calculate each pixel value and stored them on an ImageData, then I use canvas.putImageData() and the browser will probably iterate over again the ImageData and display pixels. I don't like this because it needs to iterate twice in order to display the image and that every time it's update.
2) As I said images can be very huge so I need to use scaling/slicing, but canvas only provides these features for drawImage not for putImageData. If I don't want to calculate it myself I have to use a buffer canvas, copy the entire image in it and then use drawImage with this buffer canvas as image.
This is bad for 2 reasons :
I'm duplicating data, I have to keep the original ArrayBuffer and a buffer canvas. The buffer canvas can be created and deleted but as I need to update the image on the mouse movement it's better to keep it.
If a change is made on the image like the contrast/brightness I have to re-calculate it for the entire image (buffer canvas creation) even if I'm just displaying a small part of it.
Here is some code :
//image contains raw data: TypedArrayView and some attributes
// size (2828,2320)
var image;
//displayed canvas
// size (500,500)
var canvas;
//buffer canvas
// size (2828,2320)
var buffCanvas;
function drawImage(sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight){
var buffContext = buffCanvas.getContext('2d');
var imageData = buffContext.getImageData(buffCanvas.width,buffCanvas.height);
//Iterate over all the image (2828,2320)
for(var i = 0; i < imageData.data.length; i++ )
//process pixel calculation
imageData[i] = image.buffer[i] + SomeCalculation;
buffContext.putImageData(imageData,0,0);
canvas.getContext('2d').drawImage(buffCanvas,sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
}
function onmousemove(){
//change some image properties
image.contrast = 150;
...
drawImage(sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
}
I'm kinda new on this so I must be missing something because the way I'm doing this can't be productive.
My dream would be to create a view like it's done for ArrayBuffer or to override(not possible) bracket operator or create a fake image element. This could work this way :
function MedicalImage(data, ...){
...
}
MedicalImage.prototype.getPixelValueAt = function(pos){
return data[pos] + SomeCalculation;
}
var img = new MedicalImage();
context.drawImage(img,sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
And browser would call getPixelValueAt() to get the pixel value when needed.

How can I clear an arc or circle in HTML5 canvas?

I found that there's a clearRect() method, but can't find any to clear an arc (or a full circle).
Is there any way to clear an arc in canvas?
There is no clearArc however you can use Composite Operations to achieve the same thing
context.globalCompositeOperation = 'destination-out'
According to MDC the effect of this setting is
The existing content is kept where it doesn't overlap the new shape.
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing
So any filled shape with this mode on will end up erasing current canvas content.
This is a circular equivalent of clearRect(). The main thing is setting up a composite operation per #moogoo's answer.
var cutCircle = function(context, x, y, radius){
context.globalCompositeOperation = 'destination-out'
context.arc(x, y, radius, 0, Math.PI*2, true);
context.fill();
}
See https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html:
Nope, once you've drawn something on a canvas there is no object to clear, just the pixels you've drawn. The clearRect method doesn't clear a previously drawn object, it just clears the pixels in the space defined by the parameters. You can use the clearRect method to clear the arc if you know a rectangle which contains it. This will of course clear any other pixels in the area, so you'll have to redraw them.
Edit: MooGoo has given a much better answer below
You can use the clearRect() method to erase a portion of the canvas (including your arc), but when you're using clearRect() with arcs or anything else that you used beginPath() and closePath() for while drawing, you'll need to handle the paths while erasing, too. Otherwise, you may end up with a faded version of your arc still appearing.
//draw an arc (in this case, a circle)
context.moveTo(x, y);
context.beginPath();
context.arc(x,y,radius,0,Math.PI*2,false);
context.closePath();
context.strokeStyle = "#ccc";
context.stroke();
//now, erase the arc by clearing a rectangle that's slightly larger than the arc
context.beginPath();
context.clearRect(x - radius - 1, y - radius - 1, radius * 2 + 2, radius * 2 + 2);
context.closePath();
Make sure to call beginPath()
function animate (){
requestAnimationFrame(animate)
c.clearRect(0,0,canvas.width,canvas.height);
c.beginPath();
c.arc(x,y,40,0,Math.PI * 2,false);
c.strokeStyle='rgba(200,0,0,1)';
c.stroke();
c.fillStyle ='rgba(0,0,0,1)';
c.fillRect(x,y,100,100);
x++
} animate()
Credit to #Gabriele Petrioli in this answer: Why doesn't context.clearRect() work inside requestAnimationFrame loop?
Here's an updated fiddle for you too (uses clearRect): https://jsfiddle.net/x9ztn3vs/2/
It has a clearApple function:
block.prototype.clearApple = function() {
ctx.beginPath();
ctx.clearRect(this.x - 6, this.y - 6, 2 * Math.PI, 2 * Math.PI);
ctx.closePath();
}

Categories

Resources