Clipping Images or Displaying Only Parts of an Image
Sometimes you just need to display certain parts of an image. Like for instance, when you want to show some animation, It would be better to place all the animation frames inside a single image file rather than to store them in several files. This will help save some precious phone memory since you only have to load one image file into one Image object so there's no extra overhead from using multiple Image object instances.

Once again we will just be using the project we last modified in the previous tutorial : Loading Images Into Your Game.

I'm also writing this in preparation for the next tutorial which is about graphical menus where clipping plays a major part.


Reintroducing Earth: Animated
In this tutorial we're going to show earth spinning on the phone screen like the image displayed below:

Spinning Earth
Spinning Earth 16 x 16 pixels


Here is the image strip for that animation. Firefox users can right-click and choose "Save Image As" from the context menu while IE users can choose "Save Picture As" instead. Save the image inside the "images" folder of the "src" folder of the project.

Spinning Earth Image Strip
Spinning Earth animation with all frames in a single image.
Dimensions: 144 x 16 pixels
Frame Size: 16 x 16 pixels



Just in case you're wondering, both images were made from using a satellite image of earth which you can see here:


Earth from a Satellite


...which is a bit of an overkill.


Juggling Frames
What we're actually going to do is to draw one frame at a time from the image strip you just downloaded. All the while keeping the frame we want to draw inside the clipping rectangle. An illustration would be best to demonstrate what I'm trying to say:

Clipping Rectangle Illustration


The red rectangle represents the clipping rectangle and everything outside that rectangle will not be visible on the screen. The dimmed portion of the image strip represents the portion that will not be visible on the screen. The frame number being displayed is the current frame that is inside the clipping rectangle and is visible on the screen. We decrement the X of the image strip by 16 pixels to skip to the beginning of each frame going from right to left. Again the most important thing to remember about clipping rectangles is that everything outside it is invisible and everything inside it is visible.

It's now time to start NetBeans and open the project. Upon opening the project you should see the "earthstrip.png" file inside the images folder. Navigate your way to clsCanvas.java so we can start editing some code. In the load() method, change "earth.png" to "earthstrip.png".


// try to load the image file
imgEarth = Image.createImage("/images/earthstrip.png");



Let's add some variables in the run() method under the iKey declaration:


public void run() {
int iKey = 0;

int imgX = 50; // x coordinate of the image
int frameIndex = 0; // current frame to be drawn
long lDelay = 250; //time to pause between frames in milliseconds
long lStart = 0; //time we last changed frames in milliseconds
long lCurrTick = 0; // current system time in milliseconds;



Add this code inside the main loop after we check for key presses and before we call the drawing methods:


if ((iKey & GameCanvas.FIRE_PRESSED) != 0){
isRunning = false;
}


lCurrTick = System.currentTimeMillis();
if ((lCurrTick-lStart) >= lDelay){
lStart = lCurrTick; // save the current time
if (frameIndex < 8) {
frameIndex++; // skip to the next frame
} else {
frameIndex = 0; // go back to first frame
}
imgX = 50 - (frameIndex * 16); // compute x relative to clip rect
}

//restore the clipping rectangle to full screen
g.setClip(0, 0, getWidth(), getHeight());

//set drawing color to black
g.setColor(0x000000);



There's a lot happening there. First, we store the current system time in milliseconds to the lCurrTick variable. We use that to check if the time that has passed since the last time we changed the frame, lStart, is greater than the delay we set, lDelay. If so, then it's time to change frames. And since we are changing frames, we will set lStart to the current time for our delay to work on the next loop.

We have a total of 9 frames in that animation which we index starting at 0. That means the last frame would have an index of 8. So we check if the current frame index which is stored in frameIndex is the last index. If not, then we increment it by 1 frame. If it is the last frame, we then return to the first frame.

After we have figured out if it's time to change frames and what frame to display next, we can now compute the X coordinate of the image strip so the frame we want to show will be drawn inside the clipping rectangle. Here's the formula in pseudo code:


image_X = X_of_clipping_rectangle - (frame_index * frame_width);



setClip(), Not Scissors!!
You might have also noticed that we called a new method: setClip(). The setClip() method of the Graphics class is what let's us define the position and size of the clipping rectangle. The first two parameters defines the position, x and y, and the last two parameters defines the width and height of the clipping rectangle.

Let's draw some more.

Add this code in the main loop just before the call to drawImage():


g.drawString("Frame : " + Integer.toString(frameIndex), 2, 68, Graphics.TOP | Graphics.LEFT);
g.drawString("X : " + Integer.toString(imgX), 2, 88, Graphics.TOP | Graphics.LEFT);

//clip the drawing area to a single frame
g.setClip(50, 50, 16, 16);




...and modify the drawImage() function call like so:


g.drawImage(imgEarth, imgX, 50, Graphics.TOP | Graphics.LEFT);



Notice that we placed two calls to the setClip() method of the Graphics object g. The first call sets the clipping rectangles dimensions to match that of the whole screen area of the phone. The second call sets the clipping rectangle to where the animated globe will be drawn. This is because the dimensions you pass to the setClip() method the last time you call it will be in effect until you call setClip() again with a different set of parameters. This means if you don't reset the clipping rectangle, you won't be able to draw anywhere else on the screen. Anything you draw outside the clipping rectangle will not show up on the phone screen. So it's good practice to call the setClip() method with full screen dimensions at the top of your drawing code.

Your clsCanvas source code should now look like this:



package MyGame;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;

public class clsCanvas extends GameCanvas implements Runnable {
private boolean isRunning = true;
private Graphics g;
private midMain fParent;
private Image imgEarth;

public clsCanvas(midMain m) {
super(true);
fParent = m;
setFullScreenMode(true);
}

public void start(){
Thread runner = new Thread(this);
runner.start();
}

public void load(){
try{
// try to load the image file
imgEarth = Image.createImage("/images/earthstrip.png");
}catch(Exception ex){
// exit the app if it fails to load the image
isRunning = false;
return;
}
}

public void unload(){
// make sure the object get's destroyed
imgEarth = null;
}

public void run() {
int iKey = 0;

int imgX = 50; // x coordinate of the image
int frameIndex = 0; // current frame to be drawn
long lDelay = 250; //time to pause between frames in milliseconds
long lStart = 0; //time we last changed frames in milliseconds
long lCurrTick = 0; // current system time in milliseconds;

load();
g = getGraphics();
while(isRunning){

iKey = getKeyStates();

if ((iKey & GameCanvas.FIRE_PRESSED) != 0){
isRunning = false;
}

lCurrTick = System.currentTimeMillis();
if ((lCurrTick-lStart) >= lDelay){
lStart = lCurrTick; // save the current time
if (frameIndex < 8) {
frameIndex++; // skip to the next frame
} else {
frameIndex = 0; // go back to first frame
}
imgX = 50 - (frameIndex * 16); // compute x relative to clip rect
}

//restore the clipping rectangle to full screen
g.setClip(0, 0, getWidth(), getHeight());

//set drawing color to black
g.setColor(0x000000);
//fill the whole screen
g.fillRect(0, 0, getWidth(), getHeight());
// set drawing color to white
g.setColor(0xffffff);
//display the key code last pressed
g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT);

g.drawString("Frame : " + Integer.toString(frameIndex), 2, 68, Graphics.TOP | Graphics.LEFT);
g.drawString("X : " + Integer.toString(imgX), 2, 88, Graphics.TOP | Graphics.LEFT);

//clip the drawing area to a single frame
g.setClip(50, 50, 16, 16);

//draw the image
g.drawImage(imgEarth, imgX, 50, Graphics.TOP | Graphics.LEFT);

flushGraphics();

try{
Thread.sleep(30);
} catch (Exception ex){

}
}
g = null;
unload();
fParent.destroyApp(false);
fParent = null;
}
}



You can start up the MIDlet now. You should see a spinning globe with the current frame and x coordinate displayed below it. You can also change the speed in which the globe rotates by increasing or decreasing the value of iDelay in the run() method. Increasing iDelay will make the animation run slower while setting it to a lower value will make the animation run faster.


SUN JAVA WTK 2.5.1


That's it for clipping. If you have a question or would like to point out a mistake, feel free to post a comment. Have fun!!!

10 comments   |   post a comment
said...
I am really enjoying this tutorial. One typo the line
"if (frameIndex &rt 8) {"

should read

"if (frameIndex < 8) {"

thats all keep up the great work.
said...
Found it! It was supposed to be &lt; html entity for <, heh.

Thanx a bunch! :)
Hi,
My name is James Branam and I'm the NetBeans Community Docs Manager. Your blog entry would make a fantastic Tips & Tricks entry for our Community Docs wiki (http://wiki.netbeans.org/wiki/view/CommunityDocs). Would you be willing to contribute it? If you need any help or have any questions, please contact me at james.branam@sun.com. I look forward to hearing from you.
said...
Can't you do the same this way?: loading an Image, then creating a Sprite sp = (Image, Image.Width() / frames, Imag.Height);

Where "frames" is the number of frames of the animation. Then you use sp.nextFrame() and other functions which change the frames.

Would be the same? What of these two would be better?
said...
Hi Duque,

That depends on what you are using the images for. The Sprite class can only be used if the image is exactly divisible by the frameWidth and frameHeight. So all the frames must have the same size.

So if you have a bunch of graphics with different sizes in one image file, which is usually done to decrease the size of the image and preserve memory, you won't be able to use the Sprite class and you would have to use clipping to display different parts of the image.

It just so happens that I used an animation as an example to help beginners get a grasp of how to deal with the coordinate system when clipping images.

BTW, here's a link to the Sprite class tutorial : Using the Sprite Class and Sprite Movement.

Hope this helps.
said...
Ok. Got it! That was what I supposed ;)

Thanks devlin
said...
First: I wanna say that this blog is amazin!!
I'm learning a little more at each tuto...
I'm from Brazil, I'm student of computing science, and this blog too! =]
Very thaks for your job, and congratulations!

Second: I don't undertand because the coordinate X of frame 0 is 34.
Can you explain?
Thanks.


PS: my english isn't fluent, so sorry for possible gramatical's errors.
Please help.
In this example this line can work.

private midMain fParent;
....
public clsCanvas(midMain m) {
super(true);
fParent = m;
setFullScreenMode(true);
}
....
fParent.destroyApp(false);
fParent = null;
said...
This is absolutely brilliant post. Thanks for sharing.

Regards,
SBL Image clipping
Great share indeed. Thanks a lot