/**
 * Copyright (c) 2011, Martin Foster <martin@ethereal-realms.org>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice, 
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright 
 *       notice, this list of conditions and the following disclaimer in the 
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Ethereal-Realms.org nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.lyra.Widgets;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.PixelGrabber;

import javax.swing.ImageIcon;


/**
 * Contains various static functions that can be employed to perform certain
 * operations on images.
 * <p>
 * @author Martin Foster
 * @version 1.0.1, %Revision, 99%, %LastChangedDate, 17/03/11 8:17PM%
 * @since 1.0.1
 */
public class GraphicsUtilities {

	/**
     * Convenience method that returns a scaled instance of the
     * provided {@code BufferedImage}.
     * <p>
     * @param img the original image to be scaled
     * @param targetWidth the desired width of the scaled instance,
     *    in pixels
     * @param targetHeight the desired height of the scaled instance,
     *    in pixels
     * @param hint one of the rendering hints that corresponds to
     *    {@code RenderingHints.KEY_INTERPOLATION} (e.g.
     *    {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
     *    {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
     *    {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
     * @param higherQuality if true, this method will use a multi-step
     *    scaling technique that provides higher quality than the usual
     *    one-step technique (only useful in down scaling cases, where
     *    {@code targetWidth} or {@code targetHeight} is
     *    smaller than the original dimensions, and generally only when
     *    the {@code BILINEAR} hint is specified)
     * @return a scaled version of the original {@code BufferedImage}
	 * @throws NullPointerException 
     */
    public static BufferedImage getScaledInstance(BufferedImage img,
                                           int targetWidth,
                                           int targetHeight,
                                           Object hint,
                                           boolean higherQuality) throws NullPointerException {

    	// Adjust Transparency
    	int type = (img.getTransparency() == Transparency.OPAQUE) 
    		? BufferedImage.TYPE_INT_RGB 
    		: BufferedImage.TYPE_INT_ARGB;
    	
    	// Create return instance
        BufferedImage ret = (BufferedImage)img;
        int w, h;
        
        /*
         * Use multi-step technique: start with original size, then scale down 
         * in multiple passes with drawImage() until the target size is 
         * reached.
         */
        if (higherQuality) {
 
        	// Initial dimensions set
            w = img.getWidth();
            h = img.getHeight();
        } 
        
        /* 
         * Use one-step technique: scale directly from original size to target 
         * size with a single drawImage() call.
         */
        else {
            
        	// Target dimensions set
            w = targetWidth;
            h = targetHeight;
        }
        
        /*
         * Here we will either loop once and scale to the desired size or
         * alternatively scale multiple times to make a much sharper image.
         */
        do {
        	
        	// Halve the Width
            if (higherQuality && w > targetWidth) {
            	
            	// Halve the dimensions
                w /= 2;
                
                // If target is what we want just set to final size
                if (w < targetWidth)
                    w = targetWidth;
            }

            // Halve the Height
            if (higherQuality && h > targetHeight) {
            	
            	// Halve the dimensions
                h /= 2;
                
                // If target is what we want just set to final size
                if (h < targetHeight)
                    h = targetHeight;
            }

            // Working Image
            BufferedImage tmp = new BufferedImage(w, h, type);
            Graphics2D g2 = tmp.createGraphics();
            
            // Redraw
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
            g2.drawImage(ret, 0, 0, w, h, null);
            g2.dispose();

            // Scale
            ret = tmp;
            
        } while (w != targetWidth || h != targetHeight);

        // Return Scaled Image
        return ret;
    }
    // End of getScaledInstance Method
    
    
	/**
     * Convenience method that returns a scaled instance of the
     * provided {@code BufferedImage}.
     * <p>
     * @param img the original image to be scaled
     * @param targetWidth the desired width of the scaled instance,
     *    in pixels
     * @param targetHeight the desired height of the scaled instance,
     *    in pixels
     * @param hint one of the rendering hints that corresponds to
     *    {@code RenderingHints.KEY_INTERPOLATION} (e.g.
     *    {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
     *    {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
     *    {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
     * @param higherQuality if true, this method will use a multi-step
     *    scaling technique that provides higher quality than the usual
     *    one-step technique (only useful in down scaling cases, where
     *    {@code targetWidth} or {@code targetHeight} is
     *    smaller than the original dimensions, and generally only when
     *    the {@code BILINEAR} hint is specified)
     * @return a scaled version of the original {@code BufferedImage}
	 * @throws NullPointerException 
     */
    public static Image getScaledInstance(Image img,
                                          int targetWidth,
                                          int targetHeight,
                                          Object hint,
                                          boolean higherQuality) throws NullPointerException{
    	
    	// Straight forward pass-through
    	return (Image)getScaledInstance(
    		toBufferedImage(img),
    		targetWidth,
    		targetHeight,
    		hint,
    		higherQuality
    	  );
    }
    // End of getScaledInstance Method
    
	
    /**
     * This method will take an instance of Image and convert it to an instance
     * of BufferedImage.  That in turn can be used in our scaled Instance
     * handler.
     * <p>
     * @param image Instance of image.
     * @return Instance of BufferedImage.
     * @throws NullPointerException 
     */
    public static BufferedImage toBufferedImage(Image image) throws NullPointerException{
    	
    	// Return cast of instance if applicable
        if (image instanceof BufferedImage)
            return (BufferedImage)image;


        // This code ensures that all the pixels in the image are loaded
        image = new ImageIcon(image).getImage();

        // Determine if the image has transparent pixels
        boolean hasAlpha = hasAlpha(image);

        // Create a buffered image with a format that's compatible with the screen
        BufferedImage bimage = null;
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        try {
            // Determine the type of transparency of the new buffered image
            int transparency = Transparency.OPAQUE;
            if (hasAlpha) {
                transparency = Transparency.BITMASK;
            }

            // Create the buffered image
            GraphicsDevice gs = ge.getDefaultScreenDevice();
            GraphicsConfiguration gc = gs.getDefaultConfiguration();
            bimage = gc.createCompatibleImage(
                image.getWidth(null), image.getHeight(null), transparency);
        } 
        
        // The system does not have a screen
        catch (HeadlessException e) {}

        // If we have not been created already try this method
        if (bimage == null) {
        	
            // Determine the colour model
            int type = (hasAlpha) 
               ? type = BufferedImage.TYPE_INT_ARGB
               : BufferedImage.TYPE_INT_RGB;
            
            // Create buffered image
            bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
        }

        // Copy image to buffered image
        Graphics g = bimage.getGraphics();

        // Paint the image onto the buffered image
        g.drawImage(image, 0, 0, null);
        g.dispose();

        // Return Instance
        return bimage;
    }
    // End of toBufferedImage Method
    

    /**
     * When provided with an instance of Image will determine if there are
     * transparency pixels in place.  If this is the case, we will return 
     * true to indicate as such.
     * <p>
     * @param image Instance of the image.
     * @return Boolean indicating alpha/non-alpha information.
     * @throws NullPointerException 
     */
    public static boolean hasAlpha(Image image) throws NullPointerException {
    	
        // If buffered image, the colour model is readily available
        if (image instanceof BufferedImage) {
        	
        	// Cast and pass through
            BufferedImage bimage = (BufferedImage)image;
            return bimage.getColorModel().hasAlpha();
        }

        /* 
         * Use a pixel grabber to retrieve the image's colour model;
         * grabbing a single pixel is usually sufficient
         */
         PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
         try {
            pg.grabPixels();
         } catch (InterruptedException e) {}

        // Get the image's colour model
        ColorModel cm = pg.getColorModel();
        return cm.hasAlpha();
    }
    // End of hasAlpha Method
}
