/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: FormatRed.java 750418 2009-03-05 11:03:54Z vhennebert $ */

package org.apache.xmlgraphics.image.rendered;

import java.awt.Point;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;

import org.apache.xmlgraphics.image.GraphicsUtil;

/**
 * This allows you to specify the ColorModel, Alpha premult and/or
 * SampleModel to be used for output.  If the input image lacks
 * Alpha and alpha is included in output then it is filled with
 * alpha=1.  In all other cases bands are simply copied.
 *
 * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
 * @version $Id: FormatRed.java 750418 2009-03-05 11:03:54Z vhennebert $
 */
public class FormatRed extends AbstractRed {

    public static CachableRed construct(CachableRed src, ColorModel cm) {
        ColorModel srcCM = src.getColorModel();
        if ((cm.hasAlpha() != srcCM.hasAlpha()) ||
            (cm.isAlphaPremultiplied() != srcCM.isAlphaPremultiplied()))
            return new FormatRed(src, cm);

        if (cm.getNumComponents() != srcCM.getNumComponents())
            throw new IllegalArgumentException
                ("Incompatible ColorModel given");


        if ((srcCM instanceof ComponentColorModel) &&
            (cm    instanceof ComponentColorModel))
            return src;

        if ((srcCM instanceof DirectColorModel) &&
            (cm    instanceof DirectColorModel))
            return src;

        return new FormatRed(src, cm);
    }

    /**
     * Construct an instance of CachableRed around a BufferedImage.
     */
    public FormatRed(CachableRed cr, SampleModel sm) {
        super(cr, cr.getBounds(),
              makeColorModel(cr, sm), sm,
              cr.getTileGridXOffset(),
              cr.getTileGridYOffset(),
              null);
    }

    public FormatRed(CachableRed cr, ColorModel cm) {
        super(cr, cr.getBounds(),
              cm, makeSampleModel(cr, cm),
              cr.getTileGridXOffset(),
              cr.getTileGridYOffset(),
              null);
    }

    /**
     * fetch the source image for this node.
     */
    public CachableRed getSource() {
        return (CachableRed)getSources().get(0);
    }

    public Object getProperty(String name) {
        return getSource().getProperty(name);
    }

    public String [] getPropertyNames() {
        return getSource().getPropertyNames();
    }

    public WritableRaster copyData(WritableRaster wr) {
        ColorModel  cm    = getColorModel();
        CachableRed cr    = getSource();
        ColorModel  srcCM = cr.getColorModel();
        SampleModel srcSM = cr.getSampleModel();
        srcSM = srcSM.createCompatibleSampleModel(wr.getWidth(),
                                                  wr.getHeight());
        WritableRaster srcWR;
        srcWR = Raster.createWritableRaster(srcSM, new Point(wr.getMinX(),
                                                             wr.getMinY()));
        getSource().copyData(srcWR);

        BufferedImage srcBI = new BufferedImage
            (srcCM, srcWR.createWritableTranslatedChild(0,0),
             srcCM.isAlphaPremultiplied(), null);
        BufferedImage dstBI = new BufferedImage
            (cm, wr.createWritableTranslatedChild(0,0),
             cm.isAlphaPremultiplied(), null);

        GraphicsUtil.copyData(srcBI, dstBI);

        return wr;
    }

    public static SampleModel makeSampleModel(CachableRed cr, ColorModel cm) {
        SampleModel srcSM = cr.getSampleModel();
        return cm.createCompatibleSampleModel(srcSM.getWidth(),
                                              srcSM.getHeight());
    }

    public static ColorModel makeColorModel(CachableRed cr, SampleModel sm) {
        ColorModel srcCM = cr.getColorModel();
        ColorSpace cs    = srcCM.getColorSpace();

        int bands = sm.getNumBands();

        int bits;
        int dt = sm.getDataType();
        switch (dt) {
        case DataBuffer.TYPE_BYTE:   bits=8;  break;
        case DataBuffer.TYPE_SHORT:  bits=16; break;
        case DataBuffer.TYPE_USHORT: bits=16; break;
        case DataBuffer.TYPE_INT:    bits=32; break;
        default:
            throw new IllegalArgumentException
                ("Unsupported DataBuffer type: " + dt);
        }

        boolean hasAlpha = srcCM.hasAlpha();
        if (hasAlpha){
            // if Src has Alpha then our out bands must
            // either be one less than the source (no out alpha)
            // or equal (still has alpha)
            if (bands == srcCM.getNumComponents()-1)
                hasAlpha = false;
            else if (bands != srcCM.getNumComponents())
                throw new IllegalArgumentException
                    ("Incompatible number of bands in and out");
        } else {
            if (bands == srcCM.getNumComponents()+1)
                hasAlpha = true;
            else if (bands != srcCM.getNumComponents())
                throw new IllegalArgumentException
                    ("Incompatible number of bands in and out");
        }

        boolean preMult  = srcCM.isAlphaPremultiplied();
        if (!hasAlpha)
            preMult = false;

        if (sm instanceof ComponentSampleModel) {
            int [] bitsPer = new int[bands];
            for (int i=0; i<bands; i++)
                bitsPer[i] = bits;

            return new ComponentColorModel
                (cs, bitsPer, hasAlpha, preMult,
                 hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE,
                 dt);
        } else if (sm instanceof SinglePixelPackedSampleModel) {
            SinglePixelPackedSampleModel sppsm;
            sppsm = (SinglePixelPackedSampleModel)sm;
            int[] masks  = sppsm.getBitMasks();
            if (bands == 4)
                return new DirectColorModel
                    (cs, bits, masks[0], masks[1], masks[2], masks[3],
                     preMult, dt);
            else if (bands == 3)
                return new DirectColorModel
                    (cs, bits, masks[0], masks[1], masks[2], 0x0,
                     preMult, dt);
            else
                throw new IllegalArgumentException
                    ("Incompatible number of bands out for ColorModel");
        }
        throw new IllegalArgumentException
            ("Unsupported SampleModel Type");
    }
}
