Showing posts with label canvas animation. Show all posts
Showing posts with label canvas animation. Show all posts

Monday, November 4, 2013

Canvas, Bitmaps, and requestAnimationFrame (Game Programming)

After two recent posts showing how to use canvas arc drawing to create a ball and bounce it (here and here) I'm ready to talk about the more popular technique of copying bitmaps to the canvas. I hope you're not getting sick of bouncing balls!


 
Using Canvas shapes is somewhat difficult because there aren't yet powerful editors that can let you create Canvas shapes and paths. There are some intermediate plugins that will converts an Illustrator drawing to Canvas, but I'm not sure how good they are.

But there are a zillion editors that can create a bitmap that can be copied to a canvas. Instead of drawing a ball with something like the arc method, I'll copy a bitmap drawing of a ball to the canvas and then bounce it around.

By some coincidence, I happen to have a bitmap drawing of a ball left over from my post on using CSS to bounce a ball. In case you've forgotten, here is the bitmap of the ball:

Impressive! This is a 20x20pixel bitmap of a ball with transparent bits around it. You're really looking at a square, but you see only the round part.

Here's the code:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      Canvas Bounce BitMap w/requestAnimationFrame
    </title>
 
    <script>

    // Global variables
    var canvas;
    var ctx;
    var myImage = new Image();
    var boardWidth = 320;
    var boardHeight = 460;
      var ballHor = boardWidth / 2;
    var ballVer = boardHeight /2;
    var changeHor = 10;
    var changeVer = 10;
   
      // Covers all bases for various browser support.
      var requestAnimationFrame =
      window.requestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.msRequestAnimationFrame; 

    // Event listener
    window.addEventListener("load", getLoaded, false);

    // Run this when the page loads.
    function getLoaded(){

      // Make sure we are loaded.
      console.log("Page loaded!");
 
      // Load the image and the canvas.
      loadMyImage();

      // Start the main loop.
      doMainLoop();
    }

    // Runs to the beat of requestAnimationFrame.
    function doMainLoop(){

      // Loop the loop for the canvas.
      requestAnimationFrame(doMainLoop);
 
      // Clear the canvas.
      ctx.clearRect(0,0,320,460);
 
      ballMove(); 
    }

    // Calculate new ball position.
    function ballMove() {
     
      // Changes are calculated but do not
      // take effect until next time through loop.
         
      // Calculate new vertical component.
      ballVer = ballVer + changeVer;

      // If top is hit, change direction.
      if (ballVer + changeVer < -10)
        changeVer = -changeVer;
       
      // If bottom is hit, reverse direction.
      if (ballVer + changeVer > boardHeight - 10)
        changeVer = -changeVer;

      // Calculate new horizontal component.
      ballHor = ballHor + changeHor;

      // If left edge hit, reverse direction.
      if (ballHor + changeHor < -10)
        changeHor = -changeHor;
       
      // If right edge is hit, do something.
      if (ballHor + changeHor > boardWidth - 10)
        changeHor = -changeHor;
       
      // Draw the ball with new coordinates.
      ctx.drawImage(myImage, 0, 0, 20, 20,
          ballHor, ballVer, 20, 20);             
    }

    // Load the image and the canvas.
    function loadMyImage() {

      // Get the canvas element.
      canvas = document.getElementById("myCanvas");

      // Make sure you got it.
      if (canvas.getContext) {

        // Specify 2d canvas type.
        ctx = canvas.getContext("2d");

        // When the image is loaded, draw it.
        myImage.onload = function() {

          // Did the image get loaded?
          console.log("The image was loaded.");
        }

        // Define the source of the image.
        myImage.src = "ball.png";
      }
    }

</script>

</head>
<body>

  <canvas id="myCanvas" width="320" height="460">
  </canvas>

</body>
</html>


Most of the code is the same as CSS and Canvas code I've already written about. Here are the main differences:
  1. Using requestAnimationFrame instead of setInterval.
  2. Using drawImage instead of arc.
  3. Using the HTML DOM Image object.
requestAnimationFrame

You may be asking yourself, why use this when you have good old setInterval? Well, the short answer is that instead of you setting the timing of the animation with setInterval, you can let the browser decide. This is especially important because the browser can make sure that the animation is making the most efficient use of the battery, and Firefox OS phones use batteries (so does everything else, practically).

This is a more recent addition to web programming, and is mostly adopted. While the details were being figured out in the W3C committee, browser manufactures started to adopt requestAnimationFrame, but they would prefix it. So Firefox had moxRequestAnimationFrame instead of requestAnimationFrame.  Robert Helmer pointed out t me that I should use requestAnimationFrame and Panagiotis Astithas explained to me why Firefox OS 1.0 and 1.1 don't work with requestAnimationFrame:
Firefox 18, which only supported the moz-prefixed version. Firefox OS 1.2 is based off Firefox 26 and will support the unprefixed version.
And here is how to handle this in your code. First, create a new definition for requestAnimationFrame (I'm basing this on the Mozilla documentation for requestAnimationFrame).

var requestAnimationFrame =
      window.requestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.msRequestAnimationFrame; 
This covers webkit, Mozilla, and old IE browsers before IE9.

Next, instead of using setInterval, put this line in your game loop:

     requestAnimationFrame(doMainLoop);

This will call the main loop as quickly as the browser decides is correct. Typically this will be 60 frames per second, but it may vary. The point is that it will do the best thing for your animation and your battery life.

drawImage

This is a handy method on the canvas context (ctx). It might seem complicated (with 9 parameters), but it's really easy. Here's the code:

      ctx.drawImage(myImage, 0, 0, 20, 20,
          ballHor, ballVer, 20, 20);


I'll talk about myImage in a minute, but it hold the image you want to copy to the canvas. After the image, the next four numbers define the portion of the bitmap you want to copy from. The first two numbers are 0,0 and they represent the upper left-hand corner of the bitmap. The next two are 20,20 and they represent the lower right-hand corner of the image. One cool feature of this method is that you can take part of a bitmap and copy it, letting you have all your images in one big bitmap (saving loading time).

Next comes the place on canvas you want to copy the image to. ballHor and ballVer represent the upper left-hand corner of the current position of the ball, and 20,20 are lower right-hand corner. Again, you don't need to have the save size or ratio when you copy, allowing cool effects.

The drawing takes place every time through the loop, but in this example, the canvas is erased before each drawing. You could also erase where the ball was by copying a blank bitmap, and you don't need to worry about aliasing (which was a problem in the second canvas arc example if you didn't erase the whole canvas, but just the part you wanted to remove), because the edges are straight.

Using the HTML DOM Image object

I find it convenient to do all my image loading at the same time, and I like to make sure that both my image and my canvas context loaded properly, so you'll see that in the code for loadMyImage. There may be other ways to do this, but I find it works best when I define an HTML DOM image object and copy the bitmap into that.

Here's how you do it:

    var myImage = new Image();

As you may know, one of the important concept in web programming is the DOM (Document Object Model). That defines a set of objects that can be modified by JavaScript. Image is an object that you can use. One of the things that is fun about web programming is the different way things are named. When you create an <img> tag, you are creating an Image object. And to write effective HTML5 games, you need to understand HTML5, JavaScript, CSS, and the DOM.

Sometimes the DOM is documented in ways that are a little confusing. For example, MDN has a very useful page on using the Image object, but it is called the HTMLImageElement. The difference between elements and objects are fun. They are really the same thing, but they change their names depending on what country (HTML or DOM) they are in. HTMLImageElement is the Image object is the <img> tag. Note that I could have created a new empty image with

    document.createElement('img');

Sorting out when to use an element and when to use an object is definitely mind-boggling, but when you start using JavaScript in really cool ways, objects will be your best friends forever.

So there's a 4-step process for images:
  1. Define a blank object with the new Image() method.
  2. Make sure that blank image was created (using myImage.onload).
  3. Define the source of the image (myImage.src).
  4. Use the image in drawImage.
Follow these steps and your canvas images will always work. The reason you need to check is that sometimes bits of the browser load before other bits, so you always want to make sure:
  1. The page is loaded.
  2. The canvas context is loaded.
  3. The image container is loaded.
Whew!

So I've covered a few ways to use Canvas to draw a bouncing ball. There are advanced techniques that I'll be covering later, such as flipping two canvas back and forth, and using different canvases for layers. But this post covers the bread and butter of mangling canvases.

And don't forget to use requestAnimationFrame. You'll want to use the code that permits use of requestAnimationFrame or mozRequestAnimationFrame because early Firefox OS phones may not be upgraded to Firefox OS 1.2. Mine isn't, and won't be until I hear widespread reports that it is safe to upgrade my ZTE Open phone. I'll probably buy a different Firefox OS phone before then. That LG model looks nice, but maybe Asus will make a phone soon!

So far I am liking CSS bouncing balls more than Canvas bouncing balls. Canvas requires cleanup and CSS doesn't. Now it's possible that Canvas has better hardware acceleration on the Firefox OS phone, but no one has told me that, and all browsers have gotten pretty good at making CSS efficient.

But why stop with CSS and Canvas for creating games? There's a third, mysterious graphics technology called SVG (Scalable Vector Graphics) that is really cool and well-supported in Firefox and all other browsers. Next time I write about game programming, I'll explain the mystery and how to make SVG fly. Not only that, but I'll show you three different ways (in three posts) to use SVG and introduce you to an even more shadowy figure, the SVG DOM!

But first some game reviews and then on to ... touch! And lots more. There's so much to write about, and Firefox OS is the place to be for games and game programming.

Friday, November 1, 2013

Bouncing a Ball with Canvas Arc - Revisited (Game Programming)

In a recent post (Bouncing a Ball with Canvas Arc) I wrote about ... duh ... bouncing a ball using Canvas and drawing the ball with the arc method.

First of all, here are better links (thanks, Robert Helmer) for the Canvas arc method:
  1. Reference: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D#arc%28 (note that this hangs off of CanvasRenderingContext2D, not Canvas).
  2. Guide: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Drawing_shapes#Arcs (note this is part of a larger topic on drawing shapes with Canvas).
  3. From now on, I'll try to reference Mozilla Developer Network. I'm guilty of quick look ups on W3 Schools, but MDN really is the authority on Firefox!
Secondly, the post on Canvas and the arc method assumed that it is okay to erase the screen every time you move the ball. This works with some things well and the phone screen is small enough that there isn't a lot of delay. But I then thought, what about these two conditions:
  1. You have other stuff on the screen you don't erase?
  2. What if you have to do a lot of calculations to redraw something that doesn't need to be done?
 So I remembered a technique I learned from programming 8-bit games (Apple II, Commodore 64, etc.). There it was simply:
  1. Draw something.
  2. Calculate the move.
  3. Erase the thing you want to move.
  4. Draw the thing in its new position.
 Easy to do, right? Draw the ball in Fuchsia, calculate new position, erase the ball by drawing it with White, and then draw it again in its new position with Fuchsia. What could be simpler? (Well, it seemed simple at the time.)

So here's the code:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      Canvas Bounce Draw Circle and Erase
    </title>

    <script>
   
    // Global variables
    var canvas;
    var ctx;

    var boardWidth = 320;
    var boardHeight = 460;

    var ballHor = boardWidth / 2;
    var ballVer = boardHeight /2;
   
    var oldHor;
    var oldVer;

    var changeHor = 10;
    var changeVer = 10;

    // Listen to that page load!
    window.addEventListener("load", getLoaded, false);

    // Run this when the page loads.
    function getLoaded(){

      // Make sure we are loaded.
      console.log("Page loaded!");
 
      // Load the image and the canvas.
      loadMyImage();

      // Start the game loop.     
      gameLoop = setInterval(doMainLoop,16);
    }

    // This will run every 16 milliseconds.
    function doMainLoop(){
 
      // Move the ball.
      ballMove();
    }

    function ballMove() {
     
      // Changes are calculated but do not
      // take effect until next time through loop.

      // Get the old ball coordinates.    
      oldHor = ballHor;  
      oldVer = ballVer;

      // Erase the ball where it is now.
      // Color is White.
      ctx.lineWidth = 0;
      ctx.beginPath();
      ctx.arc(oldHor, oldVer, 10, 0, Math.PI * 2, true);
      ctx.closePath(); 
        ctx.fillStyle = "White";
      ctx.fill();
              
      // Calculate new vertical component.
      ballVer = ballVer + changeVer;

      // If top is hit, change direction.
      if (ballVer + changeVer < -10)
        changeVer = -changeVer;
       
      // If bottom is hit, reverse direction.
      if (ballVer + changeVer > boardHeight - 10)
        changeVer = -changeVer;

      // Calculate new horizontal component.
      ballHor = ballHor + changeHor;

      // If left edge hit, reverse direction.
      if (ballHor + changeHor < -10)
        changeHor = -changeHor;
       
      // If right edge is hit, do something.
      if (ballHor + changeHor > boardWidth - 10)
        changeHor = -changeHor;     
   
      // Draw a ball using canvas arc function.
      // Color is Fuchsia.
      // Make the radius one pixel smaller.
      ctx.beginPath();
      ctx.arc(ballHor, ballVer, 10, 0, Math.PI * 2, true);
      ctx.closePath();
      ctx.fillStyle = "Fuchsia";
      ctx.fill();    
    }

    // Load the image and the canvas.
    function loadMyImage() {

      // Get the canvas element.
      canvas = document.getElementById("myCanvas");

      // Make sure you got it.
      if (canvas.getContext) {

        // Specify 2d canvas type.
        ctx = canvas.getContext("2d");
      }
    }

</script>

</head>
<body bgcolor="White">

  <canvas id="myCanvas" width="320" height="460">
  </canvas>

</body>
</html>


This is nearly the same code as last time, with these few small changes:
  1. I added temporary variables to track the previous positions of the ball.
  2. In the ballMove section, I added erasing the ball.
  3. The main loop (doMainLoop) no longer clears the screen every time.
Erasing the Ball

This code erases the ball:

      // Get the old ball coordinates.    
      oldHor = ballHor;  
      oldVer = ballVer;

      // Erase the ball where it is now.
      // Color is White.
      ctx.lineWidth = 0;
      ctx.beginPath();
      ctx.arc(oldHor, oldVer, 10, 0, Math.PI * 2, true);
      ctx.closePath(); 
      ctx.fillStyle = "White";
      ctx.fill();


It is nearly the same as drawing the ball, but the fillStyle is White. The old ball position is created and used to erase where the ball was drawn.

But Wait! Something is wrong!

When I ran this code, it looked like this in the Simulator:


And it looked like this when I pushed it to my ZTE Open phone:

 

I'm hoping that the picture is clear enough to show that the bouncing ball is leaving a trail of ghost shapes behind it. Well, it was Halloween, but I'm not sure that would account for the behavior today.

The ball bounces all right, but its not cleaning up after itself. I did some research and it turns out that when you draw on Canvas, the pixels get scattered a little too far and you get artifacts that can cause problems. This is called aliasing, and is often a problem with drawing shapes onto a screen. And this is why many people recommend clearing the canvas every time you move an object.

There are two solutions I have found:
  1. You can erase part of a screen by using context.clearRect ( x , y , w , h ); You can do this to clear a specific rectangle at x,y with a width and height.
  2. You can overlap the drawing a little to get rid of the spilled pixels.
I like the second because it is more precise. I want to erase just the part I want. So the solution is simple. When you draw the ball, use 9 instead of 10 in this line:

      ctx.arc(ballHor, ballVer, 10, 0, Math.PI * 2, true);

so it reads:

      ctx.arc(ballHor, ballVer, 9, 0, Math.PI * 2, true);

The ball is slightly smaller, but all of it gets erased. I'm not happy with this, but I looked and saw that this isn't a bug, it is a feature of not only Firefox, but Chrome and IE. Throwing pixels around is a bit imprecise, and that's one of the reasons I'm not 100% happy with Canvas. But it works with lots of stuff. I'm still more happy CSS. See my Bouncing a Ball in CSS topic. I'll come back to CSS later, but there's still a few more things to look at with Canvas.

Programming Tip

When you install an app to the ZTE Open, there is a delay of a few seconds before you get a message telling you the app is installed. And you'll have to reboot the ZTE Open before you can see your app's icon.

Also, screenshots are still tricky. You have to hit two buttons at nearly the same time. But I'm learning how to do this. I was sending the screenshots to myself from the gallery with my email, but I figured out I could use the cable to download the screenshots from a folder named screenshots. There's a short delay before the screenshot appears in the gallery. However, if you want to take a screenshot, you must unplug the cable first. A little annoying, but liveable. This is a version 1 of an new product that never lived before, so the baby steps can be amusing and appealing.

Wednesday, October 30, 2013

Bouncing a Ball with Canvas Arc (Game Programming)

Recently I created a simple programming showing how to bounce a ball around a Firefox OS phone screen using nothing but HTML5, JavaScript, and CSS (check it out).

CSS is very cool, but Canvas has its place also. There are a few different ways to work with Canvas, and I'll show you one here. The game looks the same, so I grabbed a graphic from Wikipedia when they weren't looking.



I'll be covering bouncing balls in Canvas (with a few variations) as well as SVG (with greater variations).

So basically instead of pushing around a bitmap, we'll create a canvas in HTML5 and then draw on it using the Canvas Arc method. This is only a few more lines than the CSS version, and runs just as fast. And, again, this runs in all browsers.

Here is the code, in glorious HTML5:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      Canvas Bounce Draw Circle
    </title>

    <script>
   
    // Global variables
    var canvas;
    var ctx;

    var boardWidth = 320;
    var boardHeight = 460;

    var ballHor = boardWidth / 2;
    var ballVer = boardHeight /2;

    var changeHor = 10;
    var changeVer = 10;

    // Listen to that page load!
    window.addEventListener("load", getLoaded, false);

    // Run this when the page loads.
    function getLoaded(){

      // Make sure we are loaded.
      console.log("Page loaded!");
 
      // Load the image and the canvas.
      loadMyImage();

      // Start the game loop.     
      gameLoop = setInterval(doMainLoop,16);
    }

    // This will run every 16 milliseconds.
    function doMainLoop(){
 
      // Clear the canvas.
      ctx.clearRect(0,0,boardWidth,boardHeight);
 
      ballMove(); 
    }

    function ballMove() {
     
      // Changes are calculated but do not
      // take effect until next time through loop.
         
      // Calculate new vertical component.
      ballVer = ballVer + changeVer;

      // If top is hit, change direction.
      if (ballVer + changeVer < -10)
        changeVer = -changeVer;
       
      // If bottom is hit, reverse direction.
      if (ballVer + changeVer > boardHeight - 10)
        changeVer = -changeVer;

      // Calculate new horizontal component.
      ballHor = ballHor + changeHor;

      // If left edge hit, reverse direction.
      if (ballHor + changeHor < -10)
        changeHor = -changeHor;
       
      // If right edge is hit, do something.
      if (ballHor + changeHor > boardWidth - 10)
        changeHor = -changeHor;
       
      // Draw a ball using canvas arc function.
      ctx.beginPath();
      ctx.arc(ballHor, ballVer, 10, 0, Math.PI * 2, true);
      ctx.closePath();
      ctx.fillStyle = "Fuchsia";
      ctx.fill();             
    }

    // Load the image and the canvas.
    function loadMyImage() {

      // Get the canvas element.
      canvas = document.getElementById("myCanvas");

      // Make sure you got it.
      if (canvas.getContext) {

        // Specify 2d canvas type.
        ctx = canvas.getContext("2d");
      }
    }

</script>

</head>
<body>

  <canvas id="myCanvas" width="320" height="460">
  </canvas>

</body>
</html>


There are lots of books written about Canvas and you can always get started at the W3 Schools: http://www.w3schools.com/html/html5_canvas.asp

Essentially, this code can be divided into  parts:

  1. HTML5 shell - standard, except Firefox OS likes to know about UTF-8.
  2. JavaScript that defines some global variables and defines a page load event.
  3. An initial function that loads the canvas and starts the game loop.
  4. A game loop function that erases the screen and draws the ball.
  5. A function that calculates what's next and moves the ball.
  6. A function that loads the canvas.
  7. A canvas element in the body that defines the canvas size.
This has been tested with my trusty ZTE Open Firefox OS phone. I like the phone for all its quirks, and it is a good environment to test on because it represents the minimum that a Firefox OS phone offers. I haven't even tried to upgrade the OS, but everything I'm doing is very standard and shouldn't cause any problems. After all, it's only game programming!

Here's how it works:

Shell and Globals and Page Load

The shell is just your standard HTML5 shell. Nothing special to see here, but be sure to give it a title and specify UTF-8 (unless you are working with other kinds of UTF).

The canvas and its context (ctx) are defined as global so they can be accessed anywhere. Sometimes globals are good. The size of the screen (board), position of the ball, and the amount of change are defined.

An event handler is created that will detect when the page is loaded. I always do this because I want to make sure everything is loaded before I start calling JavaScript. In the CSS ball bounce, I used the onload function in the body but event listeners are cooler.

Initial Function

You call two functions here (and I added a console output in case you want to make doubly sure the page loaded).

First the function is called that defines the canvas. You do this once.

Next you call the game loop, which will run every 16 milliseconds. You can mess with this number, but it seems to work with the way that browsers are programmed. Or maybe it's just a memory of that time when you were sweet 16?

Game Loop

This just does two things:
  1. Clear the screen.
  2. Call a function to draw the ball.
Canvas needs to have the screen cleared. Once you draw on the canvas, it stays that way. Think of it as fire and forget. Except that with games, you can't forget, so you need to keep track of what you're doing, but the canvas won't help you. The canvas forgets! There are some other techniques you can use to take care of this, but we'll talk about that later.

Anyway, the loop just goes around and around, each time clearing the screen and drawing the ball in a new place. For fun, don't clear the screen and watch it fill up with ball images.
 
Drawing the Ball

This is the good part. Canvas has some drawing commands, like line, square, and arc. They don't have a circle command, but you can draw a circle with the arc method (arcs are just parts of a circle, so make it go around all wthe way). You can find out more about arc here. Of course, there's a W3C canvas specification, but it's written for browser manufacturers, not web monkeys like you and me.

So drawing the ball has some similarities to the CSS bouncing ball. Calculate the move, and draw the circle. The difference here is that instead of blasting with a bitmap, you draw a circle.

      ctx.beginPath();
      ctx.arc(ballHor, ballVer, 10, 0, Math.PI * 2, true);
      ctx.closePath();
      ctx.fillStyle = "Fuchsia";
      ctx.fill();    


This is a pretty standard canvas procedure. All your methods are attached to the canvas context (ctx, defined in the function below). Follow this for painting with a method:
  1. Arc is really a way to define a path. So you need to begin the path first.
  2. The arc method needs the ball x and y position, radius, and three other standard variables.
  3. Then you close the path. Otherwise the horse will get out of the barn!
  4. Then define your color with the fillStyle method (isn't Fuschia fun to spell?).
  5. Finally, use the fill method to make it happen.
Canvas likes fiddly procedures, but once you learn them, you can just fill in the blanks.

Loading the Canvas

Getting a canvas to work is not that hard, it just takes a few steps:
  1. Define the canvas in the body.
  2. Load the canvas into the web page.
  3. Get the context of the canvas.
  4. Make sure you get the context!
In this function, you first find the canvas element that was created in the body with the canvas tags. The canvas tag needed to have a height, width, and an id (so it can be found).

Next, do an if statement to make sure you get the canvas. In that statement, use the canvas.getContext method and assign it a name (ctx is popular). Specify that you want a "2d" canvas. 3D is coming but I'm not sure how well it is supported and I'm only looking at 2D games right now.

You want to make sure you got the context. It's like page load. You'll appreciate it the first time you make a typo or forget to define the canvas tag in the body.

Canvas Element in the Body

Just put the canvas element in the body. Give it a size and id. The position of the canvas is determined by the flow of the page, but wherever it is, once you have its context, you can blast away on it. Think of canvas as its own little world. You can draw on it and every HTML5 browser supports it.

That's it for now. This seems to run as fast as the CSS ball bouncing, but I haven't done any precise tests yet. It runs fast enough to convince me I can do an action game with Canvas or CSS.

So far, the advantage goes to CSS, because I don't care about what's on the screen and I don't erase anything. In CSS I just move the ball, but in Canvas I have to clean up after the ball moves. Well, maybe that's like taking care of a cute little puppy.

Next time: what if you have other stuff on the Canvas screen and don't want to erase everything?