You are previewing HTML5 Canvas.

HTML5 Canvas

Cover of HTML5 Canvas by Steve Fulton... Published by O'Reilly Media, Inc.
  1. HTML5 Canvas
    1. SPECIAL OFFER: Upgrade this ebook with O’Reilly
    2. A Note Regarding Supplemental Files
    3. Preface
      1. Running the Examples in the Book
      2. What You Need to Know
      3. How This Book Is Organized
      4. Conventions Used in This Book
      5. Using Code Examples
      6. We’d Like to Hear from You
      7. Safari® Books Online
      8. Acknowledgments
    4. 1. Introduction to HTML5 Canvas
      1. The Basic HTML Page
      2. Basic HTML We Will Use in This Book
      3. The Document Object Model (DOM) and Canvas
      4. JavaScript and Canvas
      5. HTML5 Canvas “Hello World!”
      6. Debugging with Console.log
      7. The 2D Context and the Current State
      8. The HTML5 Canvas Object
      9. Another Example: Guess The Letter
      10. What’s Next
    5. 2. Drawing on the Canvas
      1. The Basic File Setup for This Chapter
      2. The Basic Rectangle Shape
      3. The Canvas State
      4. Using Paths to Create Lines
      5. Advanced Path Methods
      6. Compositing on the Canvas
      7. Simple Canvas Transformations
      8. Filling Objects with Colors and Gradients
      9. Filling Shapes with Patterns
      10. Creating Shadows on Canvas Shapes
      11. What’s Next
    6. 3. The HTML5 Canvas Text API
      1. Displaying Basic Text
      2. Setting the Text Font
      3. Text and the Canvas Context
      4. Text with Gradients and Patterns
      5. Width, Height, Scale, and toDataURL() Revisited
      6. Final Version of Text Arranger
      7. What’s Next
    7. 4. Images on the Canvas
      1. The Basic File Setup for This Chapter
      2. Image Basics
      3. Simple Cell-Based Sprite Animation
      4. Advanced Cell-Based Animation
      5. Applying Rotation Transformations to an Image
      6. Creating a Grid of Tiles
      7. Zooming and Panning an Image
      8. Pixel Manipulation
      9. Copying from One Canvas to Another
      10. What’s Next
    8. 5. Math, Physics, and Animation
      1. Moving in a Straight Line
      2. Bouncing Off Walls
      3. Curve and Circular Movement
      4. Simple Gravity, Elasticity, and Friction
      5. Easing
      6. What’s Next?
    9. 6. Mixing HTML5 Video and Canvas
      1. HTML5 Video Support
      2. Converting Video Formats
      3. Basic HTML5 Video Implementation
      4. Preloading Video in JavaScript
      5. Video and the Canvas
      6. Video on the Canvas Examples
      7. Animation Revisited: Moving Videos
      8. What’s Next?
    10. 7. Working with Audio
      1. The Basic <audio> Tag
      2. Audio Formats
      3. Audio Tag Properties, Functions, and Events
      4. Playing a Sound with No Audio Tag
      5. Creating a Canvas Audio Player
      6. Case Study in Audio: Space Raiders Game
      7. What’s Next
    11. 8. Canvas Game Essentials
      1. Why Games in HTML5?
      2. Our Basic Game HTML5 File
      3. Our Game’s Design
      4. Game Graphics: Drawing with Paths
      5. Animating on the Canvas
      6. Applying Transformations to Game Graphics
      7. Game Graphic Transformations
      8. Game Object Physics and Animation
      9. A Basic Game Framework
      10. Putting It All Together
      11. The player Object
      12. Geo Blaster Game Algorithms
      13. The Geo Blaster Basic Full Source
      14. Rock Object Prototype
      15. What’s Next
    12. 9. Combining Bitmaps and Sound
      1. Geo Blaster Extended
      2. Creating a Dynamic Tile Sheet at Runtime
      3. A Simple Tile-Based Game
      4. What’s Next
    13. 10. Mobilizing Games with PhoneGap
      1. Going Mobile!
      2. Creating the iOS Application with PhoneGap
      3. Beyond the Canvas
      4. What’s Next
    14. 11. Further Explorations
      1. 3D with WebGL
      2. Multiplayer Applications with ElectroServer 5
      3. Conclusion
    15. Index
    16. About the Authors
    17. Colophon
    18. SPECIAL OFFER: Upgrade this ebook with O’Reilly
O'Reilly logo

Pixel Manipulation

In this section, we will first examine the Canvas Pixel Manipulation API, and then build a simple application demonstrating how to manipulate pixels on the canvas in real time.

The Canvas Pixel Manipulation API

The Canvas Pixel Manipulation API gives us the ability to “get,” “put,” and “change” individual pixels utilizing what is known as the CanvasPixelArray interface. ImageData is the base object type for this manipulation, and an instance of this object is created with the createImageData() function call. Let’s start there.

The createImageData() function sets aside a portion of memory to store individual pixels’ worth of data based on the following three constructors:

imagedata = context.createImageData(sw, sh)

The sw and sh parameters represent the width and height values for the ImageData object. For example, imagedata=createImageData(100,100) would create a 100×100 area of memory in which to store pixel data.

imagedata = context.createImageData(imagedata)

The imagedata parameter represents a separate instance of ImageData. This constructor creates a new ImageData object with the same width and height as the parameter ImageData.

imagedata = context.createImageData()

This constructor returns a blank ImageData instance.

ImageData attributes

An ImageData object contains three attributes:

ImageData.height

This returns the height in pixels of the ImageData instance.

ImageData.width

This returns the width in pixels of the ImageData instance.

ImageData.data

This returns a single dimensional array of pixels representing the image data. Image data is stored with 32-bit color information for each pixel, meaning that every fourth number in this data array starts a new pixel. The four elements in the array represent the red, green, blue, and alpha transparency values of a single pixel.

Getting image data

To retrieve a set of pixel data from the canvas and put it into an ImageData instance, we use the getImageData() function call:

imagedata = context.getImageData(sx, sy, sw, sh)

sx, sy, sw, and sh define the location and size of the source rectangle to copy from the canvas to the ImageData instance.

Note

A security error will be thrown if the origin domain of an image file is not the same as the origin domain of the web page. This affects local files (when running on your hard drive rather than on a web server running locally or on a remote server), as most browsers will treat local image files as though they are from a different domain than the web page. When running on a web server, this error will not be thrown with local files. The current version of Safari (5.02) does not throw this error for local files.

Putting image data

To copy the pixels from an ImageData instance to the canvas, we use the putImageData() function call. There are two different constructors for this call:

context.putImageData (imagedata, dx, dy)
context.putImageData (imagedata, dx, dy [, dirtyX, dirtyY,
                       dirtyWidth, dirtyHeight ])

The first constructor simply paints the entire ImageData instance to the destinationX (dx) and destinationY (dy) locations. The second constructor does the same, but allows the passage of a “dirty rectangle,” which represents the area of the ImageData to paint to the canvas.

Application Tile Stamper

We are going to create a simple application that will allow the user to highlight a box around some pixels on an image, copy them, and then use them as a stamp to paint back to the canvas. It will not be a full-blown editing application by any means—it’s just a demonstration of one use of the ImageData object.

Note

This application will need to be run from a local or remote web server, as most browsers will throw an exception if an application attempts to call getImageData() on a file—even in the same folder on a local machine. The current version of Safari (5.02) does not throw this error.

To create this simple application, we will use the tile sheet from earlier in this chapter. The user will click on a spot on the tile sheet, highlighting a 32×32 square tile. That tile can then be painted onto the bottom section of the canvas. To demonstrate pixel manipulation, we will set the color of the pixels to a new alpha value before they are painted to the screen. This will be the humble beginning to making our own tile map editor.

Once again, we will use the tanks_sheet.png file from Figure 4-7.

How ImageData.data is organized

The ImageData.data attribute is a single-dimensional array containing four bytes for every pixel in the ImageData object. We will be using 32×32 tiles in our example application. A 32×32 tile contains 1,024 pixels (or 1K of data). The ImageData.data attribute for an ImageData instance that holds a 32×32 image would be 4,096 bytes (or 4K). This is because a separate byte is used to store each of the red, green, blue, and alpha values for each pixel. In our application, we will loop through each pixel and set its alpha value to 128. Here is the code we will use:

for (j=3; j< imageData.data.length; j+=4){
   imageData.data[j] = 128;
}

We start our loop at 3, which is the fourth attribute in the array. The single-dimensional array contains a continuous set of values for each pixel, so index 3 represents the alpha value for the first pixel (because the array is 0 relative). Our loop then skips to every fourth value in the array and sets it to 128. Once the loop is complete, all pixels will have an alpha value of 128.

Note

As opposed to other Canvas alpha manipulations where the alpha value is between 0 and 1, the alpha value is between 0 and 255 when manipulating it via the pixel color values.

A visual look at our basic application

Figure 4-13 is a screenshot of the simple Tile Stamper application we will create.

Note

Figure 4-13 is running in Safari 5.1 locally. As of this writing, this is the only browser that does not throw an exception when trying to manipulate the pixel data of a locally loaded file when not run on a web server.

The Tile Stamper application

Figure 4-13. The Tile Stamper application

The screen is broken up into two sections vertically. The top section is the 256×128 tile sheet; the bottom is a tile map of the same size. The user will select a tile in the top section, and it will be highlighted by a red square. The user can then stamp the selected tile to the tile map drawing area in the lower portion. When a tile is drawn in this lower portion, we will set its alpha value to 128.

Adding mouse events to the canvas

We need to code our application to respond to mouse clicks and to keep track of the current x and y positions of the mouse pointer. We will set up two global application scope variables to store the mouse pointer’s current position:

var mouseX;
var mouseY;

We will also set up two event listener functions and attach them to the theCanvas object:

theCanvas.addEventListener("mousemove", onMouseMove, false);
theCanvas.addEventListener("click", onMouseClick, false);

In the HTML, we will set up a single Canvas object:

<canvas id="canvas" width="256" height="256"  style="position: absolute; 
    top: 50px; left: 50px;">
 Your browser does not support HTML5 Canvas.
</canvas>

In the JavaScript portion of our code, we will define the canvas:

theCanvas = document.getElementById("canvas");

Notice that we set the <canvas> position to top: 50px and left: 50px. This will keep the application from being shoved up into the top-left corner of the browser, but it also gives us a chance to demonstrate how to find correct mouse x and y values when the <canvas> tag is not in the top-left corner of the page. The onMouseMove function will make use of this information to offset the mouseX and mouseY values based on the position of the <canvas> tag:

function onMouseMove(e) {
   mouseX = e.clientX-theCanvas.offsetLeft;
   mouseY = e.clientY-theCanvas.offsetTop;
}

The onMouseClick function will actually do quite a lot in our application. When the mouse button is clicked, this function will determine whether the user clicked on the tile sheet or on the tile map drawing area below it. If the user clicked on the tile sheet, the function will determine which exact tile was clicked. It will then call the highlightTile() function and pass in the id (0–31) of the tile clicked, along with the x and y locations for the top-left corner of the tile.

If the user clicked in the lower portion of the tile map drawing area, this function will again determine which tile the user clicked on, and stamp the current selected tile in that location on the tile map. Here is the function:

function onMouseClick(e) {

   if (mouseY < 128){
      //find tile to highlight
      var col = Math.floor(mouseX / 32);
      var row = Math.floor(mouseY / 32);
      var tileId = (row*7)+(col+row);
      highlightTile(tileId,col*32,row*32)
   }else{
      var col = Math.floor(mouseX / 32);
      var row = Math.floor(mouseY / 32);
      context.putImageData(imageData,col*32,row*32);
      }
   }

Let’s take a closer look at the tile sheet click (mouseY < 128).

To determine the tileId of the tile clicked on the tile sheet, we first need to convert the x location of the mouse click to a number from 0‒7, and the y location to a number from 0‒3. We do this by calling the Math.floor function on the result of the current mouseX or mouseY location, divided by the tile width or height (they are both 32). This will find the row and col of the clicked tile:

var col = Math.floor(mouseX / 32);
var row = Math.floor(mouseY / 32)

To find the tileId (the 0‒31 tile number of the tile sheet) of this row and column combination, we need to use the following calculation:

TileId = (row*totalRows-1) + (col+row);

The actual calculation, with values for our application, looks like this:

var tileId = (row*7)+(col+row);

For example, if the user clicks on the point where mouseX = 50 and mouseY = 15, the calculation would work like this:

col = Math.floor(50/32);    // col = 1
row = Math.floor(15/32);    // row = 0
tileId = (0*7)+(1+0);       // tileId = 1

This position is the second tile on the tile sheet. The onMouseClick() function then passes the tileId and col value multiplied by 32, and the row value multiplied by 32, into the highlightTile() function. This tells the highlightTile() function the exact tileId, row, and col the user clicked.

If the user clicked the tile map drawing area in the lower portion of the screen, the code does the same row and column calculation. However, it then calls the putImageData() function and passes in the ImageData instance that holds the tile to stamp and the top-left location to place the tile:

var col = Math.floor(mouseX / 32);
var row = Math.floor(mouseY / 32);
context.putImageData(imageData,col*32,row*32);

The highlightTile() function

The highlightTile() function accepts three parameters:

  • The 0–31 tileId of the tile on the tile sheet

  • The top-left x coordinate of the tile represented by the tileId

  • The top-left y coordinate of the tile represented by the tileId

Note

The x and y coordinates can be found by passing in the tileId value, but they are needed in the onMouseDown function, so we pass them in from there when calling highlightTile(). This way, we do not need to perform the calculation twice.

The first task highlightTile() tackles is redrawing the tile sheet at the top of the screen:

context.fillStyle = "#aaaaaa";
context.fillRect(0,0,256,128);
drawTileSheet();

It does this to delete the red box around the current tile, while preparing to draw a new red box around the tile represented by the tileId passed in.

The drawTileSheet() function then paints the tanks_sheet.png file to the canvas starting at 0,0:

function drawTileSheet(){
   context.drawImage(tileSheet, 0, 0);
}

Next, the highlightTile() function copies the new pixel data (with no red line around it yet) from the canvas and places it in the ImageData instance:

ImageData = context.getImageData(x,y,32,32);

The ImageData variable now contains a copy of the pixel data for the tile from the canvas. We then loop through the pixels in ImageData.data (as described previously in How ImageData.data is organized), and set the alpha value of each to 128.

Finally, now that the ImageData variable contains the correct pixels with the altered alpha values, we can draw the red line around the tile that’s been selected to stamp on the tile map:

var startX = Math.floor(tileId % 8) *32;
var startY = Math.floor(tileId / 8) *32;
context.strokeStyle = "red";
context.strokeRect(startX,startY,32,32)

Example 4-16 is the entire set of code for this application.

Example 4-16. The Tile Stamper application

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH4EX16: Tile Stamper Application</title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded() {

   canvasApp();

}

function canvasSupport () {
  return Modernizr.canvas;
}

function canvasApp(){

   if (!canvasSupport()) {
          return;
   }else{
      var theCanvas = document.getElementById("canvas");
      var context = theCanvas.getContext("2d");
   }

   var mouseX;
   var mouseY;

   var tileSheet = new Image();
   tileSheet.addEventListener('load', eventSheetLoaded , false);
   tileSheet.src = "tanks_sheet.png";

   var imageData = context.createImageData(32,32);

   function eventSheetLoaded() {
      startUp();
   }

   function startUp(){
      context.fillStyle = "#aaaaaa";
      context.fillRect(0,0,256,256);
      drawTileSheet();
   }

   function drawTileSheet(){
      context.drawImage(tileSheet, 0, 0);

   }

   function highlightTile(tileId,x,y){
      context.fillStyle = "#aaaaaa";
      context.fillRect(0,0,256,128);
      drawTileSheet();

      imageData = context.getImageData(x,y,32,32);
      //loop through imageData.data. Set every 4th value to a new value
      for (j=3; j< imageData.data.length; j+=4){
         imageData.data[j]=128;
      }

      var startX = Math.floor(tileId % 8) *32;
      var startY = Math.floor(tileId / 8) *32;
      context.strokeStyle = "red";
      context.strokeRect(startX,startY,32,32)
   }

   function onMouseMove(e) {
      mouseX = e.clientX-theCanvas.offsetLeft;
      mouseY = e.clientY-theCanvas.offsetTop;

   }

   function onMouseClick(e) {
      console.log("click: " + mouseX + "," + mouseY);
      if (mouseY < 128){
         //find tile to highlight
         var col = Math.floor(mouseX / 32);
         var row = Math.floor(mouseY / 32)
         var tileId = (row*7)+(col+row);
         highlightTile(tileId,col*32,row*32)
      }else{
         var col = Math.floor(mouseX / 32);
         var row = Math.floor(mouseY / 32);

         context.putImageData(imageData,col*32,row*32);


      }
   }

   theCanvas.addEventListener("mousemove", onMouseMove, false);
   theCanvas.addEventListener("click", onMouseClick, false);

}

</script>
</head>
<body>
<div>
<canvas id="canvas" width="256" height="256"  style="position: absolute; 
    top: 50px; left: 50px;">
 Your browser does not support HTML5 Canvas.
</canvas>
</div>
</body>
</html>

Note

As of this writing, you must run this application from a web server in order to manipulate the local tanks_sheet.png file on the canvas. If you are using the Safari browser (version 5.1 as of this writing), you can test the application on a local drive and it will function properly.

The best content for your career. Discover unlimited learning on demand for around $1/day.