/*
 * @(#)Images.java  2.2  2008-04-19
 *
 * Copyright (c) 2005-2008 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */

package org.monte.media.image;

import java.awt.*;
import java.awt.image.*;
import java.net.*;

import javax.swing.*;

/**
 * Image processing methods.
 *
 * @author  Werner Randelshofer, Karl von Randow
 * @version 2.2 2008-04-19 Create graphiteFilter lazily. 
 * <br>2.1 2007-07-25 Added method toBufferedImage(RenderedImage).
 * <br>2.0 2006-12-24 by Karl von Randow: On the fly conversion from Aqua
 * Blue to Aqua Graphite appearance added.
 * <br>1.0.2 2005-09-12 Brought my work-around for Java 1.4.1 back to
 * live.
 * <br>1.0.1 2005-05-21 Accidentaly used bitmask transparency
 * instead of translucent transparency.
 * <br>1.0  13 March 2005  Created.
 */
public class Images {
    
    /** Prevent instance creation. */
    private Images() {
    }
    /*
    private static GraphiteFilter graphiteFilter;
    
    private static GraphiteFilter getGraphiteFilter() {
        if (graphiteFilter == null) {
            graphiteFilter = new GraphiteFilter();
        }
        return graphiteFilter;
    }*/
    
    public static Image createImage(Class baseClass, String location) {
        URL resource = baseClass.getResource(location);
        if (resource == null) {
            System.err.println("Warning: Images.createImage no resource found for "+baseClass+" "+location);
            return null;
        }
        return createImage(resource);
    }
    public static Image createImage(URL resource) {
        Image image = Toolkit.getDefaultToolkit().createImage(resource);
        /*
        if (Preferences.getString("AppleAquaColorVariant").equals("6")) {
            if (canGraphite(resource)) {
                image = toGraphite(image);
            }
        }*/
        return image;
    }

    /*
    private static Properties canGraphite;
    
    private static boolean canGraphite(URL resource) {
        if (canGraphite == null) {
            synchronized (Images.class) {
                if (canGraphite == null) {
                    Properties p = new Properties();
                    try {
                        p.load(Images.class.getResourceAsStream("graphiteable.properties"));
                    } catch (IOException e) {
                        System.err.println("Failed to load graphiteable.properties: " + e);
                    }
                    canGraphite = p;
                }
            }
        }
        String file = resource.getFile();
        int i = file.lastIndexOf(File.separatorChar);
        if (i != -1) {
            file = file.substring(i + 1);
        }
        return canGraphite.containsKey(file);
    }
    
    /**
     * This method returns a buffered image with the contents of an image.
     *
     * Code derived from the Java Developers Almanac 1.4
     * http://javaalmanac.com/egs/java.awt.image/Image2Buf.html?l=rel
     * /
    private static Image toGraphite(Image image) {
        return Toolkit.getDefaultToolkit().
                createImage(new FilteredImageSource(image.getSource(), getGraphiteFilter()));
    }*/
    
    /**
     * Based on a code example from:
     * http://tams-www.informatik.uni-hamburg.de/applets/hades/webdemos/00-intro/02-imageprocessing/saturation.html
     * author karlvr
     *
     * /
    public static class GraphiteFilter extends RGBImageFilter {
        private final static float saturationAdjust = 0.179f;
        private static float hueAdjust = 0.0052f;
        private static float brightnessAdjust = 0.09f;
        
        
        private float[] hsb = new float[3];
        
        public int filterRGB(int x, int y, int rgb) {
            int alpha = rgb & 0xff000000;
            int red = (rgb >> 16) & 0xff;
            int green = (rgb >> 8) & 0xff;
            int blue = rgb & 0xff;
            /*
            float RW = (1f - saturationAdjust) * 0.3086f; // or 0.299 for YIV values
            float RG = (1f - saturationAdjust) * 0.6084f; // or 0.587 for YIV values
            float RB = (1f - saturationAdjust) * 0.0820f; // or 0.114 for YIV values
             * /
            float RW = (1f - saturationAdjust) * 0.333f; // or 0.299 for YIV values
            float RG = (1f - saturationAdjust) * 0.333f; // or 0.587 for YIV values
            float RB = (1f - saturationAdjust) * 0.333f; // or 0.114 for YIV values
            
            float a = RW + saturationAdjust;
            float b = RW;
            float c = RW;
            float d = RG;
            float e = RG + saturationAdjust;
            float f = RG;
            float g = RB;
            float h = RB;
            float i = RB + saturationAdjust;
            
            int outputRed   = (int) (a*red + d*green + g*blue);
            int outputGreen = (int) (b*red + e*green + h*blue);
            int outputBlue  = (int) (c*red + f*green + i*blue);
            return alpha | (outputRed << 16) | (outputGreen << 8) | (outputBlue);
        }
    }*/

    /** Converts the image to a buffered image. */
    public static BufferedImage toBufferedImage(RenderedImage rImg) {
        BufferedImage image;
        if (rImg instanceof BufferedImage) {
            image = (BufferedImage) rImg;
        } else {
            Raster r = rImg.getData();
            WritableRaster wr = WritableRaster.createWritableRaster(
                    r.getSampleModel(), null);
            rImg.copyData(wr);
            image = new BufferedImage(
                    rImg.getColorModel(),
                    wr,
                    rImg.getColorModel().isAlphaPremultiplied(),
                    null
                    );
        }
        return image;
    }
    
    public static BufferedImage toBufferedImage(Image image) {
        if (image instanceof BufferedImage) {
            return (BufferedImage)image;
        }
        
        // This code ensures that all the pixels in the image are loaded
        image = new ImageIcon(image).getImage();
        
        // Create a buffered image with a format that's compatible with the screen
        BufferedImage bimage = null;
        
        if (System.getProperty("java.version").startsWith("1.4.1_")) {
            // Workaround for Java 1.4.1 on Mac OS X.
            // For this JVM, we always create an ARGB image to prevent a class
            // cast exception in
            // sun.awt.image.BufImgSurfaceData.createData(BufImgSurfaceData.java:434)
            // when we attempt to draw the buffered image.
            bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
        } else {
            // Determine if the image has transparent pixels; for this method's
            // implementation, see e661 Determining If an Image Has Transparent Pixels
            boolean hasAlpha;
            try {
                hasAlpha = hasAlpha(image);
            } catch (IllegalAccessError e) {
                // If we can't determine this, we assume that we have an alpha,
                // in order not to loose data.
                hasAlpha = true;
            }
            
            
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            try {
                // Determine the type of transparency of the new buffered image
                int transparency = Transparency.OPAQUE;
                if (hasAlpha) {
                    transparency = Transparency.TRANSLUCENT;
                }
                
                // Create the buffered image
                GraphicsDevice gs = ge.getDefaultScreenDevice();
                GraphicsConfiguration gc = gs.getDefaultConfiguration();
                bimage = gc.createCompatibleImage(
                        image.getWidth(null), image.getHeight(null), transparency);
            } catch (Exception e) {
                //} catch (HeadlessException e) {
                // The system does not have a screen
            }
            
            if (bimage == null) {
                // Create a buffered image using the default color model
                int type = BufferedImage.TYPE_INT_RGB;
                if (hasAlpha) {
                    type = BufferedImage.TYPE_INT_ARGB;
                }
                bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
            }
        }
        
        // Copy image to buffered image
        Graphics g = bimage.createGraphics();
        
        // Paint the image onto the buffered image
        g.drawImage(image, 0, 0, null);
        g.dispose();
        
        return bimage;
        
        // My own implementation:
        /*
        if (image instanceof BufferedImage) {
            return (BufferedImage) image;
        } else {
            BufferedImage bufImg;
            Frame f = new Frame();
            f.pack();
            MediaTracker t = new MediaTracker(f);
            t.addImage(image, 0);
            try { t.waitForAll(); } catch (InterruptedException e) {}
         
            // Workaround for Java 1.4.1 on Mac OS X.
            if (System.getProperty("java.version").startsWith("1.4.1_")) {
                bufImg = new BufferedImage(image.getWidth(f), image.getHeight(f), BufferedImage.TYPE_INT_ARGB);
            } else {
                bufImg = GraphicsEnvironment
                .getLocalGraphicsEnvironment()
                .getDefaultScreenDevice()
                .getDefaultConfiguration()
                .createCompatibleImage(image.getWidth(null), image.getHeight(null), Transparency.TRANSLUCENT);
            }
            Graphics2D imgGraphics = bufImg.createGraphics();
            imgGraphics.drawImage(image, 0, 0, f);
            imgGraphics.dispose();
            f.dispose();
            return bufImg;
        }*/
    }
    
    /**
     * This method returns true if the specified image has transparent pixels
     *
     * Code taken from the Java Developers Almanac 1.4
     * http://javaalmanac.com/egs/java.awt.image/HasAlpha.html
     */
    public static boolean hasAlpha(Image image) {
        // If buffered image, the color model is readily available
        if (image instanceof BufferedImage) {
            BufferedImage bimage = (BufferedImage)image;
            return bimage.getColorModel().hasAlpha();
        }
        
        // Use a pixel grabber to retrieve the image's color 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 color model
        ColorModel cm = pg.getColorModel();
        return cm.hasAlpha();
    }
    
    /**
     * Splits an image into count subimages.
     */
    public static BufferedImage[] split(Image image, int count, boolean isHorizontal) {
        BufferedImage src = Images.toBufferedImage(image);
        if (count == 1) {
            return new BufferedImage[] { src };
        }
        
        BufferedImage[] parts = new BufferedImage[count];
        for (int i=0; i < count; i++) {
            if (isHorizontal) {
                parts[i] = src.getSubimage(
                        src.getWidth() / count * i, 0,
                        src.getWidth() / count, src.getHeight()
                        );
            } else {
                parts[i] = src.getSubimage(
                        0, src.getHeight() / count * i,
                        src.getWidth(), src.getHeight() / count
                        );
            }
        }
        return parts;
    }
        /** Converts the image into a format that can be handled easier. */
    public static BufferedImage toIntImage(BufferedImage img) {
        if (img.getRaster().getDataBuffer() instanceof DataBufferInt) {
            return img;
        } else {
            BufferedImage intImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
            Graphics2D g = intImg.createGraphics();
            g.drawImage(img, 0, 0, null);
            g.dispose();
            return intImg;
        }
    }
    
    /** Converts an image into an array of integer pixels.
     * If the image has an integer data buffer, the internal pixel array is returned.
     */
    public static int[] toPixels(BufferedImage img) {
       return ((DataBufferInt) toIntImage(img).getRaster().getDataBuffer()).getData();
    }
    /** Converts an array of integer pixels into an image. 
     * The array is referenced by the image.
     */
    public static BufferedImage toImage(int[] pixels, int width, int height) {
       return new BufferedImage(DirectColorModel.getRGBdefault(),//
               Raster.createWritableRaster(new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT,width,height,
                       new int[]{0xff00000,0xff00,0xff}),//
               new DataBufferInt(pixels, width*height),new Point(0,0)),
               false, null
               );
    }
}
