Mask for putImageData with HTML5 canvas? - javascript

I want to take an irregularly shaped section from an existing image and render it as a new image in Javascript using HTML5 canvases. So, only the data inside the polygon boundary will be copied. The approach I came up with involved:
Draw the polygon in a new canvas.
Create a mask using clip
Copy the data from the original canvas using getImageData (a rectangle)
Apply the data to the new canvas using putImageData
It didn't work, the entire rectangle (e.g. the stuff from the source outside the boundary) is still appearing. This question explains why:
"The spec says that putImageData will not be affected by clipping regions." Dang!
I also tried drawing the shape, setting context.globalCompositeOperation = "source-in", and then using putImageData. Same result: no mask applied. I suspect for a similar reason.
Any suggestions on how to accomplish this goal? Here's basic code for my work in progress, in case it's not clear what I'm trying to do. (Don't try too hard to debug this, it's cleaned up/extracted from code that uses a lot of functions that aren't here, just trying to show the logic).
// coords is the polygon data for the area I want
context = $('canvas')[0].getContext("2d");
context.save();
context.beginPath();
context.moveTo(coords[0], coords[1]);
for (i = 2; i < coords.length; i += 2) {
context.lineTo(coords[i], coords[i + 1]);
}
//context.closePath();
context.clip();
$img = $('#main_image');
copy_canvas = new_canvas($img); // just creates a new canvas matching dimensions of image
copy_ctx = copy.getContext("2d");
tempImage = new Image();
tempImage.src = $img.attr("src");
copy_ctx.drawImage(tempImage,0,0,tempImage.width,tempImage.height);
// returns array x,y,x,y with t/l and b/r corners for a polygon
corners = get_corners(coords)
var data = copy_ctx.getImageData(corners[0],corners[1],corners[2],corners[3]);
//context.globalCompositeOperation = "source-in";
context.putImageData(data,0,0);
context.restore();

dont use putImageData,
just make an extra in memory canvas with document.createElement to create the mask and apply that with a drawImage() and the globalCompositeOperation function (depending on the order you need to pick the right mode;
I do something similar here the code is here (mind the CasparKleijne.Canvas.GFX.Composite function)

Related

Turning a canvas element into an image object

I have a primary canvas element which I perform drawing operations on using the context. I can successfully draw images, shapes, and also transform those objects using the context.
I now need my own textbox. I want to create another canvas, render the text to that canvas, then copy that data onto the primary canvas... the catch is I want to have it transformable, almost is if I was just calling the drawImage function.
There exists the getImageData and putImageData methods, but those do not work because it does not take into consideration the transformation.
I am looking for something like this...
var tempcanvas = document.createElement("canvas");
var ctx = tempcanvas.getContext('2d');
ctx.fillRect(0,0,5,5);
then on my primary canvas which I already have, I would like to draw that as if it were an image object.
context = maincanvas.getContext('2d');
context.translate(50,50);
context.rotate(180);
context.drawImage(tempcanvas)

Animate a circle being drawn using Paper.js

I'm trying to animate a circle being drawn using Paper.js.
As the circle tool is just a quick access for instantiating a path constructed via moveTo/arcTo etc, there are no arguments to support start and end angles (for open pie chart-like circles).
What I am looking for is a way to animate the circle being drawn from it's first point to an angle of my choice at a certain radius.
The actual canvas specification allows for explicit startAngle and endAngle to be specified. If this was the case within Paper.js I could easily achieve what I am looking for. However, within Paper.js I have yet to come across a method of replicating such control. I created something in Fabric.js that worked as Fabric's implementation of the circle shape used the same attributes as the arc command in the specification.
Does anyone know of a way this can be achieved so I can animate the endAngle?
Here's a conversion function that accepts html5 canvas arc arguments and returns the from, through, to arguments needed for a Paper.js arc.
function canvasArcToPaperArc(cx,cy,radius,startAngle,endAngle,strokecolor){
var startX=cx+radius*Math.cos(startAngle);
var startY=cy+radius*Math.sin(startAngle);
var endX=cx+radius*Math.cos(endAngle);
var endY=cy+radius*Math.sin(endAngle);
var thruX=cx+radius*Math.cos((endAngle-startAngle)/2);
var thruY=cy+radius*Math.sin((endAngle-startAngle)/2);
var from = new Point(startX,startY);
var through = new Point(thruX,thruY);
var to = new Point(endX,endY);
return({from:from, through:through, to:to});
}

Test canvas drawings with Protractor

Is there a way to test if a drawing was made on a canvas using Protractor?
i.e. I draw a rectangle based on user clicks:
var shape = new createjs.Shape();
shape.graphics.beginStroke("black");
shape.graphics.drawRect(crd.x, crd.y, crd.width, crd.height);
stage.addChild(shape)
stage.update()
Now I want to make a spec to test if a rectangle was drawn on the specified coordinates and, as a plus, to test if its borders are of color black.
Is this possible using Protractor/WebDriverJS API?
The way that we test our canvas in protractor is as follows:
We set up a "well known" base64 image string that represents what we want our canvas to be after we draw on it. Then we use browser.executeScript to get the dataUrl of the canvas. We then compare string to string and that tells us if the drawing was correct or not.
var base64ImageString = "data:image/png;base64,iVBORw0KGgoAA...snipped for brevity";
describe("The Canvas", function () {
browser.get('/#'));
/*
.
do your drawing
.
*/
it("should contain the right drawings", function(){
browser.executeScript("return document.getElementsByTagName('canvas')[0].toDataURL()").then(function (result) {
expect(result).toBe(base64ImageString);
});
});
});
Works like a champ for us. We're experimenting with getting the Uint8ClampedArray to see if it's any easier - but so far this method is great except for a subtle gotcha.
In our experience, the image string that we get back from the toDataUrl method only represents the visible area of the canvas - not the entire canvas. It's good enough for us - but your mileage may vary. It's also why we're experimenting with thy byte array because it allows you to specify a specific X x Y area of the canvas.
This might be possible but you would have to create a dummy canvas with the desired output.
you could compare the Imagedata from the dummyCanvas to the imagedata from the browser objects canvas.
It should look something along the lines of:
describe('Canvas Test', function() {
it('should have a title', function() {
browser.get('http://whenthetestShouldrun.com');
var dummyCanvas= document.createElement('canvas');
//some code to edit the canvas to what you want
expect(browser.By.id('canvas').getImageData(imageX, imageY, imageWidth, imageHeight)).toEqual(dummyCanvas.getImageData(imageX, imageY, imageWidth, imageHeight));
});
});

Capturing only a portion of canvas with .todataurl Javascript/HTML5

I can capture a full canvas with .todataurl without a problem. But I do not see or know if there is anyway to capture only a portion of the canvas and save that to image.
e.i. Mr. Potatohead script draws hats, hands feet faces etc etc. mixed all over the canvas and you can drag and drop them onto the mr potato in the center of the canvas. Press a button save the image of mr potato looking all spiffy to jpg for you. Without all the extra hats/feet/faces in the image.
I have resigned myself to the fact that this is impossible based on everything I've read. But you folks have proven to be smarter than google (or atleast google in my hands) a few times so i am taking a shot.
Sorry no code to post this time... unless you want this:
var canvas = document.getElementById("mrp");
var dataUrl = canvas.toDataURL();
window.open(dataUrl, "toDataURL() image", "width=800, height=600");
But that is just the example of dataurl i am working off of.. and it works outside of the fact it doesnt cap just the mr potato
My fallback is to pass the image to php and work with it there to cut out everything i dont want then pass it back.
EDIT
tmcw had a method for doing this. Not sure if its the way it SHOULD be done but it certainly works.
document.getElementById('grow').innerHTML="<canvas id='dtemp' ></canvas>";
var SecondaryCanvas = document.getElementById("dtemp");
var SecondaryCanvas_Context = SecondaryCanvas.getContext ("2d");
SecondaryCanvas_Context.canvas.width = 600;
SecondaryCanvas_Context.canvas.height = 600;
var img = new Image();
img.src = MainCanvas.toDataURL('image/png');
SecondaryCanvas_Context.drawImage(img, -400, -300);
var du = SecondaryCanvas.toDataURL();
window.open(du, "toDataURL() image", "width=600, height=600");
document.getElementById('grow').innerHTML="";
grow is an empty span tag, SecondaryCanvas is a var created just for this
SecondaryCanvas_Context is the getcontext of SecondaryCanvas
img created just to store the .toDataURL() of the main canvas containing the Mr. PotatoHead
drawImage with negative (-) offsets to move image of MainCanvas so that just the portion i want is showing.
Then cap the new canvas that was just created and open a new window with the .png
on and if you get an error from the script saying security err 18 its because you forgot to rename imgTop to img with the rest of the variables you copy pasted and chrome doesnt like it when you try to save local content images like that.
Here's a method that uses an off-screen canvas:
var canvas = document.createElement('canvas');
canvas.width = desiredWidth;
canvas.height = desiredHeight;
canvas.getContext('2d').drawImage(originalCanvas,x,y,w,h,0,0,desiredWidth, desiredHeight);
result = canvas.toDataURL()
Create a new Canvas object of a specific size, use drawImage to copy a specific part of your canvas to a specific area of the new one, and use toDataURL() on the new canvas.
A bit more efficient (and maybe a cleaner) way of extracting part of the image:
// x,y are position in the original canvas you want to take part of the image
// desiredWidth,desiredHeight is the size of the image you want to have
// get raw image data
var imageContentRaw = originalCanvas.getContext('2d').getImageData(x,y,desiredWidth,desiredHeight);
// create new canvas
var canvas = document.createElement('canvas');
// with the correct size
canvas.width = desiredWidth;
canvas.height = desiredHeight;
// put there raw image data
// expected to be faster as tere are no scaling, etc
canvas.getContext('2d').putImageData(imageContentRaw, 0, 0);
// get image data (encoded as bas64)
result = canvas.toDataURL("image/jpeg", 1.0)
you can give left,top,width and Height parameters to toDataURL function.. Here is the code to get data image depending on the object on canvas.
mainObj = "your desired Object"; // for example canvas._objects[0];
var image = canvas.toDataURL({ left: mainObj.left, top:mainObj.top,
width: mainObj.width*mainObj.scaleX, height: mainObj.height*mainObj.scaleY});

html5 canvas - Saving paths or clip areas to reuse

I'm currently implementing a 2d deformable terrain effect in a game I'm working on and its going alright but I can envision it becoming a performance hog very fast as I start to add more layers to the effect.
Now what I'm looking for is a way to possibly save a path, or clipping mask or similar instead of having to store each point of the path in the terrain that i need to draw through each frame. And as I add more layers I will have to iterate over the path more and more which could contain thousands of points.
Some very simple code to demonstrate what I'm currently doing
for (var i = 0; i < aMousePoints.length; i++)
{
cRenderContext.save();
cRenderContext.beginPath();
var cMousePoint = aMousePoints[i];
cRenderContext.arc(cMousePoint.x, cMousePoint.y, 30, 0, 2 * Math.PI, false);
cRenderContext.clip();
cRenderContext.drawImage(cImg, 0, 0);
cRenderContext.closePath();
cRenderContext.restore();
}
Basically I'm after an effecient way to draw my clipping mask for my image over and over each frame
Notice how your clipping region stays exactly the same except for its x/y location. This is a big plus.
The clipping region is one of the things that is saved and restored with context.save() and context.restore() so it is possible to save it that way (in other words defining it only once). When you want to place it, you will use ctx.translate() instead of arc's x,y.
But it is probably more efficient to do it a second way:
Have an in-memory canvas (never added to the DOM or shown on the page) that is solely for containing the clipping region and is the size of the clipping region
Apply the clipping region to this in-memory canvas, and then draw the image onto this canvas.
Then use drawImage with the in-memory canvas onto your game context. In other words: cRenderContext.drawImage(in-memory-canvas, x, y); where x and y are the appropriate location.
So this way the clipping region always stays in the same place and is only ever drawn once. The image is moved on the clipping-canvas and then drawn to look correct, and then the in-memory canvas is drawn to your main canvas. It should be much faster that way, as calls to drawImage are far faster than creating and drawing paths.
As a separate performance consideration, don't call save and restore unless you have to. They do take time and they are unnecessary in your loop above.
If your code is open-source, let me know and I'll take a look at it performance-wise in general if you want.
Why not have one canvas for the foreground and one canvas for the background? Like the following demo
Foreground/Background Demo (I may of went a little overboard making the demo :? I love messing with JS/canvas.
But basically the foreground canvas is transparent besides the content, so it acts like a mask over the background canvas.
It looks like it is now possible with a new path2D object.
The new Path2D API (available from Firefox 31+) lets you store paths, which simplifies your canvas drawing code and makes it run faster. The constructor provides three ways to create a Path2D object:
new Path2D(); // empty path object
new Path2D(path); // copy from another path
new Path2D(d); // path from from SVG path data
The third version, which takes SVG path data to construct, is especially handy. You can now re-use your SVG paths to draw the same shapes directly on a canvas as well:
var p = new Path2D("M10 10 h 80 v 80 h -80 Z");
Information is taken from Mozilla official site.

Categories

Resources