Friday, May 2, 2008

GPS device emulation how to

I do not have a GPS device. Though I dare to write applications for it. Somehow I have managed a way to emulate a GPS device.

So, what is the source of sample GPS data ? I use public gpsd servers as NMEA data source ..
You can do google to find a gpsd server.

How can we emulate ? I use blueproxy to proxy the gpsd server connection over bluetooth. So you just need to download and install blueproxy.

Finally it is simple, start your server with the following command,

shell #> blueproxy -v -c 11 -h gpsd_server.address -p gpsd_port

Please replace the "gpsd_server.address" and "gpsd_port" with something appropriate. Note that gpsd servers needs to receive R before it can start to broadcast NMEA messages. So just send 'R\r\n' before you wait for data.

As I am midlet developer, I tried the idea of proxy in midlet too .. Finally I was able to test the whole thing in WTK emulator.

Here is the code,(HTML generated by highlight 2.6.9, http://www.andre-simon.de/)

package blueProxy;

import java.io.IOException;

import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.StringItem;
import javax.microedition.midlet.MIDlet;
import java.io.OutputStream;
import java.io.InputStream;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.DiscoveryAgent;

public class BlueProxy extends MIDlet implements CommandListener {
// UI Specific
private final TextField url = new TextField("URL", "socket://gpsd.mainframe.cx:2947", 200, TextField.ANY);
private final StringItem log = new StringItem("Log", "no clients are connected ..");
private final Command quit = new Command("Quit", Command.EXIT, 1);
private final Command start = new Command("Start", Command.OK, 1);

public void startApp() {
// test ui
Form myForm = new Form("Blue Proxy");
myForm.append(url);
myForm.append(log);
myForm.addCommand(quit);
myForm.addCommand(start);
myForm.setCommandListener(this);
Display.getDisplay(this).setCurrent(myForm);
}

public void pauseApp() {
}

/**
* Destroy must cleanup everything. The thread is signaled to stop and no
* result is produced.
*/
public void destroyApp(boolean unconditional) {
notifyDestroyed();
}

public void commandAction(Command cmd, Displayable arg1) {
if(cmd == start) {
((Form)arg1).removeCommand(start);
new Thread() {
public void run() {
System.out.println("Started thread#1");
LocalDevice ld = null;
try {
ld = LocalDevice.getLocalDevice();
ld.setDiscoverable(DiscoveryAgent.GIAC);
} catch( BluetoothStateException e) {
e.printStackTrace();
return;
}
try {
// open server socket ..
log.setText("opening connection notifier ..");
StreamConnectionNotifier connn = (StreamConnectionNotifier) Connector.open("btspp://localhost:1;encrypt=false;authorize=false;authenticate=false");
while(true) {
log.setText("waiting for a new client ..");
try {
final StreamConnection conn = (StreamConnection)connn.acceptAndOpen();
// open client socket ..
log.setText("opening socket to " + url.getString());
final StreamConnection client = (StreamConnection) Connector.open(url.getString());
// peering ..
new Thread() {
public void run() {
System.out.println("Started thread#2");
try {
InputStream is = conn.openInputStream();
OutputStream os = client.openOutputStream();
byte data[] = new byte[200];
int res = 0;
while((res = is.read(data)) != 0) {
System.out.println("bt > " + new String(data, 0, res));
os.write(data, 0, res);
}
} catch(IOException e) {
e.printStackTrace();
}
}
}.start();
InputStream is = client.openInputStream();
OutputStream os = conn.openOutputStream();
byte data[] = new byte[200];
int res = 0;
while((res = is.read(data)) != 0) {
System.out.println("bt < " + new String(data, 0, res));
os.write(data, 0, res);
}
} catch(IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
} else {
destroyApp(true);
}


}
}

Thursday, May 1, 2008

Image thumbnail in an optimized way for J2Me platform

Trying to show thumbnail view after taking a big snapshot ? Trying to show a list view of images ? Or in any other case you try to resize or scale an image ?

I was searching for an optimized code for thumbnail creation. The main issues here are

- You need to be careful about the memory you use. After taking a big image in memory you may have a little left for allocation.
- You do not have floating point in most of the mobiles. So your image may not keep the image ratio right after scaling.
- Your need very time efficient code , you can keep the image quality apart ..

I did some google. And found the following discussions useful.

http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=41&t=002988
http://developer.sonyericsson.com/thread/27493

I tested some of those examples. Some of them cause OutOfMemory Exception. Some take a long time to resize image. But I thought I could do better. Finally I wrote the following code.


/**
* Gets the thumbnail that fit with given screen width, height and padding ..
*
* @param image The source image
* @param padding padding to the screen
* @return scaled image
*/
private final Image getThumbnailWrapper(Image image, int expectedWidth, int expectedHeight, int padding) {
final int sourceWidth = image.getWidth();
final int sourceHeight = image.getHeight();
int thumbWidth = -1;
int thumbHeight = -1;

// big width
if(sourceWidth >= sourceHeight) {
thumbWidth = expectedWidth - padding;
thumbHeight = thumbWidth * sourceHeight / sourceWidth;
// fits to height ?
if(thumbHeight > (expectedHeight - padding)) {
thumbHeight = expectedHeight - padding;
thumbWidth = thumbHeight * sourceWidth / sourceHeight;
}
} else {
// big height
thumbHeight = expectedHeight - padding;
thumbWidth = thumbHeight * sourceWidth / sourceHeight;
// fits to width ?
if(thumbWidth > (expectedWidth - padding)) {
thumbWidth = expectedWidth - padding;
thumbHeight = thumbWidth * sourceHeight / sourceWidth;
}
}

// XXX As we do not have floating point, sometimes the thumbnail resolution gets bigger ...
// we are trying hard to avoid that ..
thumbHeight = (sourceHeight < thumbHeight) ? sourceHeight : thumbHeight;
thumbWidth = (sourceWidth < thumbWidth) ? sourceWidth : thumbWidth;

return getThumbnail(image, thumbWidth, thumbHeight);
}

/**
* Gets thumbnail with a height and width specified ..
* @param image
* @param thumbWidth
* @param thumbHeight
* @return scaled image
*/
private final Image getThumbnail(Image image, int thumbWidth, int thumbHeight) {
int x, y, pos, tmp, z = 0;
final int sourceWidth = image.getWidth();
final int sourceHeight = image.getHeight();

// integer ratio ..
final int ratio = sourceWidth / thumbWidth;

// buffer where we read in data from image source
final int[] in = new int[sourceWidth];

// buffer of output thumbnail image
final int[] out = new int[thumbWidth*thumbHeight];

final int[] cols = new int[thumbWidth];

// pre-calculate columns we need to access from source image
for (x = 0,pos = 0; x < thumbWidth; x++) {
cols[x] = pos;

// increase the value without fraction calculation
pos += ratio;
tmp = pos + (thumbWidth - x) * ratio;
if(tmp > sourceWidth) {
pos--;
} else if(tmp < sourceWidth - ratio) {
pos++;
}
}

// read through the rows ..
for (y = 0, pos = 0, z = 0; y < thumbHeight; y++) {

// read a single row ..
image.getRGB(in, 0, sourceWidth, 0, pos, sourceWidth, 1);

for (x = 0; x < thumbWidth; x++, z++) {
// write this row to thumbnail
out[z] = in[cols[x]];
}

pos += ratio;
tmp = pos + (thumbHeight - y) * ratio;
if(tmp > sourceHeight) {
pos--;
} else if(tmp < sourceHeight - ratio) {
pos++;
}
}
return Image.createRGBImage(out, thumbWidth, thumbHeight, false);
}


This code has low memory need as it only reads one row from the source image each time. And it is super fast (I mean unbelievable). If anyone argue at this point or think there is a way to make it more optimized then welcome :) ..