Test canvas drawings with Protractor - javascript

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));
});
});

Related

How can I draw from ImageData to canvas with zoom in Haxe?

Well, I fill ScreenBuffer:ImageData 480x360 and then want to draw it to the canvas 960x720. The task is to decrease the fillrate; the nowadays pixels are very small and we can make them bigger with some quality loss. I look for the operator with 2D-accelaration. But we can't write directly to js.html.Image and ImageData hasn't link to js.html.Image. I found an example for pure JS:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas
However, it doesn't want to work in Haxe because there isn't 'zoom' element. And there is some information about restrictions in HTML at copying from one image to another.
Many thanks for answers!
The compiler writes "js.html.Element has no field getContext"
getElementById()'s return type is the generic js.html.Element class. Since in your case, you know you're dealing with a <canvas>, you can safely cast it to the more specific CanvasElement. This then lets you call its getContext() method:
var canvas:CanvasElement = cast js.Browser.document.getElementById('zoom');
var zoomctx = canvas.getContext('2d');

Saving canvas to image via canvas.toDataURL results in black rectangle

Im using Pixi.js and trying to save a frame of the animation to an image. canvas.toDataUrl should work, but all i get is a black rectangle. See live example here
the code I use to extract the image data and set the image is:
var canvas = $('canvas')[0];
var context = canvas.getContext('2d');
$('button').click(function() {
var data = renderer.view.toDataURL("image/png", 1);
//tried var data = canvas.toDataURL();
$('img').attr('src', data);
})
I know this has been answered at least 5 other times on SO but ...
What Kaiido mentioned will work but the real issue is that canvas, when used with WebGL, by default has 2 buffers. The buffer you are drawing to and the buffer being displayed.
When you start drawing into a WebGL canvas, as soon as you exit the current event, for example your requestAnimationFrame callback, the canvas is marked for swapping those 2 buffers. When the browser re-draws the page it does the swap. The buffer that you were drawing to is swapped with the one that was being displayed. You're now drawing to other buffer. That buffer is cleared.
The reason it's cleared instead of just left alone is that whether the browser actually swaps buffers or does something else is up to the browser. For example if antialiasing is on (which is the default) then it doesn't actually do a swap. It does a "resolve". It converts the highres buffer you just drew to a normal res anti-aliased copy into the display buffer.
So, to make it more consistent, regardless of which way the browser does its default it just always clears whatever buffer you're about to draw to. Otherwise you'd have no idea if it had 1 frame old data or 2 frame old data.
Setting preserveDrawingBuffer: true tells the browser "always copy, never swap". In this case it doesn't have to clear the drawing buffer because what's in the drawing buffer is always known. No swapping.
What is the point of all that? The point is, if you want to call toDataURL or gl.readPixels you need to call it IN THE SAME EVENT.
So for example your code could work something like this
var capture = false;
$('button').click(function() {
capture = true;
});
function render() {
renderer.render(...);
if (capture) {
capture = false;
var data = renderer.view.toDataURL("image/png", 1);
$('img').attr('src', data);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
In that case because you call toDataURL in the same javascript event as you rendered to it you'll get the correct results always regardless of wither or not preserveDrawingBuffer is true or false.
If you're writing app that is not constantly rendering you could also do something like
$('button').click(function() {
// render right now
renderer.render(...);
// capture immediately
var data = renderer.view.toDataURL("image/png", 1);
$('img').attr('src', data);
});
The reason preserveDrawingBuffer is false by default is because swapping is faster than copying so this allows the browser to go as fast as possible.
Also see this answer for one other detail
[NOTE]
While this answer is the accepted one, please do read the one by #gman just below, it does contain a way better way of doing.
Your problem is that you are using webGL context, then you need to set the preserveDrawingBuffer property of the webGL context to true in order to be able to call toDataURL() method.
Or alternatively, you can force pixi to use the 2D context, by using the CanvasRenderer Class

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.

Mask for putImageData with HTML5 canvas?

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)

Categories

Resources