ImageCanvas.java

/**
 * Copyright (c) 2012 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners.
 * See LICENSE.TXT for license information.
 */

package com.nokia.example;

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.*;

/**
 * This class displays a selected image centered in the screen. This class
 * implements a GestureListener which notifies the MIDlet of gesture events
 * associated with the UI element. This class also implements
 * OrientationListener to detect a change in the orientation of the device's
 * display
 */
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;

    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();
    }

    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;
    }

    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);
        }
    }

    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();
        }
    }

    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();
    }

    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;
        }
    }

    /**
     * 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);
    }

    /**
     * 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;
        }

    }
}