Showing posts with label HTML. Show all posts
Showing posts with label HTML. Show all posts

February 11, 2014

HTML5 Canvas: Offscreen Rendering

Offscreen Rendering

Introduction

This post is part of a series about HTML5 Canvas. In this article I talk about "offscreen rendering" and an useful application for this technique: The Spritebuffer. The two important methods for offscreen rendering are context.getImageData() and context.putImageData().

What means offscreen rendering?

As the name says, it is about rendering content somewhere, but the screen. That "somewhere" means the memory. So, we are able to render graphics to the memory. Another term often used for this technique is "pre-rendering". In general, this technique is used to improve the visual experience (by reactivity and runtime performance), as rendering to memory is much faster than rendering to screen; especially when using context.drawImage() as shown at JsPerf.

How can I apply offscreen rendering?

A visual rendering applies for canvas elements which are embedded in the DOM only. Canvas elements that are not linked into the DOM can be used for offscreen rendering, as its content won't be visualized onto the screen. Therefore, to realize offscreen painting, simply create a canvas element dynamically without embedding it into the DOM. The following code demonstrates this simple technique.

<html>
    <head>
    <script type="application/javascript">        
        function main(){            
            // here we create an OFFSCREEN canvas
            var offscreenCanvas = document.createElement('canvas');
            offscreenCanvas.width = 300px;
            offscreenCanvas.height = 300px;
            var context = offscreenCanvas.getContext('2d');
            
            // draw something into the OFFSCREEN context
            context.fillRect(10,10,290,290);
            // ...
            
        }
    </script>
    </head>
    <body onload='main()'>
    </body>
</html>

Ok, I painted something into an offscreen canvas. And now?

Now, it gets interesting. We have painted something into an offscreen canvas and we want to use its content. The canvas API provides two functions to copy and paste image data from one canvas into another. While context.getImageData() fetches a rectangular area from a canvas, context.putImageData() pastes image data into a context. So, it is quite straightforward to copy image data from the offscreen canvas into the visual 'onscreen' canvas.

<html>
    <head>
    <script type="application/javascript">        
        function createOffscreenCanvas(){
            // here we create an OFFSCREEN canvas
            var offscreenCanvas = document.createElement('canvas');
            offscreenCanvas.width = 300px;
            offscreenCanvas.height = 300px;
            var context = offscreenCanvas.getContext('2d');
            
            // draw something into the OFFSCREEN context
            context.fillRect(10,10,280,280);
            // ...
        }    
            
            
        function copyIntoOnscreenCanvas(offscreenCanvas){
            var onscreenContext = document.getElementById('onscreen').getContext('2d');
            var offscreenContext = offscreenCanvas.getContext('2d');
            
            // cut the drawn rectangle
            var image = offscreenCanvas.getImageData(10,10,280,280); 
            // copy into visual canvas at different position
            onscreenContext.putImageData(image, 0, 0);
            
        }
            
        function main(){                        
            copyIntoOnscreenCanvas(createOffscreenCanvas());        
        }
    </script>
    </head>
    <body onload='main()'>
        <!-- this is the visual canvas -->
        <canvas id='onscreen' style='width:300px; height:300px'></canvas>
    </body>
</html>

Applying transformations on image data

Unfortunately, the image data pasted with putImageData() cannot be transformed with rotate(), scale(), translate(), setTransform(). The methods getImageData() and putImageData() are raw pixel operations for which no context state apply. To make the image data state aware, it is necessary to draw it into another (offscreen) canvas. This new canvas can be rendered with drawImageData(). Yes, you read correctly: drawImageData() also accepts a canvas object as argument.
The following code illustrates the copying technique.
function main(){
    loadImage("image.png");   
}

function loadImage(imgFile, onload){
    var img = new Image();
    img.onload = function() { exec(img); };
    img.src = imgFile;
}

function exec(imgData){
    
    // create offscreen image
    var imageCanvas = createOffscreenCanvas(imgData.width,imgData.height);    
    var imageContext = imageCanvas.getContext('2d');
    imageContext.drawImage(imgData, 0,0);
    
    // copy a part from image to subimage
    var w = 32; var h = 32;
    var subImageData = imageContext.getImageData(16,16,w,h);    
    var subImage = createOffscreenCanvas(w,h);
    var subImageContext = subImage.getContext('2d');
    subImageContext.putImageData(subImageData,0,0);
    
    paintScene(subImage);
    
}

function createOffscreenCanvas(width,height){
    var canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    return canvas;
}


function paintScene(subImage){
    var onscreenContext =  document.getElementById('onscreen').getContext('2d');    
    
    // it is possible to use subImage as stamp
    onscreenContext.drawImage(subImage,32,64);
    onscreenContext.drawImage(subImage,96,64);
    
    // apply transformation 
    onscreenContext.save();
    onscreenContext.translate(80,80);
    onscreenContext.rotate(45 * (Math.PI/180));
    onscreenContext.translate(-80,-80);
    onscreenContext.drawImage(subImage,64,64);
    onscreenContext.restore();    
}
The rendered and visualized result may look like this.

Use case: Spritebuffer

A common use case for offscreen rendering is a spritebuffer. A sprite is a (movable) graphic object that is drawn over another graphics, e.g. background. Sprites are usually bundled in one or more images. The specific sprite is copied from that bundle to the canvas. As we want our sprite to be transformable we need to make our graphic object become a canvas first. The following code shows the implementation of a spritebuffer and its usage. The 'class' is ready to use and you may feel free to copy and reuse it.
var DEG2RAD = Math.PI/180.0;

function drawScene(imgSprite){    
    var spriteBuffer = new SpriteBuffer(imgSprite);    
    var context = document.getElementById('myCanvas').getContext('2d');
    
    var sprite = spriteBuffer.getSprite(0,0,32,32);
    context.rotate(45 * DEG2RAD);
    context.translate(100,100);    
    context.drawImage(sprite, 0, 0);
};

function loadImage(imageUrl, onload){
    var image = new Image();
    image.onload = function(){
        onload(image);        
    };
    this.image.src = imageUrl;
};

function main(){
    loadImage('./img/sprites.png', drawScene);
};
function SpriteBuffer(spriteImage) {        
        
    var imageBuffer;
    
    var initImageBuffer = function(spriteImage){
        var canvas = document.createElement('canvas');
        canvas.width = spriteImage.width;
        canvas.height = spriteImage.height;
        imageBuffer = canvas.getContext('2d');            
        imageBuffer.drawImage(spriteImage, 0, 0);
    };
    
    this.getSprite = function(x,y,w,h){
        var imgData = imageBuffer.getImageData(x,y,w,h);
        var sprite = document.createElement("canvas");
        sprite.width=w;
        sprite.height=h;            
        sprite.getContext('2d').putImageData(imgData, 0,0);
        return sprite;    
    };
    
    initImageBuffer(spriteImage);
};

December 19, 2013

HTML5 Canvas: State Stacking

Stacking states with Canvas

Introduction

This is the first post of a series about HTML5 Canvas. In this article I will explain the meaning of context.save() and context.restore(). Simply said, these methods are reponsible for stacking the contexts states. But what does it mean?

What are states in Canvas?

First, it is necessary to know, what states are. The simplified answer is: Everything that does not draw!
The Canvas API provides a set of methods which can be distinguished between drawing methods and auxiliary methods. A good part (not all) of these auxiliary methods are used to define the appearance of drawn areas, and/or paths. For example, strokeStyle, fillStyle, globalAlpha, lineWidth, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, font, textAlign, textBaseline etc. are all methods which modify states. Also considered as a state is the transformation matrix, which is modified by the methods translate, rotate, scale, setTransform. Another kind of state is a defined clipping region, modified by clip; Everything modified by these methods are states, which can be stacked.

What can I do with stacking?

Obviously, it is easy to recover already set states by simply popping it from the stack, because sometimes it is quite cumbersome to define a proper state. This also keeps your code cleaner. Stacking can even improve runtime performance, as demonstrated here at JsPerf. Another important advantages is the "isolatation" of state dependent operations. In the next paragraph I'll explain this concept more precisely.

Isolation of state dependent operations

Using state stacking you can isolate some and group other state operations quite easily. Imagine a car whose wheels shall rotate, while the car is moving forward. You can isolate the rotation of the wheels during their painting by stacking the "translation matrix" and apply the rotation. Afterwards, you restore the "translation matrix" and paint the next frame. The following snippet demonstrate this principle by transforming texts. Here is the visual result.
And here comes the code.
function main(){
    var context = document.getElementById('myCanvas').getContext('2d');
    
    var painter = new Painter(context);
    
    painter.setFillColor(255,0,0,1);
    painter.drawText("Text 1", 50);    
    painter.pushState();    
    painter.rotate(320, 100, 45);
    painter.setFillColor(0,0,255,1);
    painter.drawText("Text 2", 100); 
    painter.popState();
    painter.drawText("Text 3", 150);                
}
function Painter(ctx){
    var context = ctx;
    var DEG2RAD = Math.PI/180.0;
    var center = {};
        
    var init = function(ctx){
        context = ctx;
        center[0] = context.canvas.width/2;
        center[1] = context.canvas.height/2;
    };
            
    this.pushState = function(){
        context.save();
    };
    
    this.popState = function(){
        context.restore();
    };
    
    this.rotate = function(posX, posY, angle){
        context.translate(posX, posY);
        context.rotate(angle * DEG2RAD);
        context.translate(-posX, -posY);
    };
    
    this.setFillColor = function(r,g,b,a){
        context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a +")";
    };
    
    this.drawText = function(text, ypos){        
        context.save();
        context.font = "30px Arial";
        context.textAlign = "center";
        context.fillText(text, center[0], ypos);
        context.restore();              
    };
        
    init(ctx);
}

September 19, 2013

Close the tags, but close them fucking correctly.

Or: Do not mix HTML with X(HT)ML

Yesterday, our team struggled over a broken layout on IE8, while it all works well on FF, IE9+, Chrome, and Safari.
Initially, we thought we could fix it easily adapting the CSS, e.g. replacing display:inline-block by floating elements. This did not work. So, we checked the HTML structure for some missing close tags...everything looked correct. We started to be dispaired, as our ideas faded, and our knowledge came up to an end. Luckily, I saw a small warning showing up in the developer console in Browser Mode IE10 (not IE8), which seemed quite strange to me, as I did not find any problem within the referenced code.

The complete message (in portuguese) is the following: HTML1523: Marca de fim com sobreposição. As marcas devem ser estruturadas como "<b><i></i></b>" em vez de "<b><i></b></i>". It complains allegedly overlapping tags within the following HTML:

<div class="login">
    <asp:Label runat="server" ID="lnkLogin">Login<span class="seta-baixo"/></asp:Label>
    <asp:LinkButton runat="server" ID="lnkLogout">Logout</asp:LinkButton>
</div>

Do you see any problem?

Syntactically, everything seems fine, or not? Well, there is a small detail. I remembered that once I had problems with inline closed script tags. That is, when a script tag is closed in XHTML/XML manner, the script is not loaded correctly and cannot be used. That is because syntax of two similar but not identical languages are mixed up. Just to remember:

HTML is not XHTML(nor XML)!

You will run into problems, when mixing up XML-Syntax in HTML. Some browsers can deal with it, but others not. The solution was really simple. We closed the span tag the correct HTML way. You could say "Use the correct doctype definition to avoid the problem", and you would be right. But it is more bulletproof to use the HTML style, as it is also valid for XML!
<div class="login">
    <asp:Label runat="server" ID="lnkLogin">Login<span class="seta-baixo"></span></asp:Label>
    <asp:LinkButton runat="server" ID="lnkLogout">Logout</asp:LinkButton>
</div>

With the correction the layout works on all browsers as expected, even on IE8.
So, before you get nuts, and spent valuable time, remember to correctly close tags in HTML style.

September 04, 2013

requestAnimationFrame is what you need for browser-based animations

Are you still using setInterval for animating things on your website?

Forget about it! This is what I discovered yesterday. I started to make a simple parallax scrolling and used setInterval initially. I was not much convinced with the result, because I encountered some flicker and minor slowdowns. So, I searched for hints for optimizing my experience and discovered there is some relatively new technique out there called requestAnimationFrame. Here is what I've done intially
function ParallaxScroller() {
    var that = this;    
    var layers = [];
            
    this.addLayer = function(layer){
        layers.push(layer);    
    }
    
    
    this.start(timeout){
        setInterval(render, timeout);
    }
    
    this.render=function(){        
        for(var i=0; i<layers.length; ++i){
            layers[i].update();
        }        
    }
};

var scroller = new ParallaxScroller();
scroller.addLayer( /* layer */ );
scroller.start(50);
And here is the same code using requestAnimationFrame
function ParallaxScroller() {
    var that = this;    
    var layers = [];
            
    this.addLayer = function(layer){
        layers.push(layer);    
    }
        
    this.render=function(){        
        for(var i=0; i<layers.length; ++i){
            layers[i].update();
        }                
        requestAnimationFrame(that.render); // # HERE!
    }
};

var scroller = new ParallaxScroller();
scroller.addLayer( /* layer */ );
scroller.render();
The thoughtful reader may have noticed, that there is no interval passed to requestAnimationFrame. To understand what is happening here, it is vital to know about the essential difference between requestAnimationFrame and setInterval:

Using setInterval for animations forces the browser to refresh the screen when the related callback is triggered. That means, screen updates occur at any time, and have a good chance to be unnecessary. As a result the animations can be crude. Using rFA instead overcomes those "update interruptions". As the name says it requests an update frame. So, the browser becomes responsible to decide whether a callback is triggered, or not. Yes, he might decide not to trigger, for example if the updating page is not visible. The browser can synchronize the callback with its own refreshing rate, which is usually 60Hz, to avoid unnecessary (and CPU costly) repaints. Consequently, you get smoother results due to synchronized refreshes and can safe CPU resource (and battery life), especially while the screen is not visible.

That's why I simply do not care about frame rates when animate my stuff on the screen using rFA. The animations are updated in the correct time slot nearly all 16 ms. There are other articles out in the wild which explain more precisely how it works, like "Better performance with requestAnimationFrame", CreativeJS requestAnimationFrame , and "Better JavaScript animations with requestAnimationFrame". Also interesting is "Microsoft's rFA Testdrive", that compares both methods visually.

August 29, 2013

Using Prettify with Blogger.com

I tried to configure Blogger using Prettify.js and struggled a bit as it did not work as I expected.
I looked at one of my favorite knowledge sources and saw that using syntax highlighting in Blogger.com should be quite simple.

So, I started to follow the advices given at Stackoverflow.com and added/modified the following lines in the HTML of Devbutze.

<head>
    <link href="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.css" rel="stylesheet" type="text/css"/>
    <script src="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js" type="text/javascript"></script>
...
</head>
<body onload="prettyPrint()">
...
</body>

It is worth notice - as mentioned also at Stackoverflow - that after correct configuration the to-be-highlighted code won't be colored while previewing. But even when I published my very, very first test post there was still that poor black and white code. WTF?

After some trials I discovered that the necessary correct execution of prettyPrint() failed for some reason at least in Chrome. With FF I had no problems and it worked like expected.
The solution, without talking too much, is very simple. Initially, I used the 'Dynamic' Template for the blog. When I changed to 'Simple' Template it worked like a charm. Well, that is not really a solution, but as I don't like the Dynamic Template that much, it serves as a solution at least for me.