In the last post, we talked about how IE 9 powers Windows 7 devices, how Silverlight/XNA powers Windows Phone 7 devices, and how Windows Azure cloud connects those devices. This post focuses on the device side. We create a graphic rich web application using HTML 5 canvas, and browse it in IE 9.

You can see the application lively on http://smalldemos.cloudapp.net/Html5Demos/CanvasDemo.htm. It is similar to the Direct2D demo I wrote for 1Code. The source code for this project can also be downloaded from 1Code using this link: http://1code.codeplex.com/releases/view/51868#DownloadId=152535.

Please note this application has nothing to do with Windows Azure (except for the fact the application is distributed from Windows Azure). It just demonstrates how, as a developer, can create a graphic rich web application that runs in a browser within just a few hours. The next post will demonstrate how to connect client applications to cloud services.

Choose among canvas, SVG, and Silverlight

There're several ways to create graphic rich web applications. Below is a summary for 3 most common APIs:

Canvas is supported in IE 9 and most other modern browsers. But it is not supported in IE 8 and earlier. Canvas provides an immediate graphics API that is similar to Direct2D and GDI. Choose canvas if you only need to support latest browsers, and if you're familiar with immediate graphics APIs.

SVG is supported in IE 9 and most other modern browsers. It is not supported in earlier browsers, but can be worked around by installing plug-ins. SVG provides a retained graphics API that is similar to Silverlight and WPF. Choose SVG if you prefer retained graphics model.

Silverlight is a small browser plug-in that supports most browsers, and has already been installed on more than half computers in this world. Silverlight is not a standard. Choose Silverlight if you're more confortable with .NET than JavaScript, and if you want to adopt a better designer/developer workflow. In most cases, you don't need to program for graphics in Silverlight. You simply need to design graphics using Expression Design and Expression Blend. Silverlight also supports hardware acceleration (no matter which browser it is using). And it uses CLR's JIT feature to increase the performance to a further step.

This post covers canvas, while the next post covers SVG and Silverlight for Windows Phone.

Familiar programming model

Some developers think canvas (and the whole HTML 5) is a new technology, and takes a long time to learn. Some of them may even fear of the new technologies. But keep in mind what canvas brings you is simply the fact that you can render graphics in a browser without a plug-in. It doesn't create any new ideas. So actually it is very easy to learn, as long as you have worked with another similar graphics API.

I had no knowledge of canvas prior to today, but I learned and created the sample within 3 hours, because I can leverage my existing Driect2D knowledge.

As you will find out later, the programming model of canvas is very similar to Direct2D (or GDI if you have to mention it). It is always much easier to learn a new technology based on familiar concepts than learn a new concept.

Immediate graphic API

Canvas provides an immediate drawing graphics API that is similar to Direct2D and GDI. That is, what you draw on the screen is the final bitmap result. You no longer have access to a shape (like a rectangle) once it is drawn. The API doesn't provide a retain tree that stores shapes information.

Immediate graphic APIs are usually much more difficult to use than retained APIs, especially if you want to add interactivity. They also don't support declarative drawing (think of SVG's xml and Silverlight's XAML). But they consume fewer memory since they don't remember the shapes' information. So complex games are usually created using an immediate graphics API, while simpler scenarios are usually created using a retained graphics API.

Canvas is considered as a high level API because under the hook, the JavaScript code is interpreted by the browser's script engine to internal data understood by the browser's rendering engine. The script engine then pass those internal data to the rendering engine, and the rendering engine invokes a native graphic API to render the scene. For example, IE 9's rendering engine invokes Direct2D under the hook.

But because IE 9's scripting engine is considerably fast, the overhead of translating script to internal data and then to Direct2D instructions can usually be ignored.

On the other hand, working with graphics in JavaScript is much simpler than Direct2D. Even if you still have to think on every steps in the drawing process (because it's immediate API), you don't need to worry about handling COM errors, creating Direct2D devices and render targets, and lots of other tasks.

The canvas element

To use canvas in HTML, you define a canvas element:

    <canvas id="mainCanvas"></canvas>

That's all you can do to declaratively define drawing elements on a canvas. The remaining tasks will have to be done in JavaScript code.

The following sections walks through how to create the demo app. Since this is a Windows Azure blog, we assume you have already worked with another similar API, and understand the basic concepts like brush and transform. If not, it is highly recommended to read an introduction post like http://msdn.microsoft.com/en-us/scriptjunkie/ff961912.aspx. The Direct2D documentation on MSDN also helps you to understand the concepts. Once again, keep in mind the concepts are the same. No matter which technology you're using, you only need to learn the concepts for once.

Initialization

Before drawing on a canvas, you must obtain its drawing context. Currently only 2D is supported by canvas. In addition, you have to define a size for the canvas. Canvas doesn't support CSS. Instead, you should use the width/height properties. The properties don't support percentage size like 100%. So if you want to make the canvas resizable, you must handle the window's resize event.

            mainCanvas = $('#mainCanvas');

            drawingContext = mainCanvas[0].getContext('2d');
            // Canvas does not support CSS. So we have to manually set the width/height properties.

            mainCanvas[0].width = $(window).width();

            mainCanvas[0].height = $(window).height();

            // Make the canvas fit the browser window.

            $(window).resize(function ()

            {

                mainCanvas[0].width = $(window).width();

                mainCanvas[0].height = $(window).height();

                render();

            });

            render();
 

Render the scene

Render a simple rectangle

Now in the render function, you can draw anything you like. For example, to draw a simple rectangle:

            // Draw a black background.

            drawingContext.fillStyle = 'black';

            drawingContext.fillRect(0, 0, canvasWidth, canvasHeight);

As you can see, when using a solid color, you don't need to create a solid color brush, as you do in Direct2D. You can simply use the fillStyle property of the drawingContext.

Render circles

The next step in our demo app is to draw some small stars. Each star is a simple white circle. There's no method in canvas API to draw an ellipse. Instead, an ellipse is considered as a path, and you should draw an arc path:

        function drawSmallStars()

        {

            drawingContext.fillStyle = 'white';

            for (var i = 0; i < 300; i++)

            {

                var x = Math.random() * canvasWidth;

                var y = Math.random() * canvasHeight;

                drawingContext.beginPath();

                drawingContext.arc(x, y, 1, 0, Math.PI * 2, true);

                drawingContext.closePath();

                drawingContext.fill();

            }

        }

Render complex paths

You can draw other paths using similar code, such as a Bezier curve:

drawingContext.bezierCurveTo(

            56.9435916193468, 9.34704511572823,

            53.1105762246886, 6.5137471975463,

            52.4435735457851, 16.0137402172507

            );

Next we draw the planet. The planet is composed of a circle and two complex continent paths. When drawing complex paths, it is often easier to draw them in a graphic tool first. We use Expression Design to draw the paths, and exported them to XAML. If you're working with Silverlight or WPF, you can take the XAML directly, without any manual code. But since we're working on JavaScript, we have to do some coding.

To simplify the task, let's create a PowerShell script, to convert the XAML content to JavaScript code. For example:

$invocation = (Get-Variable MyInvocation -Scope 0).Value
$currentDir = Split-Path $invocation.InvocationName
cd $currentDir
$xmlData = [xml](Get-Content PlanetUpPathXaml.xml)
$segments = $xmlData.PathFigure.SelectNodes("BezierSegment")
$segments | ForEach-Object { "drawingContext.bezierCurveTo(
{0},
{1},
{2}
);
" -f $_.GetAttribute("Point1"), $_.GetAttribute("Point2"), $_.GetAttribute("Point3") } > PlanetUpPathOutput.txt

The PowerShell script reads the source XAML (xml) file, use XPath to select all BezierSegments, and for each BezierSegment, output a corresponding JavaScript code.

This is one disadvantage of HTML canvas compared to Silverlight. But maybe in the future, we will see more mature tools that output JavaScript code from a graphic designer. So we will be able to keep the designer/developer workflow.

Working with clipping paths

The continent paths need to be clipped to fit in the planet circle. To create a clip path, simply call the clip method on the drawing context. The last path created by the drawing context will be used as the clipping path. The clipping path will be used by all future drawings. If you don't like this behavior, you can wrap the drawing tasks that rely on clipping in a save/restore pair:

            // Clip the paths to fit in the circle.

            drawingContext.save();

            drawingContext.clip();

            drawPlanetUpPath();

            drawPlanetDownPath();

            drawingContext.restore();

Using gradient brushes

We also need to draw the star (the sun if you like). The star is composed of a circle and a complex outline. The code is similar to the planet. We also use PowerShell to convert XAML design to JavaScript code. There's one difference: The star's outline is a radial gradient brush. The concept of radial gradient brush in HTML canvas is similar to that in Direct2D. But the API parameters are different.

In Direct2D (and most other graphic APIs), you define a start point which acts as the center of the circle. You define an end point which is a point on the circumference. For example:

// Create a RadialGradientBrush (star outline).

HRESULT Renderer::CreateStarOutlineBrush()

{

HRESULT hr = S_OK;

ID2D1GradientStopCollection* gradientStopCollection;

D2D1_GRADIENT_STOP gradientStops[2];

gradientStops[0].color = D2D1::ColorF(0xFF7A00);

gradientStops[0].position = 0.72093f;

gradientStops[1].color = D2D1::ColorF(0xEBFF00, 0.5f);

gradientStops[1].position = 1.0f;

hr = this->m_pRenderTarget->CreateGradientStopCollection(gradientStops, 2, &gradientStopCollection);

hr = m_pRenderTarget->CreateRadialGradientBrush(

D2D1::RadialGradientBrushProperties(D2D1::Point2F(95, 95), D2D1::Point2F(0, 0), 95, 95),

gradientStopCollection,

&this->m_pStartOutlineBrush

);

return hr;

}

In HTML canvas, however, you define an inner circle and an outer circle. So the code will be:

            var outlineBrush = drawingContext.createRadialGradient(95, 95, 0, 95, 95, 95);

            outlineBrush.addColorStop(0.72093, '#FF7A00');

            outlineBrush.addColorStop(1, 'rgba(235, 255, 0, 136)');

            drawingContext.fillStyle = outlineBrush;

They're essentially the same. Just two different approaches to the same problem.

You apply a gradient brush using the drawing context's fillStyle property. The brush will be used for any future drawings, unless you put the code in a save/restore pair.

Creating animations and transforms

JavaScript doesn't provide storyboards you find in Windows Animation Manager and Silverlight. You have to use timers. The sample uses a timer whose interval is 1 millisecond. That is, 1000 frames per second. You don't need to worry if the machine is not powerful enough to support such a high frame rate. The browser will drop to an appropriate frame automatically. This feature also shows the capability of different browsers. For example, on my machine (quad-core CPU, NVidia 8800 GPU), IE 9 renders the animation very fast, while another browser that doesn't support hardware acceleration renders much slower.

            setInterval(function ()

            {

                render();

            }, 1);

Like working with other immediate graphics APIs, you have to deal with animation directly when rendering. There's no property based animations like in Silverlight. So when drawing the planet, we apply a translate transform to the drawing context, whose translationX changes as time passes away:

            drawingContext.translate(10 + animateTranslateX, canvasHeight / 2 - 100);

Once again, transforms are applied to all future drawing, unless you wrap the code in a save/restore pair.

You can also use matrix transform directly. For example, when drawing the star, we use a matrix transform to apply a scaling and a translating at the same time:

            // Scale the star, and translate it to the center of the screen. We use a matrix transform here.

            drawingContext.transform(2, 0, 0, 2, canvasWidth / 2 - 190, canvasHeight / 2 - 190);

Conclusion

HTML 5 canvas may seem to be a new technology, but the concepts are very similar to existing technologies like Direct2D. So as long as you're familiar with another immediate graphics API, you should be able to pick it up very quickly.

Canvas and other rich internet application technologies like Silverlight allows you to deliver great user experiences in the browser. It takes much more effort to create the application in HTML than in Silverlight. But if the user is using a modern browser, (s)he doesn't need to install any plug-in. And if the user is using the latest browser such as IE 9, the hardware acceleration feature makes HTML application's performance almost the same as Silverlight.

You can think the sample we built in this post as one scene in a game. You can build upon it. For example, download some data from the cloud, connect the user to Facebook, and so on. Just imagine the possibilities. The next post (and the final part of the series) will transfer your attention from client applications to cloud services.