Saturday, March 12, 2011

Convert image to byte array (byte[]) in JavaMe II

The Convert image to byte array (byte[]) in JavaMe post  showed how you can read an image from your jar file or from the sd card of your device and convert it to a byte array. Those images are called immutable (non-changeable) and when we read them we are reading bytes in some format (png, jpeg..) but, what happens when we are creating mutable (changeable) images in memory and we want to get their bytes?

For example, what if you can create a snapshot from what a canvas is showing? or what if you create an application that reads an image and apply some effects like blur, resizing, etc.?

You have to be very careful in these situations because you could, some how, get the bytes from the mutable image, but you certanly need to encode them (format them in png or jpeg...) in order to show them in your application or to write them to a file so the user can see it.

So in this post we are going to use a very simple PNG encoder. You can get more information about it using the following link:


There are some other encoders that also use compression and formats like bmp, jpeg, etc. but for this post we are just showing you an example with PNG as it is the MIDP's standard image format. Download the PNG class and add it to your project so you can use it.

In this example we are going to create a mutable image and then we are going to use some methods to get the image's bytes and finally we are going to encode those bytes using the PNG Encoder. The image is very simple, just a smily.



  
   //inside a midlet...

   /**
   * Creates a mutable (changeable) Image with a smily.
   * @return Image mutable image with a smile face on it
   */
  public Image createImage() {
    Image img = Image.createImage(100, 100);
    //Get the graphics so you can paint the image
    Graphics g = img.getGraphics();
    //change to black an paint the whole background
    g.setColor(0x000000);
    g.fillRect(0, 0, 100, 100);

    //change to yellow
    g.setColor(0xFFFF00);
    //left|top position of the face so it seems centered
    int xCenter = (img.getWidth() - 80) / 2;
    int yCenter = (img.getHeight() - 80) / 2;
    //draw the face
    g.fillArc(xCenter, yCenter, 80, 80, 0, 360);
    //change to black color
    g.setColor(0x000000);
    //left eye
    g.fillArc(xCenter + 20, yCenter + 20, 10, 15, 0, 360);
    //right eye
    g.fillArc(xCenter + 50, yCenter + 20, 10, 15, 0, 360);
    //smile...well kind of
    g.fillArc(xCenter + 15, yCenter + 50, 50, 10, 180, 180);

    return img;
  }

OK, with the last piece of code you created a mutable image (changeable) but, in what format is it? PNG? JPEG? none? That's correct you don't know, so that's why you have to encode it. The PNG encoder that we are using in this post has a method called: +toPNG(int,int,byte[],byte[],byte[],byte[]):byte[]
That's the method we are going to use in order to get our image's bytes in PNG format. But before using it, we need to get the params we are going to send. The first ints are the width and height of the image, the byte arrays are in order: alpha, red, green and... blue.

The width and height are pretty straightforward: +getWidth():int and +getHeight():int from the Image object, but to get the byte arrays you will need to do some binary operations:

  
   //inside a midlet...

  /**
   * Gets the channels of the image passed as parameter.
   * @param img Image
   * @return matrix of byte array representing the channels:
   * [0] --> alpha channel
   * [1] --> red channel
   * [2] --> green channel
   * [3] --> blue channel
   */
  public byte[][] convertIntArrayToByteArrays(Image img) {
    int[] pixels = new int[img.getWidth() * img.getHeight()];
    img.getRGB(pixels, 0, img.getWidth(), 0, 0, img.getWidth(), 
               img.getHeight());
    
    // separate channels
    byte[] red = new byte[pixels.length];
    byte[] green = new byte[pixels.length];
    byte[] blue = new byte[pixels.length];
    byte[] alpha = new byte[pixels.length];

    for (int i = 0; i < pixels.length; i++) {
      int argb = pixels[i];
      //binary operations to separate the channels
      //alpha is the left most byte of the int (0xAARRGGBB)
      alpha[i] = (byte) (argb >> 24);
      red[i] = (byte) (argb >> 16);
      green[i] = (byte) (argb >> 8);
      blue[i] = (byte) (argb);
    }

    return new byte[][]{alpha, red, green, blue};
  }

Don't you worry if you don't understand immediatly the above code, just imagine a pixel as the following int 0xAARRGGBB, where AA = alpha channel, RR = red channel, GG = green channel and BB = blue channel, so if you want to separate the alpha channel byte you need to move it to the right, that's the binary operator >> doing... and remember 1 byte = 8 bits so in order to move AA (1 byte = 8 bits) to BB you have to move it 3 times or 24 bits and when you have it in the right most byte, you can cast it to byte and then you have separated the alpha channel, got it?

Finally we need to invoke +toPNG(int,int,byte[],byte[],byte[],byte[]):byte[]  method in order to get our bytes encoded as PNG format and that's it. What to do with the encoded bytes is up to you... send them over the network, write them to a file using FileConnection API, etc. You can also check my previous posts if you need more documentation.

see ya soon!


References:

A minimal PNG encoder for J2ME. 2009. [online].
Available on Internet: http://www.chrfr.de/software/midp_png.html
[accessed on March 12 2011].

J2ME Screenshot of a Canvas. March 2010. Forum.Nokia [online].
Available on Internet: http://discussion.forum.nokia.com/forum/showthread.php?191207-J2ME-Screenshot-of-a-Canvas
[accessed on March 12 2011].

PNG Encoding in Java ME. March 2010. Forum.Nokia [online].
Available on Internet: http://wiki.forum.nokia.com/index.php/PNG_Encoding_in_Java_ME
[accessed on March 12 2011].