How to reset canvas context state entirely - html

I have a simple canvas animation, as follows:
function animate(){
var canvas = document.getElementById("canvas");
canvas.width = $(window).width();
canvas.addEventListener("click", replay, false);
var context = canvas.getContext("2d");
//animation code
function replay(e){
animate();
}
}
So my expectation is when the user clicks the canvas the animation will replay because I am reassigning the width:
canvas.width = $(window).width();
which will reset the entire context state ( read it here http://diveintohtml5.info/canvas.html)
It works the first time you click the canvas but after that it remembers the context transformation state and shows some weird animation.
I tried adding this:
context.setTransform( 1, 0, 0, 1, 0, 0 );
to explicitly reset the transformation matrix to identity matrix before re-drawing but has no effect.
It is different from this How to clear the canvas for redrawing
because it is about how to clear the display of context but I want to clear everything the display plus the context state.
I tried logging the state of each variable during the animation I get something weird with the ff code.
function moveHorizontal(x){
if(x < 100 ){
context.translate(1, 0);
console.log("x:"+x);
drawImage();
x += 1;
setTimeout(moveHorizontal,10,x);
}
}
I am calling this function initially as moveHorizontal(0).
so the log is as expected the first time:
x:0
x:1
x:2
.
.
x:99
when I click "replay" I get same thing as above:
x:0
x:1
x:2
.
.
x:99
which is correct but when I click "replay" the second time
I am getting this:
x:0
x:0
x:1
x:1
.
.
.
x:99
x:99
which triples the translation leading to unexpected effect.
any explanation?

Update
With the new information this may be the cause of the problem (there is not enough code to be 100% sure but it should give a good pointer as to where the problem is):
As the code is asynchronous and seem to share a global/parent variable x, what happens is that when, at some point, the replay is invoked before one earlier has stopped, the x is reset to 0 causing both (or n number of loops) to continue.
To prevent this you either need to isolate the x variable for each run, or using a simpler approach blocking the loop from running if it's already running using x from a previous invocation. You can use a flag to do this:
var x = 0,
isBusy = false;
function animation(x) {
if (isBusy) return;
if (x < 100) {
isBusy = true;
// ...
setTimeout(...); // etc.
}
else {
isBusy = false;
}
}
optionally, store the timeout ID from setTimeout (reqID = setTimeout(...)) and use clearTimeout(reqID) when you invoke a new loop.
Old answer
The only way to reset the context state entirely is to:
Set a new dimension
Replace it with a new canvas (which technically isn't resetting)
The latter should be unnecessary though.
According to the standard:
When the user agent is to set bitmap dimensions to width and height,
it must run the following steps:
Reset the rendering context to its default state.
...
In the example code in your post you are using a mix of jQuery and vanilla JavaScript. When working with canvas (or video element) I always recommend using vanilla JavaScript only.
Since you are not showing more of the code it is not possible for us to pinpoint to any specific error. But as neither setting an identity matrix nor setting a size for canvas seem to work, it indicates that the problem is in a different part of the code. Perhaps you have persistent offsets involved for your objects that are not reset.
Try to eliminate any possible errors coming from using jQuery to set width as well:
canvas.width = window.innerWidth; // and the same for height
Two other things I notice is that you set the size of canvas inside a function that appears to be part of a loop - if this is the case it should be avoided. The browser will have to, in essence, build a new canvas every time this happens. Second, this line:
canvas.addEventListener("click", replay, false);
can simply be replaced with (based on the code as shown):
canvas.addEventListener("click", animate, false);
You will need to post more code to get a deeper going answer though. But as a conclusion in regards to context: setting a new size will reset it. The problem is likely somewhere else in the code.

Related

How to add width property to the canvas in flex 3

I need to add the width property to the canvas .By traceing the value iam geting its value as zero though it contain images and has a width.
//code of adding 5 canvases to the main canvas
public function addcanvas(canarr:Array):void
{
var can_arr:Array=new Array();
can_arr=canarr;
for(var c:int=0;c<can_arr.length;c++)
{
canvasA=new Canvas();
canvasA.id="canvasA";
trace("ccc:"+c);
canvasA.x=(c*10)+10;
canvasA.y=0;2
this.addChild(canvasA);
}
trace("canvasA.width: "+canvasA.width);
}
and next i will add images into each canvas
//enter code here
public function ClubCards(imgarr:Array):void
{
var Gimg_arr:Array = new Array();
Gimg_arr =imgarr;
for(var j:int=0;j < Gimg_arr.length;j++)
{
myimage1 = new Image();
myimage1.x=0+(j*20);
myimage1.y=40;
myimage1.source=gameModule.getStyle(Gimg_arr[j].toString());
GroupingImageListeners(myimage1);
canvasA.addChild(myimage1);
trace("canvasA.width: "+canvasA.width );
}
iam unable to know the width of the canvas which has images in it . can u please help me out.how can i get that canvas width
Thank you in advance
I can't add a comment, so I will add an answer saying this code should be triggered by a call to the updateDisplayList method (during the appropriate phase of the flex component life-cycle). I would suggest reading and understanding this update cycle before doing any further Flex development (it will save you a lot of time in the long-run).
More info can be found here: http://livedocs.adobe.com/flex/3/html/help.html?content=ascomponents_advanced_2.html
UPDATE
Since you mentioned Canvas, I updated the link to point to the 3.x version of the article
You did not make your canvas invalidate its size. Without an order to redraw, I don't think you'll get the correct size. Without Event.RENDER dispatched your component will not calculate its size.
Maybe you should try:
canvas.invalidateSize();
before accessing its width and see the result. This should make your canvas call its method measure() and calculate its size.

Save un-scaled canvas with image background after changes were applied

I've got a big issue and it's almost a week trying to make it work so any help I would really appreciate - I am trying to create a simple image editor in html5, so I upload an image, load it into canvas and then paint on it -
I also want to be able to zoom in and zoom out- just that I can't figure out how should I save the canvas state - for the paint mouseevents I am using an array which saves canvas.toDataUrl, but this one will save only what it is visible in canvas, only a part of the scaled image, and not the entire one -
if anyone knows how can I un-scale the canvas together with the painting over it and save it in the stack from where I can retrieve it for other painting events, I'll appreciate a lot! Thanks
Saving state
The canvas' save() and restore() is not related to the pixels in the canvas at all. Save() only saves current pen color, fill color, transform, scale, rotation and so forth - parameter values only, not actual pixel data.
And so, the restore() will only restore these parameter values to the previous ones.
The canvas element is passive, meaning it only holds the pixels that you see on the screen. It does not keep a backup of anything so if you change its size, re-size browser window or open dialogs in the browser causing it to clear, you will need to update the canvas yourself.
This also applies when you change a parameter value such as scale. Nothing on the canvas will change setting a new value. The only thing that happens is that your next draw of what-ever will use these parameter values for the drawing (in other words: if you apply rotation nothing rotates, but the next thing you draw will be rotated).
Drawing on existing image
As you need to maintain the content it also means you need to store the image you draw on as well as what you draw.
When you draw for example lines you need to record every stroke to arrays. When the canvas needs an update (ie. zoom) you redraw the original image first at the new scale, then iterate through the arrays with lines and re-render them too.
Same for points, rectangles, circles and what have you..
Think of canvas as just a snapshot of what you have stored elsewhere (image object, arrays, objects) . Canvas is just a view-port for that data.
I would recommend to store as this:
var backgroundImage; //reference to your uploaded image
var renderStack = []; //stores all drawing objects (see below)
//example generic object to hold strokes, shapes etc.
function renderObject() {
this.type = 'stroke'; //or rectangle, or circle, or dot, ...
this.x1;
this.y1;
this.x2;
this.y2;
this.radius;
this.penWidth;
this.penColor;
this.fillColor;
this.points = [];
//... extend as you need or use separate object for each type
}
When you then draw a stroke (pseudo):
var currentRenderObject;
function mouseDown(e) {
//get a new render object for new shape/line etc.
currentRenderObject = new renderObject();
//get type from your selected tool
currentRenderObject.type = 'stroke'; //for example
//do the normal draw operations, mouse position etc.
x =..., y = ...
}
function mouseMove(e) {
//get mouse positions, draw as normal
x = ..., y = ...
//store the points to the array, here:
//we have a line or stroke, so we push the
//values to ourpoint-array in the renderObject
currentRenderObject.points.push(x);
currentRenderObject.points.push(y);
}
function mouseUp(e) {
//when paint is done, push the current renderObject
//to our render stack
renderStack.push(currentRenderObject);
}
Now you can make a redraw function:
function redraw() {
clearCanvas();
drawBackgroundImage();
for(var i = 0, ro; ro = renderStack[i]; i++) {
switch(ro.type) {
case 'stroke':
//... parse through point list
break;
case 'rectangle':
//... draw rectangle
break;
...
}
}
}
function zoom(factor) {
//set new zoom, position (scale/translate if you don't
//want to do it manually). Remember that mouse coords need
//to be recalculated as well to match the zoom factor.
redraw();
}
//when canvas is scaled for some reason, or the window
canvas.onresize = windows.onresize = redraw;
A bonus doing this here is you can use your render stack as a undo/redo stack as well...
Hope this helped to better understand how canvas works.

Kinetic.js don't lose grip on mouse out

When you drag an object and mouse is out of rendering area, dragging stops (firing an event) and user loses a grip.
It's extremelly inconvenient, taking into account that all other technologies (Flash, raw HTML5 Canvas, etc) allows to save the grip even if mouse is out.
Is there a way to solve the problem?
UPDATE: Up to the moment solved the problem by changing library file and binding listeners to the document, not to the container. I know that it's bad to hack into library files, but after inspecting the library's source code I haven't found out way around.
you could check if the element is out of sight and if so bring it back:
shape.on('dragend', function() {
var pos = shape.getPosition();
var layer = pos.getLayer();
if (pos.y < 0) {
pos.y = 0;
}
var maxY = layer.getHeight() - shape.getHeight();
if (pos.y > maxY) {
pos.y = maxY
}
shape.setPosition(pos);
}
Look at element.setCapture(). You can call it from within an event handler for a mouse event, eg. mousedown:
function mouseDown(e) {
e.target.setCapture();
e.target.addEventListener("mousemove", mouseMoved, false);
}
Although browser support is a bit spotty (IE and Firefox support it, not sure about other browsers), for cross browser use you would have to fall back to the binding on the document approach you've already hit upon.

Canvas Animation Not Rendering

I'm new to the canvas tag and am playing around with some animation. Basically, I'm trying to setup a "ground" section composed of multiple images (similar to an 8bit side scroller game like Mario Brothers). The ground will be composed of multiple images, which I've built a constructor function to load these and tile them across the bottom.
function Ground(context,ImageName,ImgX,ImgY,ImgW,ImgH){
this.width=ImgW;
this.height=ImgH;
this.x=ImgX;
this.y=ImgY;
img=new Image();
img.onload=function(){context.drawImage(img,ImgX,ImgY,ImgW,ImgH);};
img.src='images/'+ImageName;};
This seems to work out just fine. I've then setup the rest of the animation, including a basic setup for Key Left/Right events, like so:
window.onload=function(){
var canvas=document.getElementById('canvas'),
context=canvas.getContext('2d'),
Grounds=[],
ImgX=-150; // INITIAL STARTING X FOR FIRST GROUND TILE
// INSERT GROUND ELEMENTS
for(var i=0,l=8; i<l; i++){
var ImgX+=150;
Grounds[i]=new Ground(context,"ground.png",ImgX,650,150,150);
};
// ASSIGN LEFT/RIGHT KEYS
window.addEventListener('keyup',function(event){
switch(event.keyCode){
case 37:
for(var i=0,l=Grounds.length; i<l; i++){
Grounds[i].x+=10;
};
break;
case 39:break;
};
});
// ANIMATION LOOP
(function drawFrame(){
window.mozRequestAnimationFrame(drawFrame,canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
}());
};
I know exactly what my problem is, but don't know how to solve it. The animation loop is clearing the canvas every frame, but not redrawing the updated position (if any) when the user presses the left arrow key.
I'm missing the redraw part here and I'm not exactly sure how to handle this or if I'm approaching this entirely wrong. Any help is very appreciated! Thanks!
First of all you're incrementing the property x of the ground tiles but that property is not even used anywhere in your code. Modify your code so that the onload event of those image objects draws the image according to their own x property so changes to it will actually affect what is drawn. Also add the image object as a property of the Ground object so you can access it later on from outside.
Your approach is really not so good but if you want to do it without going back to 0 do it as follows:
function Ground(context,ImageName,ImgX,ImgY,ImgW,ImgH){
this.width=ImgW;
this.height=ImgH;
this.x=ImgX;
this.y=ImgY;
var self = this; // Add local reference to this Ground instance
this.img=new Image(); // img is now a property too
this.img.onload=function(){context.drawImage(this, self.x, self.y,self.width,self.height);};
this.img.src='images/'+ImageName;};
Ok so now you can change the property x of the ground tiles and call the draw function of it again (which is the onload event).
Grounds[i].x+=10;
Grounds[i].img.dispatchEvent(new Event("load"));
Please note that you should really make the updates of all the values first and then all the draw calls separately.
Can you not just add a draw method? You usually so something like this:
init -> update -> clear, redraw -> update -> clear, redraw -> ...
// ANIMATION LOOP
(function drawFrame(){
window.mozRequestAnimationFrame(drawFrame,canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
contect.drawImage(...);
}());

Can't get a working progress bar for file download AS3

I'm currently enrolled in an ActionScript course, and cannot seem to get my progress bar to work correctly. We were tasked with creating a simple image loader application, which would access externally hosted images (through use of a PHP proxy script), and then spit the image onto the user's screen (with a progress event and progress bar to track the image's download progress).
However, when I run it, I get these mysterious black graphical glitches everywhere, as well as a non-functioning progress bar. In some browsers I get the following error: "ArgumentError: Error #2109: Frame label NaN not found in scene NaN. at flash.display.MovieClip/gotoAndStop() at Document/imageLoadProgress().
Here is my code - I cant for the life of me figure out what is wrong. I figure it has something to do with the imageLoadProgress() function, or my progress bar (which is just a simple 100-frame movie clip with a motion tween and a "stop()" command at the beginning).
public function loadImage()
{
var imageURL:String = imageArray[currentImageIndex];
if(loaderInfo.url.substr(0,4) == "http")
{
trace("we're online");
//use the proxy script
//escape() will replace spaces in file names with %20
imageURL = "./image_proxy.php?filename=" + escape(imageArray[currentImageIndex]);
}
var imageRequest:URLRequest = new URLRequest(imageURL);
this.imageLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, this.imageIOError);
this.imageLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, this.imageLoadProgress);
this.imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, this.imageLoadComplete);
this.imageLoader.load(imageRequest);
//end loadImage
}
public function imageIOError(e:Event)
{
trace("ImageIOError called. Error=" + e);
//end imageIOError
}
public function imageLoadProgress(e:ProgressEvent)
{
var percentLoaded:Number = e.bytesLoaded/e.bytesTotal;
percentLoaded = Math.round(percentLoaded * 100);
//this.progress_mc.scaleY = percentLoaded;
if (percentLoaded < 1)
{
this.progressBar.gotoAndStop(1);
}
else
{
this.progressBar.gotoAndStop(percentLoaded);
}
//end imageLoadProgress
}
Your loader script seems absolutely fine to me, except for a tiny flaw:
The only way the NaN(== not a Number) in your error message makes sense to me is if your ProgressEvent's bytesTotal property equals 0 - because division by zero doesn't produce valid results, as we all know.
I can't say for sure why this happens, not knowing any of your server-side code, but I assume it would occur if your PHP proxy script does not set the Content-Length HTTP response header correctly. If you were loading an actual image file, the web server would automatically set the header for you - in the case of your proxy script, it seems you have to take care of it yourself.
Anyway, you can make your loader script more error-proof by testing for NaN, and making sure the frame you jump to is never greater than 100:
var frame:int = isNaN (percentLoaded) || percentLoaded < 1 ? 1 :
percentLoaded > 100 ? 100 : percentLoaded;
progressBar.gotoAndStop(frame);
(I chose shorthand notation, but this is essentially the same as writing two if statements and an else).