Sonntag, 30. September 2007

My Image Resizing / Scaling improved version

Trying desperately to find a useful function for scaling / resizing down images while preloading them for my J2ME application, being able to process transparency as well as consume as low heap memory as possible, I came up with a mix of functions all combined into one:



/**
* resizeImage
* Gets a source image along with new size for it and resizes it.
*
* @param src The source image.
* @param factor The resizing factor
* @param div Final image width will be corrected to be divideable by this number
*
* @return The resized image.
*/
Image resizeImage(Image src, float factor, int div) {
int srcW = src.getWidth();
int srcH = src.getHeight();

// Scale down destination width and height by factor
int destW = (int)(srcW * factor);
int destH = (int)(srcH * factor);

// Correction of width and height to be divideable by div-parameter
if ((div != 0) && (destW % div != 0)) {
for (int i=1; i<=div; i++) {
if ((destW - i) % div == 0) {
destW = destW - i;
if (destH % (destW / div) != 0) {
for (int j=1; j<=div; j++) {
if ((destH - j) % (destW / div) == 0) {
destH = destH - j;
break;
}
}
}
}
}
}

// Prepare arrays for line-by-line-processing
final int[] lineRGB = new int[srcW];
final int[] srcPos = new int[destW]; // cache for x positions

/*
* Pre-calculate x positions with modified bresenham algorithm
*/
int n = 0;
int eps = -(srcW >> 1);
for( int x = 0; x < srcW; x++ ) {
eps += destW;
if ( (eps << 1) >= srcW ) {
if( ++n == destW ) {
break;
}
srcPos[n] = x;
eps -= srcW;
}
}

final int[] dest = new int[destW*destH];
for( int y = 0; y < destH; y++ ) {
src.getRGB( lineRGB, 0, srcW, 0, y * srcH / destH, srcW, 1 );
for( int x = 0; x < destW; x++ ) {
dest[y*destW+x] = lineRGB[srcPos[x]];
}
}

// Garbage collect
System.gc();
// Return a new image created from the destination pixel buffer
Image destImg = Image.createRGBImage(dest, destW, destH, true);
return destImg;
}



To copy this code, please open the source of this page and CTRL-C from there.

Any comments/improvements would be much appreciated.

--thgc

Sonntag, 23. September 2007

Resizing images

A useful class for resizing images (even with alpha transparency!).

Will only work properly with MIDP 2.0


import javax.microedition.lcdui.Image;

public class Resizer {
// fixed point constants
private static final int FP_SHIFT = 13;
private static final int FP_ONE = 1 << FP_SHIFT;
private static final int FP_HALF = 1 << (FP_SHIFT - 1);

// resampling modes - valid values for the mode parameter of resizeImage()
// any other value will default to MODE_BOX_FILTER because of the way the conditionals are set in resizeImage()
public static final int MODE_POINT_SAMPLE = 0;
public static final int MODE_BOX_FILTER = 1;

/**
* getPixels
* Wrapper for pixel grabbing techniques.
* I separated this step into it's own function so that other APIs (Nokia, Motorola, Siemens, etc.) can
* easily substitute the MIDP 2.0 API (Image.getRGB()).
* @param src The source image whose pixels we are grabbing.
* @return An int array containing the pixels in 32 bit ARGB format.
*/
static int[] getPixels(Image src) {
int w = src.getWidth();
int h = src.getHeight();
int[] pixels = new int[w * h];
src.getRGB(pixels,0,w,0,0,w,h);
return pixels;
}

/**
* drawPixels
* Wrapper for pixel drawing function.
* I separated this step into it's own function so that other APIs (Nokia, Motorola, Siemens, etc.) can
* easily substitute the MIDP 2.0 API (Image.createRGBImage()).
* @param pixels int array containing the pixels in 32 bit ARGB format.
* @param w The width of the image to be created.
* @param h The height of the image to be created. This parameter is actually superfluous, because it
* must equal pixels.length / w.
* @return The image created from the pixel array.
*/
static Image drawPixels(int[] pixels, int w, int h) {
return Image.createRGBImage(pixels,w,h,true);
}

static Image resizeImage(Image src, float factor) {
return resizeImage(src, factor, MODE_BOX_FILTER);
}

/**
* resizeImage
* Gets a source image along with new size for it and resizes it.
* @param src The source image.
* @param destW The new width for the destination image.
* @param destH The new heigth for the destination image.
* @param mode A flag indicating what type of resizing we want to do. It currently supports two type:
* MODE_POINT_SAMPLE - point sampled resizing, and MODE_BOX_FILTER - box filtered resizing (default).
* @return The resized image.
*/
static Image resizeImage(Image src, float factor, int mode) {
int srcW = src.getWidth();
int srcH = src.getHeight();
int destW = (int)(srcW * factor);
int destH = (int)(srcH * factor);

// create pixel arrays
int[] destPixels = new int[destW * destH]; // array to hold destination pixels
int[] srcPixels = getPixels(src); // array with source's pixels

if (mode == MODE_POINT_SAMPLE) {
// simple point smapled resizing
// loop through the destination pixels, find the matching pixel on the source and use that
for (int destY = 0; destY < destH; ++destY) {
for (int destX = 0; destX < destW; ++destX) {
int srcX = (destX * srcW) / destW;
int srcY = (destY * srcH) / destH;
destPixels[destX + destY * destW] = srcPixels[srcX + srcY * srcW];
}
}
}
else {
// precalculate src/dest ratios
int ratioW = (srcW << FP_SHIFT) / destW;
int ratioH = (srcH << FP_SHIFT) / destH;

int[] tmpPixels = new int[destW * srcH]; // temporary buffer for the horizontal resampling step

// variables to perform additive blending
int argb; // color extracted from source
int a, r, g, b; // separate channels of the color
int count; // number of pixels sampled for calculating the average

// the resampling will be separated into 2 steps for simplicity
// the first step will keep the same height and just stretch the picture horizontally
// the second step will take the intermediate result and stretch it vertically

// horizontal resampling
for (int y = 0; y < srcH; ++y) {
for (int destX = 0; destX < destW; ++destX) {
count = 0; a = 0; r = 0; b = 0; g = 0; // initialize color blending vars
int srcX = (destX * ratioW) >> FP_SHIFT; // calculate beginning of sample
int srcX2 = ((destX + 1) * ratioW) >> FP_SHIFT; // calculate end of sample

// now loop from srcX to srcX2 and add up the values for each channel
do {
argb = srcPixels[srcX + y * srcW];
a += ((argb & 0xff000000) >> 24); // alpha channel
r += ((argb & 0x00ff0000) >> 16); // red channel
g += ((argb & 0x0000ff00) >> 8); // green channel
b += (argb & 0x000000ff); // blue channel
++count; // count the pixel
++srcX; // move on to the next pixel
}
while (srcX <= srcX2 && srcX + y * srcW < srcPixels.length);

// average out the channel values
a /= count;
r /= count;
g /= count;
b /= count;

// recreate color from the averaged channels and place it into the temporary buffer
tmpPixels[destX + y * destW] = ((a << 24) | (r << 16) | (g << 8) | b);
}
}

// vertical resampling of the temporary buffer (which has been horizontally resampled)
for (int x = 0; x < destW; ++x) {
for (int destY = 0; destY < destH; ++destY) {
count = 0; a = 0; r = 0; b = 0; g = 0; // initialize color blending vars
int srcY = (destY * ratioH) >> FP_SHIFT; // calculate beginning of sample
int srcY2 = ((destY + 1) * ratioH) >> FP_SHIFT; // calculate end of sample

// now loop from srcY to srcY2 and add up the values for each channel
do {
argb = tmpPixels[x + srcY * destW];
a += ((argb & 0xff000000) >> 24); // alpha channel
r += ((argb & 0x00ff0000) >> 16); // red channel
g += ((argb & 0x0000ff00) >> 8); // green channel
b += (argb & 0x000000ff); // blue channel
++count; // count the pixel
++srcY; // move on to the next pixel
}
while (srcY <= srcY2 && x + srcY * destW < tmpPixels.length);

// average out the channel values
a /= count; a = (a > 255) ? 255 : a;
r /= count; r = (r > 255) ? 255 : r;
g /= count; g = (g > 255) ? 255 : g;
b /= count; b = (b > 255) ? 255 : b;

// recreate color from the averaged channels and place it into the destination buffer
destPixels[x + destY * destW] = ((a << 24) | (r << 16) | (g << 8) | b);
}
}
}

// return a new image created from the destination pixel buffer
return drawPixels(destPixels,destW,destH);
}
}


To copy above code, please open page source and CTRL-C from there.

--thgc

Freitag, 13. Juli 2007

Quicklist class

I found this QuickList class to be very useful for passing dynamic parameters to functions without using Vectors and the likes:




/**
* Simple class for generating lists.
*
* QuickList differs from the List Collection in as much as once created,
* a QuickList does
* not change. New elements are added to a QuickList by creating a
* new QuickList which includes both the new element and the old QuickList.
*
* This makes QuickList suitable for generating low-overhead
* temporary data structures (amongst other things).
*/
public class QuickList {

/**
* All elements in this QuickList apart from the first
*/
private QuickList tail;
/**
* The first element in this QuickList
*/
private Object head;

/**
* Empty Quicklist. Add to the empty QuickList in order to generate a new list
*/
public static final QuickList EMPTY = new QuickList(null, null);

/**
* Creates a new instance of QuickList.
*
* @param tail A list to which a new item is being appended
* @param head a new item to append to a list
*/
protected QuickList(QuickList tail, Object head) {
this.head = head;
this.tail = tail;
}

/**
* Adds an item to a list.
*
* Note that this method does not change this list, but instead returns a
* new list with the new item added to it.
*
* @param item An item to add to the list
* @return A new list with item added to the front
*/
public QuickList add(Object item) {
return new QuickList(this, item);
}

/**
* Get the first item in this list.
*
* @return the first item in this list
*/
public Object head() {
return head;
}

/**
* Get the rump of this list (everything except the first item).
*
* @return A list including all items except the first
*/
public QuickList tail() {
return tail;
}

/**
* Calculate the size of a QuickList
*
* @return Number of elements in this quicklist (not counting EMPTY element)
*/
public int size() {
if (this == EMPTY) return 0;
else return tail.size() + 1;
}
}



To copy this code, please open the source of this page and copy it (STRG+C) from there.

Samstag, 16. Juni 2007

Defining waypoints for your sprite, or, moving your sprite on a straight line

If you want to move your sprite from one point to another on a straight line, you will have to calculate the line based on the two positions given. Since the equation for a line is

mx+b = y

you will have to calculate the slope (m) and the intercept (b) to be able to calculate any position in between the starting and the end position. The two methods for doing this in J2ME:




class Util {
/**
* Slope calculation for line given by two x-y-positions
*
* @param x1 First position's x-parameter
* @param y1 First position's y-parameter
* @param x2 Second position's x-parameter
* @param y2 Second position's y-parameter
* @return calculated slope value
*/
public static double calcLineSlope(int x1, int y1, int x2, int y2) {
double dx = x1 - x2;
double dy = y1 - y2;
if ((dx != 0) && (dy != 0)) {
return (dy / dx);
} else {
return 0;
}
}

/**
* Intercept calculation for line given by x-y-position and slope
*
* @param x x-parameter of given position
* @param y y-parameter of given position
* @param m slope of line
* @return calculated intercept value
*/
public static double calcLineIntercept(int x, int y, double m) {
if (m != 0) {
return y - m*x;
} else {
return 0;
}
}
}


 


To put these functions into use, you can try something like the following EnemySprite class



import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.Sprite;

class EnemySprite extends Sprite {

double cur_m;
double cur_b;
int target_x;
int target_y;

int speed;

// Constructor, is called when you instantiate your EnemySprite
public EnemySprite(Image img) {
super(img);

speed = 5;

// Starting position (20, 20), target position (30, 100)
this.setPosition(20, 20);
target_x = 30; target_y = 100;
cur_m = Util.calcLineSlope(target_x, target_y, this.getX(), this.getY());
cur_b = Util.calcLineIntercept(this.getX(), this.getY(), cur_m);
}

// Call this method in your game loop
public void update () {
if ((this.getX() != target_x) || (this.getY() != target_y)) {
double dx = Math.abs(target_x - this.getX());
double dy = Math.abs(target_y - this.getY());
if ((dx != 0) && (dy != 0)) {
int nextx = 0;

if (Math.abs(this.getX() - target_x) < speed) {
nextx = target_x;
} else {
nextx = (this.getX() < target_x) ?
this.getX() + speed :
this.getX() - speed;
}
int nexty = (int)(nextx * cur_m + cur_b);

this.setPosition(nextx, nexty);
} else {
if (dx != 0) {
if (Math.abs(this.getX() - target_x) < 2*speed) {
this.setPosition(target_x, this.getY());
} else {
int nextx = (this.getX() < target_x) ?
this.getX() + 2*speed :
this.getX() - 2*speed;
this.setPosition(nextx, this.getY());
}
}
if (dy != 0) {
if (Math.abs(this.getY() - target_y) < 2*speed) {
this.setPosition(this.getX(), target_y);
} else {
int nexty = (this.getY() < target_y) ?
this.getY() + 2*speed:
this.getY() - 2*speed;
this.setPosition(this.getX(), nexty);
}
}
}
} else {

// Target position reached, inititalize next waypoint...

}
}
}


 


This will only work with MIDP 2.0, since floating-point-values are not supported in MIDP 1.0 to my knowledge.

To copy and paste this code, please open the source of this page and copy from there.



--thgc