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