ImageUtils.java

/**
 * Copyright (c) 2012-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 text file delivered with this project for more information.
 */

package com.nokia.example.statusshout.ui;

import java.io.IOException;
import java.io.InputStream;

import com.nokia.mid.ui.DirectUtils;

import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Image;

/**
 * Utilities for handling images.
 */
public class ImageUtils {
    // Constants
    private static final String TAG = "ImageUtils.";
    private static final int CHUNK_SIZE = 1024;

    /**
     * Loads an image from the phone and returns it. Note that you usually need
     * to call this method from a separate thread in order not to block the UI.
     * 
     * @param imageUri The image URI.
     * @return The loaded image or null in case of an error.
     */
    public static Image loadImageFromPhone(final String imageUri) {
        System.out.println(TAG + "loadImageFromPhone(): " + imageUri);
        Image image = null;
        
        try {
            FileConnection fileConnection = (FileConnection)Connector.open(imageUri);
            InputStream in = fileConnection.openInputStream();
            
            if (in != null) {
                image = Image.createImage(in);
                in.close();
            }
            
            fileConnection.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        
        return image;
    }

    /**
     * 
     * @param imageUri
     * @return
     */
    public static byte[] localImageToByteArray(final String imageUri) {
        System.out.println(TAG + "localImageToByteArray(): " + imageUri);
        byte[] imageData = null;
        
        try {
            FileConnection fileConnection = (FileConnection)Connector.open(imageUri);
            InputStream in = fileConnection.openInputStream();
            
            if (in != null) {
                long length = 0;
                long overallSize = fileConnection.fileSize();
                System.out.println(TAG + "localImageToByteArray(): File size is " + overallSize);
                
                byte[] nextChunk = new byte[CHUNK_SIZE];
                imageData = new byte[0];
                
                while (length < overallSize) {
                      int readAmount = in.read(nextChunk, 0, CHUNK_SIZE);
                      byte[] temp = new byte[imageData.length + readAmount];
                      System.arraycopy(imageData, 0, temp, 0, (int)length);
                      System.arraycopy(nextChunk, 0, temp, (int)length, readAmount);
                      imageData = temp;
                      length += readAmount;
                }
                
                in.close();
            }
            
            fileConnection.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        
        return imageData; 
    }

    /**
     * Sets the alpha of the each pixel in the image based on the given value.
     * 
     * @param image The original image.
     * @param alpha The alpha value.
     * @return A newly created image with applied alpha.
     * @throws NullPointerException If the given image is null.
     * @throws IllegalArgumentException If the alpha value is not in [0, 255].
     */
    public static Image setAlpha(final Image image, final int alpha)
       throws NullPointerException, IllegalArgumentException
    {
        if (image == null) {
            throw new NullPointerException();
        }
        
        if (alpha < 0 || alpha > 255) {
            throw new IllegalArgumentException();
        }
        
        final int width = image.getWidth();
        final int height = image.getHeight();
        final int[] originalRgb = new int[width * height];
        image.getRGB(originalRgb, 0, width, 0, 0, width, height);
        
        final int opaqueRgb[] = new int[width * height];
        
        for (int x = 0; x < width; ++x) {
            for (int y = 0; y < height; ++y) {
                if (originalRgb[(width * y) + x] >>> 24 == 255) {
                    opaqueRgb[(width * y) + x] =
                        originalRgb[(width * y) + x] + (alpha << 24);
                }
                else {
                    opaqueRgb[(width * y) + x] = originalRgb[(width * y) + x];
                }
            }
        }
        
        return Image.createRGBImage(opaqueRgb, width, height, true);
    }

    /**
     * Subtracts the given RGB values from the given image while maintaining
     * the alpha level of each pixel.
     * 
     * @param image The original image.
     * @param r Red value to subtract.
     * @param g Green value to subtract.
     * @param b Blue value to subtract.
     * @return The modified image.
     * @throws NullPointerException If the given image is null.
     * @throws IllegalArgumentException If any of RGB values is invalid.
     */
    public static Image substractRgb(final Image image,
                                     final int r,
                                     final int g,
                                     final int b)
       throws NullPointerException, IllegalArgumentException
    {
        if (image == null) {
            throw new NullPointerException();
        }
        
        if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
            throw new IllegalArgumentException();
        }
        
        final int[] rgbToSubstract = new int[3];
        rgbToSubstract[0] = r;
        rgbToSubstract[1] = g;
        rgbToSubstract[2] = b;
        
        final int width = image.getWidth();
        final int height = image.getHeight();
        final int[] originalRgb = new int[width * height];
        image.getRGB(originalRgb, 0, width, 0, 0, width, height);
        
        final int newRgb[] = new int[width * height];
        int pixel = 0;
        int[] argb = new int[4];
        
        for (int x = 0; x < width; ++x) {
            for (int y = 0; y < height; ++y) {
                pixel = originalRgb[(width * y) + x];
                argb[0] = (pixel & 0xff000000) >>> 24;
                argb[1] = (pixel & 0x00ff0000) >>> 16;
                argb[2] = (pixel & 0x0000ff00) >>> 8;
                argb[3] = pixel & 0x000000ff;
                
                for (int i = 0; i < 3; ++i) {
                    if (argb[i + 1] - rgbToSubstract[i] >= 0) {
                        argb[i + 1] -= rgbToSubstract[i];
                    }
                    else {
                        argb[i + 1] = 0;
                    }
                }
                    
                newRgb[(width * y) + x] =
                    (argb[0] << 24) | (argb[1] << 16) | (argb[2] << 8) | argb[3];
            }
        }
        
        return Image.createRGBImage(newRgb, width, height, true);
    }

    /**
     * This method takes an image and overlays all the visible pixels with the
     * specified color. This is useful for single color icons that should be
     * colored according to the theme of the device.
     *
     * @param image The original image.
     * @return The resulting image.
     */
    public static Image drawMaskedImage(Image image, Display display) {
        final int color = display.getColor(Display.COLOR_HIGHLIGHTED_BORDER);
        
        // Store the pixel array of the image
        final int[] sourceData = new int[image.getHeight() * image.getWidth()];
        
        image.getRGB(
                sourceData, 
                0, 
                image.getWidth(), 
                0, 
                0, 
                image.getWidth(),
                image.getHeight());
        
        // Overlay non-transparent pixels with the specified color
        for (int i = 0; i < sourceData.length; i++) {
            sourceData[i] = 
                (sourceData[i] & 0xFF000000) | (color & 0x00FFFFFF);
        }
        
        // Create the new image
        final Image overlayed =
            DirectUtils.createImage(
                image.getWidth(), 
                image.getHeight(), 
                0x000000);
        
        overlayed.getGraphics().drawRGB(
                sourceData, 
                0, 
                image.getWidth(), 
                0, 
                0,
                image.getWidth(), 
                image.getHeight(), 
                true);
        
        return overlayed;
    }

    /**
     * Scales the given image.
     * 
     * @param image The original image.
     * @param newWidth The new image width.
     * @param newHeight The new image height.
     * @return The scaled image.
     */
    public static Image scale(final Image image,
                              final int newWidth,
                              final int newHeight)
    {
        if (newWidth <= 0 || newHeight <= 0 || image == null) {
            throw new IllegalArgumentException(
                "Invalid width or height or the given image is null!");
        }
        
        // Get the size and the RGB array of the original image
        final int sourceWidth = image.getWidth();
        final int sourceHeight = image.getHeight();
        final int[] originalRgb = new int[sourceWidth * sourceHeight];
        image.getRGB(originalRgb, 0, sourceWidth, 0, 0, sourceWidth, sourceHeight);
        
        final int scaledRgb[] = new int[newWidth * newHeight];
        
        for (int y = 0; y < newHeight; y++) {
            final int dy = y * sourceHeight / newHeight;
            
            for (int x = 0; x < newWidth; x++) {
                final int dx = x * sourceWidth / newWidth;
                scaledRgb[(newWidth * y) + x] = originalRgb[(sourceWidth * dy) + dx];
            }
        }
        
        return Image.createRGBImage(scaledRgb, newWidth, newHeight, true);
    }

    /**
     * Scales the given image using pixel mixing scaling algorithm.
     * 
     * @param originalImage The original image instance.
     * @param newWidth The new image width.
     * @param newHeight The new image height.
     * @return The scaled image.
     */
    public static Image pixelMixingScale(final Image originalImage,
                                         final int newWidth,
                                         final int newHeight)
    {
        if (newWidth <= 0 || newHeight <= 0 || originalImage == null) {
            throw new IllegalArgumentException(
                    "Invalid width or height or the given image is null!");
        }
        
        final int sourceWidth = originalImage.getWidth();
        final int sourceHeight = originalImage.getHeight();
        final int[] originalRgb = new int[sourceHeight * sourceWidth];
        originalImage.getRGB(originalRgb, 0, sourceWidth, 0, 0, sourceWidth, sourceHeight);
        
        final int[] scaledRgb = new int[newWidth * newHeight];
        
        final int oWidth = sourceWidth;
        final int[] oX16 = new int[newWidth + 1];
        
        for (int newX = 0; newX <= newWidth; newX++) {
            oX16[newX] = ((newX * oWidth) << 4) / newWidth;
        }
        
        final int[] oXStartWidth = new int[newWidth];
        final int[] oXEndWidth = new int[newWidth];
        
        for (int newX = 0; newX < newWidth; newX++) {
            oXStartWidth[newX] = 16 - (oX16[newX] % 16);
            oXEndWidth[newX] = oX16[newX + 1] % 16;
        }
        
        final int oHeight = sourceHeight;
        final int[] oY16 = new int[newHeight + 1];
        
        for (int newY = 0; newY <= newHeight; newY++) {
            oY16[newY] = ((newY * oHeight) << 4) / newHeight;
        }
        
        for (int newY = 0; newY < newHeight; newY++) {
            final int oY16Start = oY16[newY];
            final int oY16End = oY16[newY + 1];
            final int oYStart = oY16Start >>> 4;
            final int oYEnd = oY16End >>> 4;
            final int oYStartHeight = 16 - (oY16Start % 16);
            final int oYEndHeight = oY16End % 16;
            
            for (int newX = 0; newX < newWidth; newX++) {
                final int oX16Start = oX16[newX];
                final int oX16End = oX16[newX + 1];
                final int oXStart = oX16Start >>> 4;
                final int oXEnd = oX16End >>> 4;
                int outArea = 0;
                int outColorArea = 0;
                int outAlpha = 0;
                int outRed = 0;
                int outGreen = 0;
                int outBlue = 0;
                
                for (int j = oYStart; j <= oYEnd; j++) {
                    final int areaHeight;
                    
                    if (oYStart == oYEnd) {
                        areaHeight = oY16End - oY16Start;
                    }
                    else if (j == oYStart) {
                        areaHeight = oYStartHeight;
                    }
                    else if (j == oYEnd) {
                        areaHeight = oYEndHeight;
                    }
                    else {
                        areaHeight = 16;
                    }
                    
                    if (areaHeight == 0) {
                        continue;
                    }
                    
                    for (int i = oXStart; i <= oXEnd; i++) {
                        final int areaWidth;
                        
                        if (oXStart == oXEnd) {
                            areaWidth = oX16End - oX16Start;
                        }
                        else if (i == oXStart) {
                            areaWidth = oXStartWidth[newX];
                        }
                        else if (i == oXEnd) {
                            areaWidth = oXEndWidth[newX];
                        }
                        else {
                            areaWidth = 16;
                        }
                        
                        if (areaWidth == 0) {
                            continue;
                        }
                        
                        int area = areaWidth * areaHeight;
                        outArea += area;
                        int argb = originalRgb[i + j * sourceWidth];
                        int a = (argb >>> 24);
                        
                        if (a == 0) {
                            continue;
                        }
                        
                        area = a * area;
                        outColorArea += area;
                        final int r = (argb & 0x00ff0000) >>> 16;
                        final int g = (argb & 0x0000ff00) >>> 8;
                        final int b = argb & 0x000000ff;
                        outRed += area * r;
                        outGreen += area * g;
                        outBlue += area * b;
                    }
                }
                
                if (outColorArea > 0) {
                    outAlpha = outColorArea / outArea;
                    outRed = outRed / outColorArea;
                    outGreen = outGreen / outColorArea;
                    outBlue = outBlue / outColorArea;
                }
                
                scaledRgb[newX + newY * newWidth] = (outAlpha << 24)
                        | (outRed << 16) | (outGreen << 8) | outBlue;
            }
        }
        
        return Image.createRGBImage(scaledRgb, newWidth, newHeight, true);
    }
}