The ImageCanvas
class displays an image when it
is selected in the file browser. The viewer is created as a Canvas
on top of which the image data is
read and drawn.
To implement the ImageCanvas
class:
Note: The following implementation is based on the Series 40 full touch version of the MIDlet, meaning it is only compliant with Java Runtime 2.0.0 for Series 40 devices and newer. For the implementation of the regular version of the MIDlet, which is compatible with earlier devices, see the corresponding source code file in the downloadable zip file.
Create the ImageCanvas.java
class file.
Import the required packages and classes.
import com.nokia.mid.ui.VirtualKeyboard; import com.nokia.mid.ui.gestures.GestureEvent; import com.nokia.mid.ui.gestures.GestureInteractiveZone; import com.nokia.mid.ui.gestures.GestureListener; import com.nokia.mid.ui.gestures.GestureRegistrationManager; import com.nokia.mid.ui.orientation.Orientation; import com.nokia.mid.ui.orientation.OrientationListener; import java.io.IOException; import java.io.InputStream; import javax.microedition.io.Connector; import javax.microedition.io.file.FileConnection; import javax.microedition.lcdui.*;
Set ImageCanvas
to extend Canvas
and implement CommandListener
, OrientationListener
, and GestureListener
. Create the required variables.
class ImageCanvas extends Canvas implements CommandListener, OrientationListener, GestureListener { private final ImageViewerMIDlet midlet; private Image currentImage = null; int imageWidth; int imageHeight; private Command backCommand = new Command("Back", Command.BACK, 0); private Ticker ticker = new Ticker("Image Viewer"); private int mouseDownX; private int mouseDownY; private int deltaX; private int deltaY; private int posX; private int posY; int rgbImageData[]; int srcWidth; int srcHeight; float scaleIndex = 1;
In the class constructor:
Hide the open keypad command in the native options menu by
calling the static VirtualKeyboard.hideOpenKeypadCommand
method.
Create the MIDlet view, add the BACK command and CommandListener
, and set a Ticker
.
To detect display orientation changes, register an OrientationListener
using the static Orientation.addOrientationListener
method provided by the Orientation API.
Call a method for setting up gesture event handling.
ImageCanvas(ImageViewerMIDlet midlet) { /** * VirtualKeyboard is supported from Java Runtime 2.0.0 for Series 40 * onwards. To determine whether a Series 40 device is a full touch * device and hide the open keypad command from the Options menu if it * is. */ if (System.getProperty("com.nokia.keyboard.type").equals("None")) { VirtualKeyboard.hideOpenKeypadCommand(true); } setTitle("Image Viewer"); this.midlet = midlet; this.addCommand(backCommand); this.setCommandListener(this); this.setTicker(ticker); /** * Orientation is supported for Java Runtime 2.0.0 for Series 40 * onwards. Registers the orientation listener. Applications that need * information about events related to display orientation changes need * to register with Orientation to get notifications of the events. */ Orientation.addOrientationListener(this); initializeGesture(); }
Create a method
for opening a FileConnection
. The FileConnection
is used to read the image file from the device file system as an InputStream
.
public boolean displayImage(String imgName) { try { FileConnection fileConn = (FileConnection) Connector.open(imgName, Connector.READ); if(rgbImageData!=null)rgbImageData = null; InputStream fis = fileConn.openInputStream(); int overallSize = (int) fileConn.fileSize(); byte[] imageData = new byte[overallSize]; int readAmount = fis.read(imageData, 0, overallSize); fis.close(); fileConn.close(); //fis = null; //fileConn = null; ticker.setString("Image Viewer:" + imgName); currentImage = Image.createImage(imageData, 0, overallSize); imageData = null; srcWidth = imageWidth = currentImage.getWidth(); srcHeight = imageHeight = currentImage.getHeight(); scaleIndex = 1; repaint(); } catch (IOException e) { midlet.showError(e); return false; } catch (Exception e) { e.printStackTrace(); midlet.showError(e); return false; } catch (Error e) { e.printStackTrace(); if (e instanceof OutOfMemoryError) { midlet.showError("File is too large to display"); } else { midlet.showError("Failed to display this file. " + e.getMessage()); } return false; } return true; }
Paint the background screen black. In situations where there is no image to display or the image does not fill the whole screen, the background color is visible.
protected void paint(Graphics g) { int w = getWidth(); int h = getHeight(); // Set background color to black g.setColor(0); g.fillRect(0, 0, w, h); setImagePlacementPoint(); System.out.println("Pos X:" + posX + " Y:" + posY); if (currentImage != null) { g.drawImage(currentImage, posX, posY, Graphics.HCENTER | Graphics.VCENTER); } else { // If no image is available display a message g.setColor(0x00FFFFFF); g.drawString("No image", posX, posY, Graphics.HCENTER | Graphics.BASELINE); } }
While the image viewer screen contains a Back button, you can also make the MIDlet return to file manager view with any pressed key.
protected void keyReleased(int keyCode) { // Exit with any key midlet.displayFileBrowser(); } public void commandAction(Command command, Displayable displayable) { if (command == backCommand) { currentImage = null; rgbImageData = null; midlet.displayFileBrowser(); } }
Create methods for detecting touch down, touch release, and drag events on the device screen.
protected void pointerPressed(int x, int y) { mouseDownX = x; mouseDownY = y; } protected void pointerReleased(int x, int y) { deltaX = 0; deltaY = 0; System.out.println("up up up !"); } protected void pointerDragged(int x, int y) { deltaX = x - mouseDownX; deltaY = y - mouseDownY; mouseDownX = x; mouseDownY = y; repaint(); }
The device can switch between landscape and portrait modes, and the image may need to be redrawn between these changes. Create a method for handling these situations.
void setImagePlacementPoint() { // This needs to be taken each time, since the values will be chaged when // user tilt the phone to potrait mode to landscape mode. int canvasWidth = getWidth(); int canvasHeight = getHeight(); if (imageWidth > canvasWidth && deltaX != 0) { posX += deltaX; if (posX < (canvasWidth - imageWidth / 2)) { posX = canvasWidth - imageWidth / 2; } else if (posX > (imageWidth / 2)) { posX = (imageWidth / 2); } } else { posX = canvasWidth / 2; } if (imageHeight > canvasHeight && deltaY != 0) { posY += deltaY; if (posY < (canvasHeight - imageHeight / 2)) { posY = canvasHeight - imageHeight / 2; } else if (posY > (imageHeight / 2)) { posY = (imageHeight / 2); } } else { posY = canvasHeight / 2; } }
Implement the OrientationListener
by defining the displayOrientationChanged
method. The method is called every time the display orientation
changes. Use the displayOrientationChanged
method
to set the UI orientation by calling the static Orientation.setAppOrientation
method.
/** * Orientation is supported for Java Runtime 2.0.0 for Series 40 onwards. * Called when display's orientation has changed. */ public void displayOrientationChanged(int newDisplayOrientation) { /** * Change MIDlet UI orientation */ Orientation.setAppOrientation(newDisplayOrientation); }
Note: To adjust the MIDlet UI orientation using
the Orientation API, declare the Nokia-MIDlet-App-Orientation
JAD attribute with the value manual
in the MIDlet
JAD file:
Nokia-MIDlet-App-Orientation: manual
Implement gesture
event handling. The initializeGesture
method sets
up gesture event handling for the MIDlet. The gestureAction
method implements the actual event handling. The MIDlet only registers
pinch gestures, which it uses to zoom in and out of the selected image.
The scaleImage
method is used by the gestureAction
method to scale images during zooming.
/** * Initializes gestures and set it to receive gesture events */ public void initializeGesture() { /** * Set the listener to register events for ImageCanvas (this,) and * register the GestureListener for the UI element (,this) */ GestureRegistrationManager.setListener(this, this); /** * Create an interactive zone and set it to receive taps */ GestureInteractiveZone myGestureZone = new GestureInteractiveZone(GestureInteractiveZone.GESTURE_PINCH); /** * Set the interactive zone to also receive other events */ //myGestureZone.setGestures(GestureInteractiveZone.GESTURE_ALL); /** * Set the location (relative to the container) and size of the * interactive zone: */ myGestureZone.setRectangle(0, 0, getWidth(), getHeight()); /** * Register the interactive zone for GestureCanvas (this) */ GestureRegistrationManager.register(this, myGestureZone); } /** * Scales the current image */ private void scaleImage(float scale) { try { scaleIndex += scale; if(rgbImageData==null){ rgbImageData = new int[srcWidth * srcHeight]; currentImage.getRGB(rgbImageData, 0, imageWidth, 0, 0, imageWidth, imageHeight); } if (scaleIndex < 0.1f) { scaleIndex = 0.1f; } int newImageWidth = (int) (srcWidth * scaleIndex); int newImageHeight = (int) (srcHeight * scaleIndex); int rgbImageScaledData[] = new int[newImageWidth * newImageHeight]; // calculations and bit shift operations to optimize the for loop int tempScaleRatioWidth = ((srcWidth << 16) / newImageWidth); int tempScaleRatioHeight = ((srcHeight << 16) / newImageHeight); int i = 0; for (int y = 0; y < newImageHeight; y++) { for (int x = 0; x < newImageWidth; x++) { rgbImageScaledData[i++] = rgbImageData[(srcWidth * ((y * tempScaleRatioHeight) >> 16)) + ((x * tempScaleRatioWidth) >> 16)]; } } //Create an RGB image from rgbImageScaledData array currentImage = Image.createRGBImage(rgbImageScaledData, newImageWidth, newImageHeight, true); imageWidth = newImageWidth; imageHeight = newImageHeight; } catch (OutOfMemoryError e) { // TODO Auto-generated catch block scaleIndex -= scale; e.printStackTrace(); midlet.showError("Out of memory "+e.getMessage()); } } /** * Defines the gestureAction method for the class. This method is called * every time a gesture event occurs for a UI element that uses * GestureListener. The method is called with all the information related to * the gesture event. */ public void gestureAction(Object arg0, GestureInteractiveZone arg1, GestureEvent gestureEvent) { switch (gestureEvent.getType()) { /** * This gesture event is supported from Java Runtime 2.0.0 for * Series 40 onwards. Receives pinch events and check the pinch * distance change to scale the current image. */ case GestureInteractiveZone.GESTURE_PINCH: if (gestureEvent.getPinchDistanceChange() < 0) { scaleImage(-0.1f); } else if (gestureEvent.getPinchDistanceChange() > 0) { scaleImage(0.1f); } repaint(); break; default: break; } } }
Now that you have implemented the image viewing functionality, implement additional features for text input and error handling.