/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.ext.awt.image.rendered;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;

import org.apache.batik.ext.awt.color.ICCColorSpaceExt;


/**
 * This implementation of rendered image forces a color profile
 * on its source
 *
 * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
 * @version $Id: ProfileRed.java,v 1.6 2003/04/11 13:58:00 vhardy Exp $
 */
public class ProfileRed extends AbstractRed {
    private static final ColorSpace sRGBCS 
        = ColorSpace.getInstance(ColorSpace.CS_sRGB);
    private static final ColorModel sRGBCM 
        = new DirectColorModel(sRGBCS, 
                               32, 
                               0x00ff0000,
                               0x0000ff00,
                               0x000000ff,
                               0xff000000,
                               false,
                               DataBuffer.TYPE_INT);

    private ICCColorSpaceExt colorSpace;

    /**
     * @param src Images on which the input ColorSpace should
     *        be forced
     * @param colorSpace colorSpace that should be forced on the
     *        source
     */
    public ProfileRed(CachableRed src,
                      ICCColorSpaceExt colorSpace){
        this.colorSpace = colorSpace;

        init(src, src.getBounds(), 
             sRGBCM, sRGBCM.createCompatibleSampleModel(src.getWidth(),
                                                        src.getHeight()),
             src.getTileGridXOffset(), src.getTileGridYOffset(), null);
             
    }

    public CachableRed getSource() {
        return (CachableRed)getSources().get(0);
    }

    /**
     * This method will turn the input image in an sRGB image as follows.
     * If there is no colorSpace defined, then the input image is 
     * simply converted to singlePixelPacked sRGB if needed. 
     * If there is a colorSpace defined, the the image data is 'interpreted'
     * as being in that space, instead of that of the image's colorSpace.
     * 
     * Here is how the input image is processed: 
     * a. It is converted to using a ComponentColorModel
     * b. Its data is extracted, ignoring it's ColorSpace
     * c. A new ComponentColorModel is built for the replacing colorSpace
     *    Note that if the number of components in the input image and 
     *    the number of components in the replacing ColorSpace do not 
     *    match, it is not possible to apply the conversion.
     * d. A new BufferedImage is built, using the new
     *    ComponentColorModel and the data from the original image
     *    converted to the ComponentColorModel built in a. The alpha
     *    channel is excluded from that new BufferedImage.
     * e. The BufferedImage created in d. is converted to sRGB using
     *    ColorConvertOp
     * f. The alpha channel information is integrated back into the image.
     *
     * IMPORTANT NOTE: The code uses a BandedSampleModel in c.) and
     * d.) and discard the alpha channel during the color conversions
     * (it is restored in f.)), because of bugs in the interleaved
     * model with alpha. The BandedSampleModel did not cause any bug
     * as of JDK 1.3.  
     */
    public WritableRaster copyData(WritableRaster argbWR){
        try{
            RenderedImage img = getSource();

            /**
             * Check that the number of color components match in the input
             * image and in the replacing profile.
             */
            ColorModel imgCM = img.getColorModel();
            ColorSpace imgCS = imgCM.getColorSpace();
            int nImageComponents = imgCS.getNumComponents();
            int nProfileComponents = colorSpace.getNumComponents();
            if(nImageComponents != nProfileComponents){
                // Should we go in error???? Here we simply trace an error
                // and return null
                System.err.println("Input image and associated color profile have" + 
                                   " mismatching number of color components: conversion is not possible");
                return argbWR;
            }

            /**
             * Get the data from the source for the requested region
             */
            int w = argbWR.getWidth();
            int h = argbWR.getHeight();
            int minX = argbWR.getMinX();
            int minY = argbWR.getMinY();
            WritableRaster srcWR = 
                imgCM.createCompatibleWritableRaster(w, h);
            srcWR = srcWR.createWritableTranslatedChild(minX, minY);
            img.copyData(srcWR);

            /**
             * If the source data is not a ComponentColorModel using a 
             * BandedSampleModel, do the conversion now.
             */
            if(!(imgCM instanceof ComponentColorModel) ||
               !(img.getSampleModel() instanceof BandedSampleModel) ||
               (imgCM.hasAlpha() &&
                imgCM.isAlphaPremultiplied() == true)) {
                ComponentColorModel imgCompCM 
                    = new ComponentColorModel
                        (imgCS,                      // Same ColorSpace as img
                         imgCM.getComponentSize(),   // Number of bits/comp
                         imgCM.hasAlpha(),             // Same alpha as img
                         false, // unpremult alpha (so we can remove it next).
                         imgCM.getTransparency(),      // Same trans as img 
                         DataBuffer.TYPE_BYTE);        // 8 bit/component.

                WritableRaster wr = Raster.createBandedRaster
                    (DataBuffer.TYPE_BYTE,
                     argbWR.getWidth(), argbWR.getHeight(),
                     imgCompCM.getNumComponents(),
                     new Point(0, 0));

                BufferedImage imgComp = new BufferedImage
                    (imgCompCM, wr, imgCompCM.isAlphaPremultiplied(), null);

                BufferedImage srcImg = new BufferedImage
                    (imgCM, srcWR.createWritableTranslatedChild(0, 0),
                     imgCM.isAlphaPremultiplied(), null);
                
                Graphics2D g = imgComp.createGraphics();
                g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
                                   RenderingHints.VALUE_COLOR_RENDER_QUALITY);
                g.drawImage(srcImg, 0, 0, null);
                img = imgComp;
                imgCM = imgCompCM;
                srcWR = wr.createWritableTranslatedChild(minX, minY);
            } 

            /**
             * Now, the input image is using a component color
             * model. We can therefore create an image with the new
             * profile, using a ComponentColorModel as well, because
             * we know the number of components match (this was
             * checked at the begining of this routine).  */
            ComponentColorModel newCM = new ComponentColorModel
                (colorSpace,                    // **** New ColorSpace ****
                 imgCM.getComponentSize(),      // Array of number of bits per components
                 false,                         // No alpa
                 false,                         // Not premultiplied
                 Transparency.OPAQUE,           // No transparency
                 DataBuffer.TYPE_BYTE);         // 8 Bits
            
            // Build a raster with bands 0, 1 and 2 of img's raster
            DataBufferByte data = (DataBufferByte)srcWR.getDataBuffer();
            srcWR = Raster.createBandedRaster
                (data, argbWR.getWidth(), argbWR.getHeight(), 
                 argbWR.getWidth(), new int[]{0, 1, 2},
                 new int[]{0, 0, 0}, new Point(0, 0));
            BufferedImage newImg = new BufferedImage
                (newCM, srcWR, newCM.isAlphaPremultiplied(), null);

            /**
             * Now, convert the image to sRGB
             */
            ComponentColorModel sRGBCompCM = new ComponentColorModel
                (ColorSpace.getInstance(ColorSpace.CS_sRGB),
                 new int[]{8, 8, 8},
                 false,
                 false,
                 Transparency.OPAQUE,
                 DataBuffer.TYPE_BYTE);

            WritableRaster wr = Raster.createBandedRaster
                (DataBuffer.TYPE_BYTE, argbWR.getWidth(), argbWR.getHeight(),
                 sRGBCompCM.getNumComponents(), new Point(0, 0));

            BufferedImage sRGBImage = new BufferedImage
                (sRGBCompCM, wr, false, null);
            ColorConvertOp colorConvertOp = new ColorConvertOp(null);
            colorConvertOp.filter(newImg, sRGBImage);

            /**
             * Integrate alpha back into the image if there is any
             */
            if (imgCM.hasAlpha()){
                DataBufferByte rgbData = (DataBufferByte)wr.getDataBuffer();
                byte[][] imgBanks = data.getBankData();
                byte[][] rgbBanks = rgbData.getBankData();
                
                byte[][] argbBanks = {rgbBanks[0], rgbBanks[1], 
                                      rgbBanks[2], imgBanks[3]};
                DataBufferByte argbData = new DataBufferByte(argbBanks, imgBanks[0].length);
                srcWR = Raster.createBandedRaster
                    (argbData, argbWR.getWidth(), argbWR.getHeight(),
                     argbWR.getWidth(), new int[]{0, 1, 2, 3},
                     new int[]{0, 0, 0, 0}, new Point(0, 0));
                sRGBCompCM = new ComponentColorModel
                    (ColorSpace.getInstance(ColorSpace.CS_sRGB),
                     new int[]{8, 8, 8, 8},
                     true,
                     false,
                     Transparency.TRANSLUCENT,
                     DataBuffer.TYPE_BYTE);
                sRGBImage = new BufferedImage(sRGBCompCM,
                                              srcWR,
                                              false,
                                              null);
                
            } 

            /*BufferedImage result = new BufferedImage(img.getWidth(),
              img.getHeight(),
              BufferedImage.TYPE_INT_ARGB);*/
            BufferedImage result = new BufferedImage(sRGBCM,
                                                     argbWR.createWritableTranslatedChild(0, 0),
                                                     false,
                                                     null);


            ///////////////////////////////////////////////
            // BUG IN ColorConvertOp: The following breaks:
            // colorConvertOp.filter(sRGBImage, result);
            //
            // Using Graphics2D instead....
            ///////////////////////////////////////////////
            Graphics2D g = result.createGraphics();
            g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
                               RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g.drawImage(sRGBImage, 0, 0, null);
            g.dispose();

            return argbWR;
        }catch(Exception e){
            e.printStackTrace();
            throw new Error();        
        }
    }

}
