Cloud + Device: Combine the power of Windows Azure, IE 9, and Windows Phone 7 (Part 2)

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

https://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: https://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

https://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.