/* ImageConverter.java -- Loads images asynchronously
   Copyright (C) 2008 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package gnu.java.awt.image;

import gnu.java.awt.image.AsyncImage;

import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.ImageConsumer;
import java.awt.image.IndexColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.util.Hashtable;

/**
 * Convert an Image to a BufferedImage.
 * 
 * @author Roman Kennke (kennke@aicas.com)
 */
public class ImageConverter implements ImageConsumer
{

  public static final String IMAGE_TRANSPARENCY_PROPERTY =
    "gnu.awt.image.transparency";

  public static final String IMAGE_PROPERTIES_PROPERTY =
    "gnu.awt.image.properties";

  private AsyncImage image;
  private BufferedImage bImage;
  private Hashtable imageProperties;
  private int width, height;
  private ColorModel colorModel;
  private ColorModel targetColorModel;

  public ImageConverter()
  {
    width = 0;
    height = 0;
    image = new AsyncImage();
  }

  public void setDimensions(int w, int h)
  {
    width = w;
    height = h;
  }

  public void setProperties(Hashtable props)
  {
    // Ignore for now.
  }

  public void setColorModel(ColorModel model)
  {
    colorModel = model;
  }

  public void setHints(int flags)
  {
    // Ignore for now.
  }

  public void setPixels(int x, int y, int w, int h, ColorModel model,
                        byte[] pixels, int offset, int scansize)
  {
    model = setupColorModel(model);

    if (bImage == null)
      {
        createImage();
      }

    Integer t = (Integer) imageProperties.get("gnu.awt.image.transparency");
    int transparency = t.intValue();

    if(targetColorModel.equals(model))
      {
        transparency = transferPixels(x, y, w, h, model, pixels, offset,
                                      scansize, transparency);
      }
    else if (model instanceof IndexColorModel
             && targetColorModel.equals(ColorModel.getRGBdefault())) 
      {
        transparency = convertIndexColorModelToSRGB(x, y, w, h,
                                                    (IndexColorModel) model,
                                                    pixels, offset, scansize,
                                                    transparency);
      }
    else
      {
        transparency = convertPixels(x, y, w, h, model, pixels, offset,
                                     scansize, transparency);
      }

    imageProperties.put("gnu.awt.image.transparency",
                        Integer.valueOf(transparency));
  }

  public void setPixels(int x, int y, int w, int h, ColorModel model,
                        int[] pixels, int offset, int scansize)
  {
    model = setupColorModel(model);
    if (bImage == null)
      {
        createImage();
      }

    Integer t = (Integer) imageProperties.get(IMAGE_TRANSPARENCY_PROPERTY);
    int transparency= t.intValue();

    if (targetColorModel.equals(model))
      {
        transparency = transferPixels(x, y, w, h, model, pixels, offset,
                                      scansize, transparency);
      }
    else if (model instanceof IndexColorModel
             && targetColorModel.equals(ColorModel.getRGBdefault()))
      {
        transparency = convertIndexColorModelToSRGB(x, y, w, h,
                                                    (IndexColorModel) model,
                                                    pixels, offset, scansize,
                                                    transparency);
      }
    else
      {
        transparency = convertPixels(x, y, w, h, model, pixels, offset,
                                     scansize, transparency);
      }

    imageProperties.put(IMAGE_TRANSPARENCY_PROPERTY,
                        Integer.valueOf(transparency));

  }

  /**
   * Initialize the color model for this setPixels run: <br/>
   * 1. if no color model was given use the hinted color model <br/>
   * 2. if no color model was given and non was hinted use the default sRGB color model. <br/>
   * Also:<br/>
   * If no target color model was set use the color model of the given pixels.
   * @param model
   * @return
   */
  private ColorModel setupColorModel(ColorModel model)
  {
    // If the given color model is null use the previously hinted color model.
    if (model == null)
      model = colorModel;

    // If no color model was given or hinted use default sRGB.
    if (model == null)
      model = ColorModel.getRGBdefault();

    // If no specific color model was requested for the target use the current
    // pixels model.
    if (targetColorModel == null)
      targetColorModel = model;
    targetColorModel = ColorModel.getRGBdefault();
    return model;
  }

  /**
   * Creates the image instance into which the pixel data is converted.
   */
  private void createImage()
  {
    if (imageProperties == null)
      {
        imageProperties = new Hashtable();
      }

    imageProperties.put(IMAGE_TRANSPARENCY_PROPERTY,
                        Integer.valueOf(Transparency.OPAQUE));
    imageProperties.put(IMAGE_PROPERTIES_PROPERTY, imageProperties);

    // For the sRGB case let the GraphicsEnvironment create an image for us.
    if (ColorModel.getRGBdefault().equals(targetColorModel))
      {
        bImage = GraphicsEnvironment.getLocalGraphicsEnvironment()
                                    .getDefaultScreenDevice()
                                    .getDefaultConfiguration()
             .createCompatibleImage(width, height, Transparency.TRANSLUCENT);
      }
    else
      {
        WritableRaster raster =
          targetColorModel.createCompatibleWritableRaster(width, height);
        bImage = new BufferedImage(targetColorModel, raster, false,
                                   imageProperties);
      }
    image.setRealImage(bImage);
    return;
  }

  /**
   * Transfers pixels into a raster of the same color model.
   *
   * @param x the X coordinate of the source pixel rectangle
   * @param y the Y coordinate of the source pixel rectangle
   * @param w the width of the source pixel rectangle
   * @param h the height of the source pixel rectangle
   * @param model the color model of the source pixels
   * @param pixels the pixel data
   * @param offset the offset in the pixel array
   * @param scansize the scanline size
   * @param transparency the assumed transparency
   * 
   * @return the determined transparency
   */
  private int transferPixels(int x, int y, int w, int h, ColorModel model,
                             byte[] pixels, int offset, int scansize,
                             int transparency)
  {
    // If we have the same color model, then we can simply drop
    // the pixel value into the target raster.
    bImage.getRaster().setDataElements(x, y, w, h, pixels);

    for (int yy = 0; yy < h; yy++)
      {
        for (int xx = 0; xx < w; xx++)
          {
            int pixel = 0xFF & pixels[yy * scansize + xx + offset];
            int alpha = model.getAlpha(pixel);
            transparency = updateTransparency(alpha, transparency);
          }
      }
    return transparency;
  }

  /**
   * Transfers pixels into a raster of the same color model.
   *
   * @param x the X coordinate of the source pixel rectangle
   * @param y the Y coordinate of the source pixel rectangle
   * @param w the width of the source pixel rectangle
   * @param h the height of the source pixel rectangle
   * @param model the color model of the source pixels
   * @param pixels the pixel data
   * @param offset the offset in the pixel array
   * @param scansize the scanline size
   * @param transparency the assumed transparency
   * 
   * @return the determined transparency
   */
  private int transferPixels(int x, int y, int w, int h, ColorModel model,
                             int[] pixels, int offset, int scansize,
                             int transparency)
  {
    // If we have the same color model, then we can simply drop
    // the pixel value into the target raster.
    bImage.getRaster().setDataElements(x, y, w, h, pixels);

    for (int yy = 0; yy < h; yy++)
      {
        for (int xx = 0; xx < w; xx++)
          {
            int pixel = pixels[yy * scansize + xx + offset];
            int alpha = model.getAlpha(pixel);
            transparency = updateTransparency(alpha, transparency);
          }
      }
    return transparency;
  }

  /**
   * Converts pixel from one color model to another, and stores them in the
   * target image.
   *
   * @param x the X coordinate of the source pixel rectangle
   * @param y the Y coordinate of the source pixel rectangle
   * @param w the width of the source pixel rectangle
   * @param h the height of the source pixel rectangle
   * @param model the color model of the source pixels
   * @param pixels the pixel data
   * @param offset the offset in the pixel array
   * @param scansize the scanline size
   * @param transparency the assumed transparency
   * 
   * @return the determined transparency
   */
  private int convertPixels(int x, int y, int w, int h, ColorModel model,
                            byte[] pixels, int offset, int scansize,
                            int transparency)
  {
    // If the color models are not the same, we must convert the
    // pixel values from one model to the other.
    Object dataEl = null;
    // Convert pixels to the destination color model.
    for (int yy = 0; yy < h; yy++)
      {
        for (int xx = 0; xx < w; xx++)
          {
            int pixel = 0xFF & pixels[yy * scansize + xx + offset];
            int rgb = model.getRGB(pixel);
            int alpha = model.getAlpha(pixel);
            transparency = updateTransparency(alpha, transparency);
            dataEl = targetColorModel.getDataElements(rgb, dataEl);
            bImage.getRaster().setDataElements(x + xx, y + yy, dataEl);
          }
      }
    return transparency;
  }

  /**
   * Converts pixel from one color model to another, and stores them in the
   * target image.
   *
   * @param x the X coordinate of the source pixel rectangle
   * @param y the Y coordinate of the source pixel rectangle
   * @param w the width of the source pixel rectangle
   * @param h the height of the source pixel rectangle
   * @param model the color model of the source pixels
   * @param pixels the pixel data
   * @param offset the offset in the pixel array
   * @param scansize the scanline size
   * @param transparency the assumed transparency
   * 
   * @return the determined transparency
   */
  private int convertPixels(int x, int y, int w, int h, ColorModel model,
                            int[] pixels, int offset, int scansize,
                            int transparency)
  {
    // If the color models are not the same, we must convert the
    // pixel values from one model to the other.
    Object dataEl = null;
    // Convert pixels to the destination color model.
    for (int yy = 0; yy < h; yy++)
      {
        for (int xx = 0; xx < w; xx++)
          {
            int pixel = pixels[yy * scansize + xx + offset];
            int rgb = model.getRGB(pixel);
            int alpha = model.getAlpha(pixel);
            transparency = updateTransparency(alpha, transparency);
            dataEl = targetColorModel.getDataElements(rgb, dataEl);
            bImage.getRaster().setDataElements(x + xx, y + yy, dataEl);
          }
      }
    return transparency;
  }

  /**
   * Converts pixels from an index color model to the target image.
   *
   * @param x the X coordinate of the source pixel rectangle
   * @param y the Y coordinate of the source pixel rectangle
   * @param w the width of the source pixel rectangle
   * @param h the height of the source pixel rectangle
   * @param model the color model of the source pixels
   * @param pixels the pixel data
   * @param offset the offset in the pixel array
   * @param scansize the scanline size
   * @param transparency the assumed transparency
   * 
   * @return the determined transparency
   */
  private int convertIndexColorModelToSRGB(int x, int y, int w, int h,
                                           IndexColorModel model,
                                           byte[] pixels, int offset,
                                           int scansize, int transparency)
  {

    int mapSize = model.getMapSize();
    int[] colorMap = new int[mapSize];
    for(int i=0; i < mapSize; i++)
      {
        colorMap[i] = model.getRGB(i);
      }

    WritableRaster raster = bImage.getRaster();
    SinglePixelPackedSampleModel sampleMode =
      (SinglePixelPackedSampleModel) raster.getSampleModel();
    DataBuffer dataBuffer = (DataBuffer) raster.getDataBuffer();

    int rasterOffset = sampleMode.getOffset(x,y)+dataBuffer.getOffset();
    int rasterScanline = sampleMode.getScanlineStride();

    for (int yy = 0; yy < h; yy++)
      {
        int xoffset = offset;
        for (int xx = 0; xx < w; xx++)
          {
            int argb  = colorMap[(pixels[xoffset++] & 0xFF)];
            dataBuffer.setElem(rasterOffset+xx, argb);
            int alpha = (argb >>> 24);
            transparency = updateTransparency(alpha, transparency);
          }
        offset += scansize;
        rasterOffset += rasterScanline;
      }

    return transparency;
  }

  /**
   * Converts pixels from an index color model to the target image.
   *
   * @param x the X coordinate of the source pixel rectangle
   * @param y the Y coordinate of the source pixel rectangle
   * @param w the width of the source pixel rectangle
   * @param h the height of the source pixel rectangle
   * @param model the color model of the source pixels
   * @param pixels the pixel data
   * @param offset the offset in the pixel array
   * @param scansize the scanline size
   * @param transparency the assumed transparency
   * 
   * @return the determined transparency
   */
  private int convertIndexColorModelToSRGB(int x, int y, int w, int h,
                                           IndexColorModel model, int[] pixels,
                                           int offset, int scansize,
                                           int transparency)
  {
    int mapSize = model.getMapSize();
    int[] colorMap = new int[mapSize];
    for(int i=0; i < mapSize; i++)
      {
        colorMap[i] = model.getRGB(i);
      }

    WritableRaster raster = bImage.getRaster();
    SinglePixelPackedSampleModel sampleMode =
      (SinglePixelPackedSampleModel) raster.getSampleModel();
    DataBuffer dataBuffer = (DataBuffer)raster.getDataBuffer();

    int rasterOffset = sampleMode.getOffset(x, y) + dataBuffer.getOffset();
    int rasterScanline = sampleMode.getScanlineStride();

    for (int yy = 0; yy < h; yy++)
      {
        int xoffset = offset;
        for (int xx = 0; xx < w; xx++)
          {
            int argb  = colorMap[pixels[xoffset++]];
            dataBuffer.setElem(rasterOffset + xx, argb);
            int alpha = (argb >>> 24);
            transparency = updateTransparency(alpha, transparency);
          }
        offset += scansize;
        rasterOffset += rasterScanline;
      }

    return transparency;
  }

  /**
   * Updates the transparency information according to the alpha pixel value.
   *
   * @param alpha the alpha pixel value
   * @param transparency the old transparency
   *
   * @return the updated transparency
   */
  private int updateTransparency(int alpha, int transparency)
  {
    if (alpha != 0xFF)
      {
        if (alpha == 0x00 && transparency <= Transparency.BITMASK)
          {
            transparency = Transparency.BITMASK;
          }
        else if (transparency < Transparency.TRANSLUCENT)
          {
            transparency = Transparency.TRANSLUCENT;
          }
      }
    return transparency;
  }

  public void imageComplete(int status)
  {
    image.notifyObservers(ImageObserver.ALLBITS, 0, 0, width, height);
  }

  public void setTargetColorModel(ColorModel model)
  {
    targetColorModel = model;
  }

  public Image getImage()
  {
    return image;
  }
}
