Saving Some Memory

I’m about to write something about a language that many of my other colleagues would go “Ew! You willingly touched that language?”. All I can say in my ‘defense’ is that at the end of the day its the solution/the algorithm/getting things done that matters.
Some colleagues working on mobile/J2ME games were getting ready to test some of their games on devices. They ran into IOException when loading assets primarly: OutOfMemory – Even though “It all works great on the em/simulator”. They were building a game on a Motorola K1 – the spec says it provides a 900KB heap (Runtime.totalMemory() traces approximately 839000). Note that the em/simulator runs on the PC and allocates more memory for itself. Also note that the size of the JAR/PNG on disk doesn’t particularly matter (yes, its obviously proportionate). The amount of memory required by most devices is: width x height x 2(16 bit indices). The game used a Sprite class which loads a large image containing the individual animation sprites, and it takes the height and width of the individual frame as a parameter. Considering most of the images have a large numer of transparent pixels, this seemed like quite a waste of memory to me. Especially I believe drawing an image on these devices is more of a ‘blit’ rather than a ‘lookup texture for fragment’ operation. Here’s a strategy I threw together in a couple of minutes to help out. The basic idea is to let the artist make a normal large image (or even multiple images of the same size) so that s/he can continue to control and see where in the frame the asset is (without any additional learning curve for some other image creation strategy) and split it using the scheme mentioned below and change the loading/drawing code accordingly to accommodate it.

// Here's how to split the image into more tightly packed sub images.
// This assumes that drawing of the frame images in the initial large
// image that you're currently using is relative to the center of the 
// frame.
// Fix up syntactical issues for Java, and fill in appropriate function names
// Note this splitting is an offline process. Especially since you
// need to write out the individual images. Do it on the device would be
// pointless unless you have a really long content pipeline and wish to
// save a step.

// This information would be used to draw the sub image at an offset
// to the initial frame.
class SubImageDimDesc {
	// If needed
	//int top, left;
	//int  bottom, right;
	int xOff, yOff;	// I hear the drawing is relative to the center of the sprite

void createSplitImages(
	String baseFilename,		// "player" for "player.png"
	int frameWidth, frameHeight,	// individual frame dimensions. eg. 86x75
	Image baseImage = Image.createImage("/"+baseFileName+".png");
	// Full input image dimensions. eg. 1032x225
	int totalWidth = baseImage.getWidth();
	int totalHeight baseImage.getHeight();
	int totalPixelsUsed = 0; 	// Just statistical data.
	int rows = frameHeight/totalHeight;
	int cols = frameWidth/totalWidth;
	// Color data in input image 0xAARRGGBB (Alpha, Red, Green, Blue)
	int [] rawImageData = new int[totalWidth*totalHeight]; // populate with input image data.
	// TODO: Populate 
	//Image.getRGB(rawImageData, totalWidth, totalHeight); // FIX ME

	SubImageDimDesc [] subImageDims = new SubImageDimDesc[rows*cols];

	int i=0;
	for(int r=0;r<rows;++r)
		for(int c=0;r<cols;++c)
			int top = frameHeight;
			int left = frameWidth;
			int bottom = 0;
			int right = 0;
			int subFrameWidth, subFrameHeight;
			// Find sub Image Dimensions from individual frame in large image.
			for(int h=0;i<frameHeight;++h)
				for(int w=0;i<frameWidth;++w)
					int pixel = rawImageData[(h*totalHeight)+(c*frameWidth)+w];
					// could also user if(pixel) but we can only check the alpha channel instead as well
					if((pixel & 0xFF000000)>0)
							top = h;
							bottom = h;
							left = w;
							right = w;
			subFrameWidth = right - left;
			subFrameHeight = bottom - top;
			// Allocate and populate data in the sub image
			int rawSubImageData[subFrameWidth*subFrameHeight];
			for(int h=0;h<subFrameHeight;++h)
				for(int w=0;w<subFrameWidth;++w)
					int pixel = rawImageData[((h+top)*totalHeight)+((c+left)*frameWidth)+w];
					rawSubImageData[(h*subFrameHeight)+w] = pixel;
			// FIX ME: See docs for function to set image data.
			//Image subImage = Image.setRGB(rawSubImageData, subFrameHeight, subFrameHeight); // FIX ME
			//subImageDims[i].top = top;
			//subImageDims[i].left = left;
			//subImageDims[i].bottom = bottom;
			//subImageDims[i].right = right;
			subImageDims[i].xOff = (frameWidth/2) - (left+subFrameWidth/2);
			subImageDims[i].yOff = (frameHeight/2) - (top+subFrameHeight/2);

			String subImageFilename = baseFilename+i+".png";
			// Write this sub image. TODO: Find correct function for this.
			// subImage.writeToFile("/"+subImageFilename);
			// Append subImageDims[i] to a descriptor file: FIX ME:
			//File splitImagesDescFile =".txt", MODE_APPEND);
			totalPixelsUsed += subFrameWidth*subFrameHeight;
	System.out.println(i+" sub images created. This would save a total of "+((totalWidth*totalHeight) - totalPixelsUsed)+" pixels compared to the original image's memory footprint");

// The split images sprite class could take the "baseName" from above
// and a number (rows*cols) of images in this 'sequence'
class SplitImagesSprite {
	SubImageDimDesc subImageDims[];
	Image subImages[];
	SplitImagesSprite(String baseName, int numImages)
		// Load Descriptor
		subImageDims = new SubImageDimDesc[numImages];
		// TODO: Read dims from file
		//File splitImagesDescFile =".txt", MODE_READ);

		// Load images
		subImages = new Image[numImages];
		for(int i=0;i<numImages;++i)
			String subImageName = baseName+i+".png";
			subImages[i] = Image.createImage("/"+subImageName);

	// When drawing the ith image/frame, draw it at the offset
	// if earlier it was something like g.drawImage(baseImage, playerPosX+frameLeft, playerPosY+frameTop, frameWidth, frameHeight);
	// it would be something like g.drawImage(subImage, playerPosX+subImageDims[i].xOff, playerPosY+subImageDims[i].yOff);
	void drawFrame(int frameNum, int posX, int posY)
		//g.drawImage(subImages[frameNum], posX+subImageDims[frameNum].xOff, posY+subImageDims[frameNum].yOff);

The first image shows 15 frames in a single image and the other two show the first frame and the last frame saved separately. Some back of the envelope numbers, each frame in the input image is (86×75) 6450 pixels. The first split image is (50×57) 2850 pixels and the last one is (63×20) 1260 pixels. Congratulations you just saved about 55% on one and 80% on the last one.

Potential interviewees, if you’ve been reading so far, this might pay off since I might base some questions on this – I don’t think I’d particularly care about you knowing the insides of a language as long as you can be creative and demonstrate a thinking mind. Additionally, It is possible that reading from disk (flash) on phones might just be twice as expensive as reading from RAM (something on the lines of a Nintendo DS?), in that case infrequently used frame sequences could be kept out of memory. Also note that I don’t claim to be an expert about Java/mobile games, so if you have something constructive to say, go ahead and comment (otherwise, you know the drill.). I learnt that there’s a memory monitor that comes with the WTK in my quick look into J2ME, if you’re not aware of it or aren’t using it – get acquainted with it.

In other news, I quickly made a little asset to throw at my exporter to load up in a project which is ‘coming soon’ on the iPhone. Perhaps I’ll post some screenshots later (Update: See the album named ‘Kangaroo Quest’).

(3ds Max – Creating an Asterisk)
Line (Spline) tool (make half the asterisk in the front viewport)
Mirror Y axis (copy)
Modify panel > attach
Delete (the un-necessary vertex in the middle on the Z/up axis.
Extrude modifier
Convert to editable poly
Connect vertices to makes sure all faces are quads/tris.

There, now this post isn’t only about J2ME.

This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s