ImageCanvas.java

/**
 * Copyright (c) 2013 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.imagescaler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;

import javax.microedition.amms.GlobalManager;
import javax.microedition.amms.MediaProcessor;
import javax.microedition.amms.MediaProcessorListener;
import javax.microedition.amms.control.imageeffect.ImageEffectControl;
import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.media.MediaException;

import com.nokia.mid.imagescale.ImageScaler;
import com.nokia.mid.imagescale.ImageScalerException;
import com.nokia.mid.imagescale.ImageScalerListener;
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;

/**
 * This class displays a selected image centered in the screen and contains
 * the implementation for scaling the image.
 */
public class ImageCanvas
    extends Canvas
    implements CommandListener, 
               GestureListener,
               ImageScalerListener,
               MediaProcessorListener,
               OrientationListener
{
    // Constants
    private static final String JPEG_TYPE = "image/jpeg";
    private static final String DESTINATION_FILE_NAME_PREFIX = "temp_";
    private static final double SEVENTYFIVE_PERCENTS = .75;
    private static final double FIFTY_PERCENTS = .5;
    private static final double TWENTYFIVE_PERCENTS = .25;

    // Members
    private final Command backCommand = new Command("Back", Command.BACK, 1);
    private final Command downscaleTo75PercentsCommand = new Command("Scale to 75 %", Command.ITEM, 1);
    private final Command downscaleTo50PercentsCommand = new Command("Scale to 50 %", Command.ITEM, 2);
    private final Command downscaleTo25PercentsCommand = new Command("Scale to 25 %", Command.ITEM, 3);
    private final Command resetCommand = new Command("Reset to original size", Command.ITEM, 6);
    private final Command monochromeCommand = new Command("To monochrome", Command.ITEM, 7);
    private MainView mainView = null;
    private ImageScaler imageScaler = null;
    private Image originalImage = null;
    private Image currentImage = null;
    private Hashtable imageTable = new Hashtable();
    private String currentImageFilePath = null;
    private ByteArrayOutputStream byteArrayOutputStream = null;
    private int originalImageWidth = 0;
    private int originalImageHeight = 0;
    private int currentImageWidth = 0;
    private int currentImageHeight = 0;
    private int currentPointerX = 0;
    private int currentPointerY = 0;
    private int deltaPointerX = 0;
    private int deltaPointerY = 0;
    private int imagePosX;
    private int imagePosY;
    private int rgbImageData[];

    /**
     * Constructor
     */
    public ImageCanvas(MainView mainView) {
        // Hide the virtual keyboard command
        if (System.getProperty("com.nokia.keyboard.type").equals("None")
            || System.getProperty("com.nokia.keyboard.type").equals("OnekeyBack"))
        {
            VirtualKeyboard.hideOpenKeypadCommand(true);
        }
        
        this.mainView = mainView;
        
        setTitle("Image Scaler");
        
        addCommand(backCommand);
        addCommand(downscaleTo75PercentsCommand);
        addCommand(downscaleTo50PercentsCommand);
        addCommand(downscaleTo25PercentsCommand);
        addCommand(resetCommand);
        setCommandListener(this);
        
        Orientation.addOrientationListener(this);
        
        // Start listening for pinch gestures
        GestureRegistrationManager.setListener(this, this);
        GestureInteractiveZone myGestureZone = new GestureInteractiveZone(
                GestureInteractiveZone.GESTURE_PINCH);
        myGestureZone.setRectangle(0, 0, getWidth(), getHeight());
        GestureRegistrationManager.register(this, myGestureZone);
    }

    /**
     * @see javax.microedition.lcdui.CommandListener#commandAction(Command, Displayable)
     */
    public void commandAction(Command command, Displayable displayable) {
        if (command == backCommand) {
            currentImage = null;
            rgbImageData = null;
            mainView.displayThis();
        }
        else if (command == downscaleTo75PercentsCommand
            || command == downscaleTo50PercentsCommand
            || command == downscaleTo25PercentsCommand)
        {
            int width = originalImageWidth;
            int height = originalImageHeight;
            
            if (command == downscaleTo75PercentsCommand) {
                width *= SEVENTYFIVE_PERCENTS;
                height *= SEVENTYFIVE_PERCENTS;
            }
            else if (command == downscaleTo50PercentsCommand) {
                width *= FIFTY_PERCENTS;
                height *= FIFTY_PERCENTS;
            }
            else {
                width *= TWENTYFIVE_PERCENTS;
                height *= TWENTYFIVE_PERCENTS;
            }
            
            final int newWidth = width;
            final int newHeight = height;
            
            new Thread(new Runnable() {
                public void run() {
                    scaleImage(newWidth, newHeight, currentImageFilePath);
                }
            }).start();
        }
        else if (command == resetCommand) {
            new Thread(new Runnable() {
                public void run() {
                    loadImage(currentImageFilePath);
                }
            }).start();
        }
        else if (command == monochromeCommand) {
            new Thread(new Runnable() {
                public void run() {
                    createMonochromeImage(currentImageFilePath);
                }
            }).start();
        }
    }

    /**
     * @see javax.microedition.lcdui.Canvas#paint(javax.microedition.lcdui.Graphics)
     */
    protected void paint(Graphics graphics) {
        final int width = getWidth();
        final int height = getHeight();
        
        // Set the background color to black
        graphics.setColor(0x000000);
        graphics.fillRect(0, 0, width, height);
        calculateImagePlacement();
        
        if (currentImage != null) {
            graphics.drawImage(currentImage, imagePosX, imagePosY,
                               Graphics.HCENTER | Graphics.VCENTER);
        } 
        else {
            // No image available
            graphics.setColor(0x00f4f4f4);
            graphics.drawString("No image", 20,
                                height / 2 - Font.getDefaultFont().getHeight() / 2,
                                Graphics.HCENTER | Graphics.BASELINE);
        }
    }

    /**
     * @see javax.microedition.lcdui.Canvas#keyPressed(int)
     */
    protected void keyReleased(int keyCode) {
        mainView.displayThis();
    }

    /**
     * @see javax.microedition.lcdui.Canvas#pointerPressed(int, int)
     */
    protected void pointerPressed(int x, int y) {
        currentPointerX = x;
        currentPointerY = y;
    }

    /**
     * @see javax.microedition.lcdui.Canvas#pointerReleased(int, int)
     */
    protected void pointerReleased(int x, int y) {
        deltaPointerX = 0;
        deltaPointerY = 0;
    }

    /**
     * @see javax.microedition.lcdui.Canvas#pointerDragged(int, int)
     */
    protected void pointerDragged(int x, int y) {
        System.out.println("ImageCanvas::pointerDragged(): [" + x + ", " + y + "]");
        deltaPointerX = x - currentPointerX;
        deltaPointerY = y - currentPointerY;
        currentPointerX = x;
        currentPointerY = y;
        repaint();
    }

    /**
     * @see com.nokia.mid.imagescale.ImageScalerListener#scaleFinished(int, int)
     * @param requestId The image scaler request ID.
     * @param result The result code.
     */
    public void scaleFinished(int requestId, int result) {
        final int reqId = requestId;
        
        new Thread(new Runnable() {
            public void run() {
                String imageFilePath = (String) imageTable.get(new Integer(reqId));
                String destinationFilePath =
                    Utils.PHOTOS_DIR + "/" + DESTINATION_FILE_NAME_PREFIX
                    + imageFilePath.substring(imageFilePath.lastIndexOf('/') + 1);
                
                FileConnection fileConnection = null;
                InputStream inputStream = null;
                
                try {
                    fileConnection = (FileConnection) Connector.open(destinationFilePath);
                }
                catch (IOException e) {
                    return;
                }
                
                Image image = null;
                
                if (fileConnection.exists()) {
                    try {
                        inputStream = fileConnection.openInputStream();
                        image = Image.createImage(inputStream);
                    }
                    catch (IOException e) {
                        return;
                    }
                }
                
                currentImage = image;
                currentImageWidth = currentImage.getWidth();
                currentImageHeight = currentImage.getHeight();
                repaint();
                
                try {
                    inputStream.close();
                    fileConnection.close();
                }
                catch (IOException e) {
                }
            }
        }).start();
    }
    

    
    /**
     * @see com.nokia.mid.ui.gestures.GestureListener#gestureAction(
     * java.lang.Object, com.nokia.mid.ui.gestures.GestureInteractiveZone,
     * com.nokia.mid.ui.gestures.GestureEvent)
     */
    public void gestureAction(Object arg0, 
                              GestureInteractiveZone arg1,
                              GestureEvent gestureEvent) 
    {
        switch (gestureEvent.getType()) {
            case GestureInteractiveZone.GESTURE_PINCH:
                Image scaledImage = null;
                int newWidth = currentImageWidth;
                int newHeight = currentImageHeight;
                
                if (gestureEvent.getPinchDistanceChange() == 0) {
                    break;
                }
                else if (gestureEvent.getPinchDistanceChange() < 0) {
                    newWidth *= 0.9;
                    newHeight *= 0.9;
                }
                else if (gestureEvent.getPinchDistanceChange() > 0) {
                    newWidth *= 1.1;
                    newHeight *= 1.1;
                }
                
                scaledImage = Utils.scaleImage(originalImage, newWidth, newHeight);
                
                if (scaledImage != null) {
                    // Image was scaled successfully
                    currentImage = scaledImage;
                    currentImageWidth = newWidth;
                    currentImageHeight = newHeight;
                    repaint();
                }
                
                break;
            default:
                break;
        }
    }

    /**
     * @see javax.microedition.amms.MediaProcessorListener#mediaProcessorUpdate(
     * MediaProcessor, String, Object)
     */
    public void mediaProcessorUpdate(MediaProcessor processor,
                                     String event,
                                     Object eventData)
    {
        if (event.equals(MediaProcessorListener.PROCESSING_ABORTED)) {
            System.out.println("ImageCanvas::mediaProcessorUpdate(): PROCESSING_ABORTED: "
                + eventData.toString());
        }
        else if (event.equals(MediaProcessorListener.PROCESSING_ERROR)) {
            System.out.println("ImageCanvas::mediaProcessorUpdate(): PROCESSING_ERROR: "
                + eventData.toString());
        }
        else if (event.equals(MediaProcessorListener.PROCESSING_STARTED)) {
            System.out.println("ImageCanvas::mediaProcessorUpdate(): PROCESSING_STARTED: "
                + eventData.toString());
        }
        else if (event.equals(MediaProcessorListener.PROCESSING_STOPPED)) {
            System.out.println("ImageCanvas::mediaProcessorUpdate(): PROCESSING_STOPPED: "
                + eventData.toString());
        }
        else if (event.equals(MediaProcessorListener.PROCESSOR_REALIZED)) {
            System.out.println("ImageCanvas::mediaProcessorUpdate(): PROCESSOR_REALIZED: "
                + eventData.toString());
        }
        else if (event.equals(MediaProcessorListener.PROCESSING_COMPLETED)) {
            System.out.println("ImageCanvas::mediaProcessorUpdate(): PROCESSING_COMPLETED: "
                + eventData.toString());
            
            Image resultImage =
                Image.createImage(byteArrayOutputStream.toByteArray(), 0,
                                  byteArrayOutputStream.size());
            
            if (resultImage != null) {
                currentImage = resultImage;
                repaint();
            }
        }
    }

    /**
     * @see com.nokia.mid.ui.orientation.OrientationListener#displayOrientationChanged(int)
     */
    public void displayOrientationChanged(int newDisplayOrientation) {
        Orientation.setAppOrientation(newDisplayOrientation);
    }

    /**
     * Loads an image from the given file path and displays it on this canvas.
     * @param filePath The image file path.
     * @return True if successful, false otherwise.
     */
    public boolean loadImage(String filePath) {
        FileConnection fileConnection = null;
        
        try {
            fileConnection = (FileConnection) Connector.open(filePath, Connector.READ);
        }
        catch (IOException e) {
            return false;
        }
        
        currentImageFilePath = filePath;
        
        if (rgbImageData != null) {
            rgbImageData = null;
        }
        
        int fileSize = 0;
        
        try {
            fileSize = (int) fileConnection.fileSize();
        }
        catch (IOException e) {
            return false;
        }
        
        byte[] imageData = new byte[fileSize];
        
        try {
            InputStream inputStream = fileConnection.openInputStream();
            inputStream.read(imageData, 0, fileSize);
            inputStream.close();
            fileConnection.close();
        }
        catch (IOException e) {
            return false;
        }
        
        currentImage = Image.createImage(imageData, 0, fileSize);
        
        originalImageWidth = currentImageWidth = currentImage.getWidth();
        originalImageHeight = currentImageHeight = currentImage.getHeight();
        
        repaint();
        return true;
    }

    /**
     * Scales the image in the given path using the image scaler API. Note that
     * this method needs to be run in a separate thread to avoid blocking the
     * user interface.
     * @param newWidth The new, target width.
     * @param newHeight The new, target height.
     * @param imageFileName The file path to the image to scale.
     */
    private void scaleImage(final int newWidth, 
                            final int newHeight,
                            final String sourceImageFilePath)
    {
        final String destinationFilePath =
            Utils.PHOTOS_DIR + "/" + DESTINATION_FILE_NAME_PREFIX
            + sourceImageFilePath.substring(sourceImageFilePath.lastIndexOf('/') + 1);
        
        imageScaler = new ImageScaler(sourceImageFilePath, destinationFilePath);
        imageScaler.addListener(this);
        Integer requestId = null;
        
        try {
            requestId = new Integer(imageScaler.scaleImage(newWidth, newHeight, true));
            imageTable.put(requestId, sourceImageFilePath);
        }
        catch (ImageScalerException e) {
            mainView.showMessage(AlertType.ERROR, e.toString());
        }
    }

    /**
     * Reads image data from the given path and creates a monochrome image of
     * the original image.
     * @param imageFilePath The path to the image.
     */
    private void createMonochromeImage(String imageFilePath) {
        MediaProcessor mediaProcessor = null;
        
        try {
            mediaProcessor = GlobalManager.createMediaProcessor(JPEG_TYPE);
        }
        catch (MediaException e) {
            mainView.showMessage(AlertType.ERROR, e.toString());
            return;
        }
        
        mediaProcessor.addMediaProcessorListener(this);
        FileConnection fileConnection = null;
        
        try {
            fileConnection = (FileConnection) Connector.open(imageFilePath, Connector.READ);
        }
        catch (IOException e) {
            return;
        }
        
        if (fileConnection != null && fileConnection.exists()) {
            InputStream inputStream = null;
            
            try {
                inputStream = fileConnection.openInputStream();
            }
            catch (IOException e) {
                return;
            }
            
            try {
                mediaProcessor.setInput(inputStream, MediaProcessor.UNKNOWN);
            }
            catch (MediaException e) {
                mainView.showMessage(AlertType.ERROR, e.toString());
                return;
            }
            
            try {
                inputStream.close();
                fileConnection.close();
            }
            catch (IOException e) {
            }
        }
        
        byteArrayOutputStream = new ByteArrayOutputStream(); 
        mediaProcessor.setOutput(byteArrayOutputStream);
        ImageEffectControl imageEffect =
            (ImageEffectControl) mediaProcessor.getControl(
                "javax.microedition.amms.control.imageeffect.ImageEffectControl");
        imageEffect.setPreset("monochrome");
        imageEffect.setEnabled(true);
        
        try {
            mediaProcessor.start();
        }
        catch (MediaException e) {
            mainView.showMessage(AlertType.ERROR, e.toString());
        }
    }

    /**
     * Calculates the image placement on the screen.
     */
    private void calculateImagePlacement() {
        final int canvasWidth = getWidth();
        final int canvasHeight = getHeight();
        
        if (currentImageWidth > canvasWidth && deltaPointerX != 0) {
            imagePosX += deltaPointerX;
            
            if (imagePosX < (canvasWidth - currentImageWidth / 2)) {
                imagePosX = canvasWidth - currentImageWidth / 2;
            }
            else if (imagePosX > (currentImageWidth / 2)) {
                imagePosX = (currentImageWidth / 2);
            }
        }
        else {
            imagePosX = canvasWidth / 2;
        }
        
        if (currentImageHeight > canvasHeight && deltaPointerY != 0) {
            imagePosY += deltaPointerY;
            
            if (imagePosY < (canvasHeight - currentImageHeight / 2)) {
                imagePosY = canvasHeight - currentImageHeight / 2;
            }
            else if (imagePosY > (currentImageHeight / 2)) {
                imagePosY = (currentImageHeight / 2);
            }
        }
        else {
            imagePosY = canvasHeight / 2;
        }
    }
}