Implementing the image viewing functionality

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.

  1. Create the ImageCanvas.java class file.

  2. 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.*;
  3. 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;
  4. 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();
        }
  5. 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;
        }
  6. 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);
            }
        }
  7. 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();
            }
        }
  8. 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();
        }
  9. 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;
            }
        }
  10. 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
  11. 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.