ImageLoader.java

/*
 * Copyright © 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.attractions.utils;

import com.nokia.example.attractions.Main;
import com.nokia.example.attractions.network.ImageOperation;
import com.nokia.example.attractions.Visual;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Hashtable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

/**
 * Utility for loading image resources.
 */
public final class ImageLoader {

    private static ImageLoader self;
    private static final Hashtable imageCache = new Hashtable();

    /**
     * An interface for notifying that an image has been loaded.
     */
    public interface Listener {

        void imageLoaded(Image image);
    }

    private ImageLoader() {
    }

    /**
     * @return ImageLoader singleton
     */
    public static ImageLoader getInstance() {
        if (self == null) {
            self = new ImageLoader();
        }
        return self;
    }

    /**
     * Loads an image from resources and returns it.
     * Caches all loaded images in hopes of saving some memory.
     * @param imagePath
     * @return loaded image
     * @throws IOException
     */
    public final Image loadImage(final String imagePath, final Hashtable cache)
        throws IOException {
        Image image = getImageFromCache(imagePath, cache);
        if (image == null) {
            InputStream in = this.getClass().getResourceAsStream(imagePath);
            if (in == null) {
                throw new IOException("Image not found.");
            }
            image = Image.createImage(in);
            cacheImage(imagePath, image, cache, false);
        }
        return image;
    }

    /**
     * Loads an image from resources or network.
     * Caches all loaded images.
     * @param url
     * @param defaultImage a default image which is returned while the image is
     * loaded from network
     * @param listener listener which is notified when the image is loaded from
     * network
     * @return the image or while the image is loaded from the network the
     * default image
     */
    public final Image loadImage(final String url, final Image defaultImage,
        final Listener listener, final Hashtable cache) {
        try {
            return loadImage(url, cache);
        }
        catch (IOException e) {
            cacheImage(url, defaultImage, cache, false);
            new ImageOperation(new ImageOperation.Listener() {

                public void imageReceived(String url, byte[] data) {
                    Image image = defaultImage;
                    try {
                        image = Image.createImage(data, 0, data.length);
                    }
                    catch (IllegalArgumentException e) {
                    }
                    catch (NullPointerException e) {
                    }
                    cacheImage(url, image, cache, true);
                    listener.imageLoaded(image);
                }
            }, url).start();
        }
        return defaultImage;
    }

    private void cacheImage(String key, Image image, Hashtable cache,
        boolean toWeakCache) {
        if (cache != null) {
            cache.put(key, image);
        }
        if (toWeakCache) {
            imageCache.put(key, new WeakReference(image));
        }
    }

    private Image getImageFromCache(String key, Hashtable cache) {
        Image image = null;
        if (cache != null) {
            image = (Image) cache.get(key);
        }
        if (image == null) {
            WeakReference imageRef = (WeakReference) imageCache.get(key);
            image = imageRef == null ? null : (Image) imageRef.get();
            if (image != null && cache != null) {
                cache.put(key, image);
            }
        }
        if (image == null) {
            imageCache.remove(key);
        }

        return image;
    }

    /**
     * Creates a map marker.
     * @param id text which is shown in the marker
     * @param imagePath
     * @return map marker image
     * @throws IOException
     */
    public final Image loadMapMarker(final String id, final String imagePath,
        final Hashtable cache)
        throws IOException {
        Image background = loadImage(imagePath, cache);
        String url = "" + id + imagePath;
        Image image = null;
        if (cache != null) {
            image = (Image) cache.get(url);
        }
        if (image == null) {
            int w = background.getWidth();
            int h = background.getHeight();
            image = Image.createImage(w, h);
            Graphics g = image.getGraphics();
            g.setColor(0xffffff);
            g.fillRect(0, 0, w, h);
            g.setColor(Visual.MAP_MARKER_COLOR);
            g.setFont(Visual.MAP_MARKER_FONT);
            g.drawString(id, w / 2, h / 2 + g.getFont().getHeight() / 2 - (Main.
                isS60Phone() ? 3 : 5),
                Graphics.BOTTOM | Graphics.HCENTER);
            image = multiplyImages(background, image);
            if (cache != null) {
                cache.put(url, image);
            }
        }
        return image;
    }

    private static Image multiplyImages(Image img1, Image img2) {
        if (img1.getWidth() != img2.getWidth() || img1.getHeight()
            != img2.getHeight()) {
            throw new IllegalArgumentException(
                "Sizes of the images must be same");
        }
        int[] rawImg1 = new int[img1.getHeight() * img1.getWidth()];
        img1.getRGB(rawImg1, 0, img1.getWidth(), 0, 0,
            img1.getWidth(), img1.getHeight());
        int[] rawImg2 = new int[img2.getHeight() * img2.getWidth()];
        img2.getRGB(rawImg2, 0, img2.getWidth(), 0, 0, img2.getWidth(), img2.
            getHeight());

        int mrgb, mr, mg, mb, a, r, g, b;
        for (int i = 0, l = rawImg1.length; i < l; i++) {
            mrgb = rawImg2[i] & 0xffffff;
            if (mrgb < 0xffffff) {
                mr = mrgb >>> 16;
                mg = (mrgb & 0xff00) >>> 8;
                mb = mrgb & 0xff;
                a = rawImg1[i] & 0xff000000;
                r = (((rawImg1[i] & 0xff0000) * mr) / 0xff) & 0xff0000;
                g = (((rawImg1[i] & 0xff00) * mg) / 0xff) & 0xff00;
                b = (((rawImg1[i] & 0xff) * mb) / 0xff) & 0xff;
                rawImg1[i] = a | r | g | b;
            }
        }

        return Image.createRGBImage(rawImg1, img1.getWidth(),
            img1.getHeight(), true);
    }

    /**
     * Scales an image.
     * @param original
     * @param newWidth
     * @param newHeight
     * @return new scaled image
     */
    public static Image scaleImage(Image original, int newWidth,
        int newHeight) {
        int[] rawInput = new int[original.getHeight() * original.getWidth()];
        original.getRGB(rawInput, 0, original.getWidth(), 0, 0, original.
            getWidth(), original.getHeight());

        int[] rawOutput = new int[newWidth * newHeight];

        // YD compensates for the x loop by subtracting the width back out
        int YD = (original.getHeight() / newHeight) * original.getWidth()
            - original.getWidth();
        int YR = original.getHeight() % newHeight;
        int XD = original.getWidth() / newWidth;
        int XR = original.getWidth() % newWidth;
        int outOffset = 0;
        int inOffset = 0;

        for (int y = newHeight, YE = 0; y > 0; y--) {
            for (int x = newWidth, XE = 0; x > 0; x--) {
                rawOutput[outOffset++] = rawInput[inOffset];
                inOffset += XD;
                XE += XR;
                if (XE >= newWidth) {
                    XE -= newWidth;
                    inOffset++;
                }
            }
            inOffset += YD;
            YE += YR;
            if (YE >= newHeight) {
                YE -= newHeight;
                inOffset += original.getWidth();
            }
        }
        return Image.createRGBImage(rawOutput, newWidth, newHeight, true);
    }
}