/*****************************************************************************
 * 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.Rectangle;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.List;


/**
 * This implements a masking operation by multiply the alpha channel of
 * one image by a luminance image (the mask).
 *
 * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
 * @version $Id: MultiplyAlphaRed.java,v 1.4 2003/04/11 13:58:00 vhardy Exp $ */
public class MultiplyAlphaRed extends AbstractRed {

    /**
     * Multiply the alpha of one image with a mask image.
     * The size of the resultant image is the intersection of the
     * two image bounds.  If you want the end image to be the size
     * of one or the other please use the PadRed operator.
     *
     * @param src   The image to convert to multiply the alpha of
     * @param alpha The mask image to multiply the alpha channel of src
     *              with.
     */
    public MultiplyAlphaRed(CachableRed src, CachableRed alpha) {
        super(makeList(src, alpha),
              makeBounds(src,alpha),
              fixColorModel(src),
              fixSampleModel(src),
              src.getTileGridXOffset(),
              src.getTileGridYOffset(),
              null);
    }

    public boolean is_INT_PACK_BYTE_COMP(SampleModel srcSM,
                                         SampleModel alpSM) {
          // Check SampleModel types DirectColorModel
        if(!(srcSM instanceof SinglePixelPackedSampleModel)) return false;
        if(!(alpSM instanceof ComponentSampleModel))         return false;

        // Check transfer types
        if(srcSM.getDataType() != DataBuffer.TYPE_INT)       return false;
        if(alpSM.getDataType() != DataBuffer.TYPE_BYTE)      return false;


        SinglePixelPackedSampleModel sppsm;
        sppsm = (SinglePixelPackedSampleModel)srcSM;

        int [] masks = sppsm.getBitMasks();
        if(masks.length != 4) return false;
        if(masks[0] != 0x00ff0000) return false;
        if(masks[1] != 0x0000ff00) return false;
        if(masks[2] != 0x000000ff) return false;
        if(masks[3] != 0xff000000) return false;
 
        ComponentSampleModel csm;
        csm = (ComponentSampleModel)alpSM;
        if (csm.getNumBands()    != 1) return false;
        if (csm.getPixelStride() != 1) return false;

        return true;
   }

    public WritableRaster INT_PACK_BYTE_COMP_Impl (WritableRaster wr) {
          // Get my source.
        CachableRed srcRed   = (CachableRed)getSources().get(0);
        CachableRed alphaRed = (CachableRed)getSources().get(1);

        // Already has alpha channel so we use it.
        srcRed.copyData(wr);

        Rectangle rgn = wr.getBounds();
        rgn = rgn.intersection(alphaRed.getBounds());
            
        Raster r = alphaRed.getData(rgn);

        ComponentSampleModel csm;
        csm = (ComponentSampleModel)r.getSampleModel();
        final int alpScanStride = csm.getScanlineStride();

        DataBufferByte alpDB   = (DataBufferByte)r.getDataBuffer();
        final int      alpBase 
            = (alpDB.getOffset() + 
               csm.getOffset(rgn.x-r.getSampleModelTranslateX(), 
                             rgn.y-r.getSampleModelTranslateY()));

            
          // Access the pixel data array
        final byte alpPixels[] = alpDB.getBankData()[0];

        SinglePixelPackedSampleModel sppsm;
        sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
        final int srcScanStride = sppsm.getScanlineStride();

        DataBufferInt srcDB   = (DataBufferInt)wr.getDataBuffer();
        final int     srcBase 
            = (srcDB.getOffset() + 
               sppsm.getOffset(rgn.x-wr.getSampleModelTranslateX(), 
                               rgn.y-wr.getSampleModelTranslateY()));

          // Access the pixel data array
        final int srcPixels[] = srcDB.getBankData()[0];

        ColorModel cm = srcRed.getColorModel();

        if (cm.isAlphaPremultiplied()) {
            // For alpha premult we need to multiply all comps.
            for (int y=0; y<rgn.height; y++) {
                int sp = srcBase + y*srcScanStride;
                int ap = alpBase + y*alpScanStride;
                int end = sp + rgn.width;

                while (sp<end) {
                    int a = ((int)alpPixels[ap++])&0xFF;
                    final int pix = srcPixels[sp];
                    srcPixels[sp] = 
                        ((((((pix>>>24)     ) *a)&0xFF00)<<16) |
                         (((((pix>>>16)&0xFF) *a)&0xFF00)<<8 ) |
                         (((((pix>>> 8)&0xFF) *a)&0xFF00)    ) |
                         (((((pix     )&0xFF) *a)&0xFF00)>>8 ));
                    sp++;
                }
            }
                
        } else {
              // For non-alpha premult we only need to multiply alpha.
            for (int y=0; y<rgn.height; y++) {
                int sp = srcBase + y*srcScanStride;
                int ap = alpBase + y*alpScanStride;
                int end = sp + rgn.width;
                while (sp<end) {
                    int a = ((int)alpPixels[ap++])&0xFF;
                    int sa = srcPixels[sp]>>>24;
                    srcPixels[sp] = ((((sa*a) & 0xFF00)<<16)|
                                     srcPixels[sp]&0x00FFFFFF);
                    sp++;
                }
            }
        }

        return wr;
    }

    public WritableRaster copyData(WritableRaster wr) {
        // Get my source.
        CachableRed srcRed   = (CachableRed)getSources().get(0);
        CachableRed alphaRed = (CachableRed)getSources().get(1);

        if (is_INT_PACK_BYTE_COMP(srcRed.getSampleModel(),
                                  alphaRed.getSampleModel()))
            return INT_PACK_BYTE_COMP_Impl(wr);

        ColorModel cm = srcRed.getColorModel();
        if (cm.hasAlpha()) {
            // Already has alpha channel so we use it.
            srcRed.copyData(wr);

            Rectangle rgn = wr.getBounds();
            if (rgn.intersects(alphaRed.getBounds()))
                rgn = rgn.intersection(alphaRed.getBounds());
            else 
                return wr;
            
            int [] wrData    = null;
            int [] alphaData = null;

            Raster r = alphaRed.getData(rgn);
            int    w = rgn.width;

            final int bands = wr.getSampleModel().getNumBands();

            if (cm.isAlphaPremultiplied()) {
                for (int y=rgn.y; y<rgn.y+rgn.height; y++) {
                    wrData    = wr.getPixels (rgn.x, y, w, 1, wrData);
                    alphaData = r .getSamples(rgn.x, y, w, 1, 0, alphaData);
                    int i=0, a, b;
                          // 4 is the most common case.  
                          // 2 is probably next most common...
                    switch (bands) {
                    case 2: 
                        for (int x=0; x<alphaData.length; x++) {
                            a = alphaData[x]&0xFF;
                            wrData[i] = ((wrData[i]&0xFF)*a)>>8; ++i;
                            wrData[i] = ((wrData[i]&0xFF)*a)>>8; ++i;
                        }
                        break;
                    case 4: 
                        for (int x=0; x<alphaData.length; x++) {
                            a = alphaData[x]&0xFF;
                            wrData[i] = ((wrData[i]&0xFF)*a)>>8; ++i;
                            wrData[i] = ((wrData[i]&0xFF)*a)>>8; ++i;
                            wrData[i] = ((wrData[i]&0xFF)*a)>>8; ++i;
                            wrData[i] = ((wrData[i]&0xFF)*a)>>8; ++i;
                        }
                        break;
                    default:
                        for (int x=0; x<alphaData.length; x++) {
                            a = alphaData[x]&0xFF;
                            for (b=0; b<bands; b++) {
                                wrData[i] = ((wrData[i]&0xFF)*a)>>8; 
                                ++i;
                            }
                        }
                    }
                    wr.setPixels(rgn.x, y, w, 1, wrData);
                }
            } else {
                int b = srcRed.getSampleModel().getNumBands()-1;
                for (int y=rgn.y; y<rgn.y+rgn.height; y++) {
                    wrData    = wr.getSamples(rgn.x, y, w, 1, b, wrData);
                    alphaData = r .getSamples(rgn.x, y, w, 1, 0, alphaData);
                    for (int i=0; i<wrData.length; i++) {
                        wrData[i] = ((wrData[i]&0xFF)*(alphaData[i]&0xFF))>>8;
                    }
                    wr.setSamples(rgn.x, y, w, 1, b, wrData);
                }
            }

            return wr;
        }

        // No alpha in source, so we hide the alpha channel in wr and
        // have our source fill wr with color info...
        int [] bands = new int[wr.getNumBands()-1];
        for (int i=0; i<bands.length; i++)
            bands[i] = i;

        WritableRaster subWr;
        subWr = wr.createWritableChild(wr.getMinX(),  wr.getMinY(), 
                                       wr.getWidth(), wr.getHeight(),
                                       wr.getMinX(),  wr.getMinY(),
                                       bands);

        srcRed.copyData(subWr);

        Rectangle rgn = wr.getBounds();
        rgn = rgn.intersection(alphaRed.getBounds());
            

        bands = new int [] { wr.getNumBands()-1 };
        subWr = wr.createWritableChild(rgn.x,     rgn.y, 
                                       rgn.width, rgn.height,
                                       rgn.x,     rgn.y, 
                                       bands);
        alphaRed.copyData(subWr);

        return wr;
    }

    public static List makeList(CachableRed src1, CachableRed src2) {
        List ret = new ArrayList(2);
        ret.add(src1);
        ret.add(src2);
        return ret;
    }

    public static Rectangle makeBounds(CachableRed src1, CachableRed src2) {
        Rectangle r1 = src1.getBounds();
        Rectangle r2 = src2.getBounds();
        return r1.intersection(r2);
    }

    public static SampleModel fixSampleModel(CachableRed src) {
        ColorModel  cm = src.getColorModel();
        SampleModel srcSM = src.getSampleModel();

        if (cm.hasAlpha()) 
            return srcSM;

        int w = srcSM.getWidth();
        int h = srcSM.getHeight();
        int b = srcSM.getNumBands()+1;
        int [] offsets = new int[b];
        for (int i=0; i < b; i++) 
            offsets[i] = i;

        // Really should check DataType range in srcSM...
        return new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE,
                                               w, h, b, w*b, offsets);
    }

    public static ColorModel fixColorModel(CachableRed src) {
        ColorModel  cm = src.getColorModel();

        if (cm.hasAlpha())
            return cm;

        int b = src.getSampleModel().getNumBands()+1;
        int [] bits = new int[b];
        for (int i=0; i < b; i++) 
            bits[i] = 8;

        ColorSpace cs = cm.getColorSpace();

        return new ComponentColorModel(cs, bits, true, false, 
                                       Transparency.TRANSLUCENT,
                                       DataBuffer.TYPE_BYTE);
    }
}
