/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy 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 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.image;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferDouble;
import java.awt.image.DataBufferFloat;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.media.jai.PlanarImage;

import icy.common.CollapsibleEvent;
import icy.common.UpdateEventHandler;
import icy.common.exception.TooLargeArrayException;
import icy.common.listener.ChangeListener;
import icy.image.IcyBufferedImageEvent.IcyBufferedImageEventType;
import icy.image.colormap.IcyColorMap;
import icy.image.colormap.LinearColorMap;
import icy.image.colormodel.IcyColorModel;
import icy.image.colormodel.IcyColorModelEvent;
import icy.image.colormodel.IcyColorModelListener;
import icy.image.lut.LUT;
import icy.math.ArrayMath;
import icy.math.MathUtil;
import icy.math.Scaler;
import icy.type.DataType;
import icy.type.TypeUtil;
import icy.type.collection.array.Array1DUtil;
import icy.type.collection.array.Array2DUtil;
import icy.type.collection.array.ArrayUtil;
import icy.type.collection.array.ByteArrayConvert;
import loci.formats.FormatException;
import loci.formats.IFormatReader;
import loci.formats.gui.SignedByteBuffer;
import loci.formats.gui.SignedShortBuffer;
import loci.formats.gui.UnsignedIntBuffer;
import plugins.kernel.importer.LociImporterPlugin;

/**
 * @author stephane
 */
public class IcyBufferedImage extends BufferedImage implements IcyColorModelListener, ChangeListener
{
    /**
     * @deprecated Use {@link IcyBufferedImageUtil.FilterType} instead.
     */
    @Deprecated
    public static enum FilterType
    {
        NEAREST, BILINEAR, BICUBIC
    };

    protected static IcyBufferedImageUtil.FilterType getNewFilterType(FilterType ft)
    {
        switch (ft)
        {
            default:
            case NEAREST:
                return IcyBufferedImageUtil.FilterType.NEAREST;
            case BILINEAR:
                return IcyBufferedImageUtil.FilterType.BILINEAR;
            case BICUBIC:
                return IcyBufferedImageUtil.FilterType.BICUBIC;
        }
    }

    /**
     * Convert a list of BufferedImage to an IcyBufferedImage (multi component).<br>
     * IMPORTANT : source images can be used as part or as the whole result<br>
     * so consider them as "lost"
     * 
     * @param imageList
     *        list of {@link BufferedImage}
     * @return {@link IcyBufferedImage}
     * @deprecated
     *             use {@link #createFrom} instead
     */
    @Deprecated
    public static IcyBufferedImage convert(List<BufferedImage> imageList)
    {
        return createFrom(imageList);
    }

    /**
     * Create an IcyBufferedImage (multi component) from a list of BufferedImage.<br>
     * IMPORTANT : source images can be used as part or as the whole result so consider them as
     * 'lost'.
     * 
     * @param imageList
     *        list of {@link BufferedImage}
     * @return {@link IcyBufferedImage}
     * @throws IllegalArgumentException
     *         if imageList is empty or contains incompatible images.
     */
    public static IcyBufferedImage createFrom(List<? extends BufferedImage> imageList) throws IllegalArgumentException
    {
        if (imageList.size() == 0)
            throw new IllegalArgumentException("imageList should contains at least 1 image");

        final List<IcyBufferedImage> icyImageList = new ArrayList<IcyBufferedImage>();

        // transform images to icy images
        for (BufferedImage image : imageList)
            icyImageList.add(IcyBufferedImage.createFrom(image));

        final IcyBufferedImage firstImage = icyImageList.get(0);

        if (icyImageList.size() == 1)
            return firstImage;

        final DataType dataType = firstImage.getDataType_();
        final int width = firstImage.getWidth();
        final int height = firstImage.getHeight();

        // calculate channel number
        int numChannel = 0;
        for (IcyBufferedImage image : icyImageList)
            numChannel += image.getSizeC();

        final Object[] data = Array2DUtil.createArray(dataType, numChannel);

        // get data from all images
        int destC = 0;
        for (IcyBufferedImage image : icyImageList)
        {
            if (dataType != image.getDataType_())
                throw new IllegalArgumentException("All images contained in imageList should have the same dataType");
            if ((width != image.getWidth()) || (height != image.getHeight()))
                throw new IllegalArgumentException("All images contained in imageList should have the same dimension");

            for (int c = 0; c < image.getSizeC(); c++)
                data[destC++] = image.getDataXY(c);
        }

        // create and return the image
        return new IcyBufferedImage(width, height, data, dataType.isSigned());
    }

    /**
     * Convert a BufferedImage to an IcyBufferedImage.<br>
     * IMPORTANT : source image can be used as part or as the whole result<br>
     * so consider it as "lost"
     * 
     * @param image
     *        {@link BufferedImage}
     * @return {@link IcyBufferedImage}
     * @deprecated
     *             use {@link #createFrom} instead
     */
    @Deprecated
    public static IcyBufferedImage convert(BufferedImage image)
    {
        return createFrom(image);
    }

    /**
     * Create an IcyBufferedImage from a {@link PlanarImage}.<br>
     * IMPORTANT : source image can be used as part or as the whole result<br>
     * so consider it as lost.
     * 
     * @param image
     *        {@link PlanarImage}
     * @return {@link IcyBufferedImage}
     */
    public static IcyBufferedImage createFrom(PlanarImage image, boolean signedDataType)
    {
        final DataBuffer db = image.getData().getDataBuffer();
        final int w = image.getWidth();
        final int h = image.getHeight();

        if (db instanceof DataBufferByte)
            return new IcyBufferedImage(w, h, ((DataBufferByte) db).getBankData(), signedDataType);
        else if (db instanceof DataBufferShort)
            return new IcyBufferedImage(w, h, ((DataBufferShort) db).getBankData(), signedDataType);
        else if (db instanceof DataBufferUShort)
            return new IcyBufferedImage(w, h, ((DataBufferUShort) db).getBankData(), signedDataType);
        else if (db instanceof DataBufferInt)
            return new IcyBufferedImage(w, h, ((DataBufferInt) db).getBankData(), signedDataType);
        else if (db instanceof DataBufferFloat)
            return new IcyBufferedImage(w, h, ((DataBufferFloat) db).getBankData(), true);
        else if (db instanceof javax.media.jai.DataBufferFloat)
            return new IcyBufferedImage(w, h, ((javax.media.jai.DataBufferFloat) db).getBankData(), true);
        else if (db instanceof DataBufferDouble)
            return new IcyBufferedImage(w, h, ((DataBufferDouble) db).getBankData(), true);
        else if (db instanceof javax.media.jai.DataBufferDouble)
            return new IcyBufferedImage(w, h, ((javax.media.jai.DataBufferDouble) db).getBankData(), true);
        else
            // JAI keep dataType and others stuff in their BufferedImage
            return IcyBufferedImage.createFrom(image.getAsBufferedImage());
    }

    /**
     * Create an IcyBufferedImage from a {@link PlanarImage}.<br>
     * IMPORTANT : source image can be used as part or as the whole result<br>
     * so consider it as lost.
     * 
     * @param image
     *        {@link PlanarImage}
     * @return {@link IcyBufferedImage}
     */
    public static IcyBufferedImage createFrom(PlanarImage image)
    {
        return createFrom(image, false);
    }

    /**
     * Create an IcyBufferedImage from a BufferedImage.<br>
     * IMPORTANT : source image can be used as part or as the whole result<br>
     * so consider it as lost.
     * 
     * @param image
     *        {@link BufferedImage}
     * @return {@link IcyBufferedImage}
     */
    public static IcyBufferedImage createFrom(BufferedImage image)
    {
        // IcyBufferedImage --> no conversion needed
        if (image instanceof IcyBufferedImage)
            return (IcyBufferedImage) image;

        // sort of IcyBufferedImage (JAI can return that type) --> no conversion needed
        if (image.getColorModel() instanceof IcyColorModel)
            return new IcyBufferedImage(
                    IcyColorModel.createInstance((IcyColorModel) image.getColorModel(), false, false),
                    image.getRaster());

        final int w = image.getWidth();
        final int h = image.getHeight();
        final int type = image.getType();
        final BufferedImage temp;
        final Graphics g;

        // we first want a component based image
        switch (type)
        {
            case BufferedImage.TYPE_INT_RGB:
            case BufferedImage.TYPE_INT_BGR:
            case BufferedImage.TYPE_USHORT_555_RGB:
            case BufferedImage.TYPE_USHORT_565_RGB:
                temp = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
                g = temp.createGraphics();
                g.drawImage(image, 0, 0, null);
                g.dispose();
                break;

            case BufferedImage.TYPE_INT_ARGB:
            case BufferedImage.TYPE_INT_ARGB_PRE:
            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
                temp = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
                g = temp.createGraphics();
                g.drawImage(image, 0, 0, null);
                g.dispose();
                break;

            default:
                // if we have severals components with an unknown / incompatible sampleModel
                if ((image.getColorModel().getNumComponents() > 1)
                        && (!(image.getSampleModel() instanceof ComponentSampleModel)))
                {
                    // change it to a basic ABGR components image
                    temp = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
                    g = temp.createGraphics();
                    g.drawImage(image, 0, 0, null);
                    g.dispose();
                }
                else
                    temp = image;
                break;
        }

        // convert initial data type in our data type
        final DataType dataType = DataType.getDataTypeFromDataBufferType(temp.getColorModel().getTransferType());
        // get number of components
        final int numComponents = temp.getRaster().getNumBands();

        // create a compatible image in our format
        final IcyBufferedImage result = new IcyBufferedImage(w, h, numComponents, dataType);

        // copy data from the source image
        result.copyData(temp);

        // in some case we want to restore colormaps from source image
        switch (type)
        {
            case BufferedImage.TYPE_BYTE_BINARY:
            case BufferedImage.TYPE_BYTE_INDEXED:
                if (numComponents == 2)
                    result.setColorMaps(image);
                break;

            case BufferedImage.TYPE_INT_ARGB:
            case BufferedImage.TYPE_INT_ARGB_PRE:
            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
            case BufferedImage.TYPE_4BYTE_ABGR:
                if (numComponents == 4)
                    result.setColorMap(3, LinearColorMap.alpha_, true);
                break;
        }

        return result;
    }

    /**
     * @deprecated Use
     *             {@link LociImporterPlugin#getThumbnailCompatible(IFormatReader, int, int, int)}
     *             instead.
     */
    @Deprecated
    public static IcyBufferedImage createCompatibleThumbnailFrom(IFormatReader reader, int z, int t)
            throws FormatException, IOException
    {
        return LociImporterPlugin.getThumbnailCompatible(reader, z, t, -1);
    }

    /**
     * @deprecated Use {@link LociImporterPlugin#getThumbnail(IFormatReader, int, int, int)}
     *             instead.
     */
    @Deprecated
    public static IcyBufferedImage createThumbnailFrom(IFormatReader reader, int z, int t)
            throws FormatException, IOException
    {
        return LociImporterPlugin.getThumbnail(reader, z, t, -1);
    }

    /**
     * @deprecated Use {@link LociImporterPlugin#getImage(IFormatReader, Rectangle, int, int, int)}
     *             instead.
     */
    @Deprecated
    public static IcyBufferedImage createFrom(IFormatReader reader, int x, int y, int w, int h, int z, int t, int c)
            throws FormatException, IOException
    {
        return LociImporterPlugin.getImage(reader, new Rectangle(x, y, w, h), z, t, c);
    }

    /**
     * @deprecated Use {@link LociImporterPlugin#getImage(IFormatReader, Rectangle, int, int)}
     *             instead.
     */
    @Deprecated
    public static IcyBufferedImage createFrom(IFormatReader reader, int z, int t) throws FormatException, IOException
    {
        return LociImporterPlugin.getImage(reader, null, z, t);
    }

    /**
     * @deprecated Use {@link #IcyBufferedImage(int, int, IcyColorModel)} instead.
     */
    @Deprecated
    public static IcyBufferedImage createEmptyImage(int width, int height, IcyColorModel cm)
    {
        return new IcyBufferedImage(width, height, cm);
    }

    @SuppressWarnings("unused")
    private static final int TYPE_CUSTOM = 0;
    @SuppressWarnings("unused")
    private static final int TYPE_INT_RGB = 1;
    @SuppressWarnings("unused")
    private static final int TYPE_INT_ARGB = 2;
    @SuppressWarnings("unused")
    private static final int TYPE_INT_ARGB_PRE = 3;
    @SuppressWarnings("unused")
    private static final int TYPE_INT_BGR = 4;
    @SuppressWarnings("unused")
    private static final int TYPE_3BYTE_BGR = 5;
    @SuppressWarnings("unused")
    private static final int TYPE_4BYTE_ABGR = 6;
    @SuppressWarnings("unused")
    private static final int TYPE_4BYTE_ABGR_PRE = 7;
    @SuppressWarnings("unused")
    private static final int TYPE_USHORT_565_RGB = 8;
    @SuppressWarnings("unused")
    private static final int TYPE_USHORT_555_RGB = 9;
    @SuppressWarnings("unused")
    private static final int TYPE_BYTE_GRAY = 10;
    @SuppressWarnings("unused")
    private static final int TYPE_USHORT_GRAY = 11;
    @SuppressWarnings("unused")
    private static final int TYPE_BYTE_BINARY = 12;
    @SuppressWarnings("unused")
    private static final int TYPE_BYTE_INDEXED = 13;

    /**
     * @deprecated
     */
    @Deprecated
    public static int TYPE_BYTE = TypeUtil.TYPE_BYTE;
    /**
     * @deprecated
     */
    @Deprecated
    public static int TYPE_DOUBLE = TypeUtil.TYPE_DOUBLE;
    /**
     * @deprecated
     */
    @Deprecated
    public static int TYPE_FLOAT = TypeUtil.TYPE_FLOAT;
    /**
     * @deprecated
     */
    @Deprecated
    public static int TYPE_INT = TypeUtil.TYPE_INT;
    /**
     * @deprecated
     */
    @Deprecated
    public static int TYPE_SHORT = TypeUtil.TYPE_SHORT;
    /**
     * @deprecated
     */
    @Deprecated
    public static int TYPE_UNDEFINED = TypeUtil.TYPE_UNDEFINED;

    /**
     * automatic update of channel bounds
     */
    private boolean autoUpdateChannelBounds;

    /**
     * internal updater
     */
    private final UpdateEventHandler updater;
    /**
     * listeners
     */
    private final List<IcyBufferedImageListener> listeners;

    /**
     * Build an Icy formatted BufferedImage, takes an IcyColorModel and a WritableRaster as input
     * 
     * @param cm
     *        {@link IcyColorModel}
     * @param wr
     *        {@link WritableRaster}
     * @param autoUpdateChannelBounds
     *        If true then channel bounds are automatically calculated.<br>
     */
    protected IcyBufferedImage(IcyColorModel cm, WritableRaster wr, boolean autoUpdateChannelBounds)
    {
        super(cm, wr, false, null);

        updater = new UpdateEventHandler(this, false);
        listeners = new ArrayList<IcyBufferedImageListener>();

        // automatic update of channel bounds
        this.autoUpdateChannelBounds = autoUpdateChannelBounds;

        // add listener to colorModel
        cm.addListener(this);
    }

    /**
     * Create an Icy formatted BufferedImage, takes an IcyColorModel and a WritableRaster as input
     * 
     * @param cm
     *        {@link IcyColorModel}
     * @param wr
     *        {@link WritableRaster}
     */
    protected IcyBufferedImage(IcyColorModel cm, WritableRaster wr)
    {
        this(cm, wr, false);
    }

    /**
     * Create an Icy formatted BufferedImage with specified IcyColorModel, width and height.<br>
     * Private version, {@link IcyColorModel} is directly used internally.
     */
    protected IcyBufferedImage(IcyColorModel cm, int width, int height)
    {
        this(cm, cm.createCompatibleWritableRaster(width, height), false);
    }

    /**
     * Create an Icy formatted BufferedImage with specified colorModel, width, height and input data.<br>
     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[3][640 * 480], true);</code><br>
     * <br>
     * This constructor provides the best performance for massive image creation and computation as it allow you to
     * directly send the data array and disable the channel bounds calculation.
     * 
     * @param cm
     *        the color model
     * @param width
     * @param height
     * @param data
     *        image data<br>
     *        Should be a 2D array with first dimension giving the number of component<br>
     *        and second dimension equals to <code>width * height</code><br>
     *        The array data type specify the internal data type and should match the given color
     *        model parameter.
     * @param autoUpdateChannelBounds
     *        If true then channel bounds are automatically calculated.<br>
     *        When set to false, you have to set bounds manually by calling
     *        {@link #updateChannelsBounds()} or #setC
     */
    protected IcyBufferedImage(IcyColorModel cm, Object[] data, int width, int height, boolean autoUpdateChannelBounds)
    {
        this(cm, cm.createWritableRaster(data, width, height), autoUpdateChannelBounds);

        // data has been modified
        dataChanged();
    }

    /**
     * Create an Icy formatted BufferedImage with specified width, height and input data.<br>
     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[3][640 * 480], true);</code><br>
     * <br>
     * This constructor provides the best performance for massive image creation and computation as
     * it allow you to directly send the data array and disable the channel bounds calculation.
     * 
     * @param width
     * @param height
     * @param data
     *        image data<br>
     *        Should be a 2D array with first dimension giving the number of component<br>
     *        and second dimension equals to <code>width * height</code><br>
     *        The array data type specify the internal data type.
     * @param signed
     *        use signed data for data type
     * @param autoUpdateChannelBounds
     *        If true then channel bounds are automatically calculated.<br>
     *        When set to false, you have to set bounds manually by calling
     *        {@link #updateChannelsBounds()} or #setC
     */
    public IcyBufferedImage(int width, int height, Object[] data, boolean signed, boolean autoUpdateChannelBounds)
    {
        this(IcyColorModel.createInstance(data.length, ArrayUtil.getDataType(data[0], signed)), data, width, height,
                autoUpdateChannelBounds);
    }

    /**
     * Create an Icy formatted BufferedImage with specified width, height and input data.<br>
     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[3][640 * 480]);</code>
     * 
     * @param width
     * @param height
     * @param data
     *        image data<br>
     *        Should be a 2D array with first dimension giving the number of component<br>
     *        and second dimension equals to <code>width * height</code><br>
     *        The array data type specify the internal data type.
     * @param signed
     *        use signed data for data type
     */
    public IcyBufferedImage(int width, int height, Object[] data, boolean signed)
    {
        this(IcyColorModel.createInstance(data.length, ArrayUtil.getDataType(data[0], signed)), data, width, height,
                false);
    }

    /**
     * Create an Icy formatted BufferedImage with specified width, height and input data.<br>
     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[3][640 * 480]);</code>
     * 
     * @param width
     * @param height
     * @param data
     *        image data<br>
     *        Should be a 2D array with first dimension giving the number of component<br>
     *        and second dimension equals to <code>width * height</code><br>
     *        The array data type specify the internal data type.
     */
    public IcyBufferedImage(int width, int height, Object[] data)
    {
        this(width, height, data, false);
    }

    /**
     * Create a single channel Icy formatted BufferedImage with specified width, height and input data.<br>
     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[640 * 480], true);</code><br>
     * <br>
     * This constructor provides the best performance for massive image creation and computation as it allow you to
     * directly send the data array and disable the channel bounds calculation.
     * 
     * @param width
     * @param height
     * @param data
     *        image data array.<br>
     *        The length of the array should be equals to <code>width * height</code>.<br>
     *        The array data type specify the internal data type.
     * @param signed
     *        use signed data for data type
     * @param autoUpdateChannelBounds
     *        If true then channel bounds are automatically calculated.<br>
     *        When set to false, you have to set bounds manually by calling
     *        {@link #updateChannelsBounds()} or #setC
     * @see #IcyBufferedImage(int, int, Object[], boolean, boolean)
     */
    public IcyBufferedImage(int width, int height, Object data, boolean signed, boolean autoUpdateChannelBounds)
    {
        this(width, height, ArrayUtil.encapsulate(data), signed, autoUpdateChannelBounds);
    }

    /**
     * Create a single channel Icy formatted BufferedImage with specified width, height and input
     * data.<br>
     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[640 * 480]);</code>
     * 
     * @param width
     * @param height
     * @param data
     *        image data<br>
     *        The length of the array should be equals to <code>width * height</code>.<br>
     *        The array data type specify the internal data type.
     * @param signed
     *        use signed data for data type
     */
    public IcyBufferedImage(int width, int height, Object data, boolean signed)
    {
        this(width, height, ArrayUtil.encapsulate(data), signed);
    }

    /**
     * Create a single channel Icy formatted BufferedImage with specified width, height and input
     * data.<br>
     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[640 * 480]);</code>
     * 
     * @param width
     * @param height
     * @param data
     *        image data<br>
     *        The length of the array should be equals to <code>width * height</code>.<br>
     *        The array data type specify the internal data type.
     */
    public IcyBufferedImage(int width, int height, Object data)
    {
        this(width, height, ArrayUtil.encapsulate(data));
    }

    /**
     * Create an ICY formatted BufferedImage with specified width, height,<br>
     * number of component and dataType.
     * 
     * @param width
     * @param height
     * @param numComponents
     * @param dataType
     *        image data type {@link DataType}
     */
    public IcyBufferedImage(int width, int height, int numComponents, DataType dataType)
    {
        this(IcyColorModel.createInstance(numComponents, dataType), width, height);
    }

    /**
     * Create an ICY formatted BufferedImage with specified width, height and IcyColorModel
     * type.<br>
     */
    public IcyBufferedImage(int width, int height, IcyColorModel cm)
    {
        this(width, height, cm.getNumComponents(), cm.getDataType_());
    }

    /**
     * @deprecated use {@link #IcyBufferedImage(int, int, int, DataType)} instead
     */
    @Deprecated
    public IcyBufferedImage(int width, int height, int numComponents, int dataType, boolean signed)
    {
        this(IcyColorModel.createInstance(numComponents, dataType, signed), width, height);
    }

    /**
     * @deprecated use {@link #IcyBufferedImage(int, int, int, DataType)} instead
     */
    @Deprecated
    public IcyBufferedImage(int width, int height, int numComponents, int dataType)
    {
        this(IcyColorModel.createInstance(numComponents, dataType, false), width, height);
    }

    /**
     * @return true is channel bounds are automatically updated when image data is modified.
     * @see #setAutoUpdateChannelBounds(boolean)
     */
    public boolean getAutoUpdateChannelBounds()
    {
        return autoUpdateChannelBounds;
    }

    /**
     * If set to <code>true</code> (default) then channel bounds will be automatically recalculated
     * when image data is modified.<br>
     * This can consume some time if you make many updates on a large image.<br>
     * In this case you should do your updates in a {@link #beginUpdate()} ... {@link #endUpdate()}
     * block to avoid
     * severals recalculation.
     */
    public void setAutoUpdateChannelBounds(boolean value)
    {
        if (autoUpdateChannelBounds != value)
        {
            if (value)
                updateChannelsBounds();

            autoUpdateChannelBounds = value;
        }
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, BufferedImage, LUT)}
     *             instead.
     */
    @Deprecated
    public BufferedImage convertToBufferedImage(BufferedImage out, LUT lut)
    {
        return IcyBufferedImageUtil.toBufferedImage(this, out, lut);
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, BufferedImage)}
     *             instead.
     */
    @Deprecated
    public BufferedImage convertToBufferedImage(BufferedImage out)
    {
        return IcyBufferedImageUtil.toBufferedImage(this, out);
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, BufferedImage, LUT)}
     *             instead.
     */
    @Deprecated
    public BufferedImage getARGBImage(LUT lut, BufferedImage out)
    {
        return IcyBufferedImageUtil.getARGBImage(this, lut, out);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, BufferedImage)}
     *             instead.
     */
    @Deprecated
    public BufferedImage getARGBImage(BufferedImage out)
    {
        return IcyBufferedImageUtil.getARGBImage(this, out);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#getARGBImage(IcyBufferedImage, LUT)} instead.
     */
    @Deprecated
    public BufferedImage getARGBImage(LUT lut)
    {
        return IcyBufferedImageUtil.getARGBImage(this, lut);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#getARGBImage(IcyBufferedImage)} instead.
     */
    @Deprecated
    public BufferedImage getARGBImage()
    {
        return IcyBufferedImageUtil.getARGBImage(this);
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#convertType(IcyBufferedImage, DataType, Scaler[])}
     *             instead.
     */
    @Deprecated
    public IcyBufferedImage convertToType(DataType dataType, Scaler scaler)
    {
        return IcyBufferedImageUtil.convertToType(this, dataType, scaler);
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#convertType(IcyBufferedImage,DataType, Scaler[])}
     *             instead.
     */
    @Deprecated
    public IcyBufferedImage convertToType(int dataType, boolean signed, Scaler scaler)
    {
        return IcyBufferedImageUtil.convertToType(this, DataType.getDataType(dataType, signed), scaler);
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#convertToType(IcyBufferedImage, DataType, boolean)}
     *             instead.
     */
    @Deprecated
    public IcyBufferedImage convertToType(DataType dataType, boolean rescale)
    {
        return IcyBufferedImageUtil.convertToType(this, dataType, rescale);
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#convertToType(IcyBufferedImage,DataType, boolean)}
     *             instead
     */
    @Deprecated
    public IcyBufferedImage convertToType(int dataType, boolean signed, boolean rescale)
    {
        return convertToType(DataType.getDataType(dataType, signed), rescale);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, int, LUT)}
     *             instead
     */
    @Deprecated
    public BufferedImage convertToBufferedImage(LUT lut, int imageType)
    {
        return IcyBufferedImageUtil.toBufferedImage(this, imageType, lut);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, int, LUT)}
     *             instead
     */
    @Deprecated
    public BufferedImage convertToBufferedImage(int imageType, LUT lut)
    {
        return IcyBufferedImageUtil.toBufferedImage(this, imageType, lut);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#getCopy(IcyBufferedImage)} instead
     */
    @Deprecated
    public IcyBufferedImage getCopy()
    {
        return IcyBufferedImageUtil.getCopy(this);
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#getSubImage(IcyBufferedImage, int, int, int, int)}
     *             instead
     */
    @Deprecated
    public IcyBufferedImage getSubImageCopy(int x, int y, int w, int h)
    {
        return IcyBufferedImageUtil.getSubImage(this, x, y, w, h);
    }

    /**
     * Not supported on IcyBufferedImage, use getSubImageCopy instead.
     */
    @Deprecated
    @Override
    public IcyBufferedImage getSubimage(int x, int y, int w, int h)
    {
        // IcyBufferedImage doesn't support subImaging (incorrect draw and copy operation)
        throw new UnsupportedOperationException(
                "IcyBufferedImage doesn't support getSubimage method, use getSubImageCopy instead.");

        // return new IcyBufferedImage(getIcyColorModel(), getRaster().createWritableChild(x, y, w,
        // h, 0, 0, null));
    }

    /**
     * Return a single component image corresponding to the component c of current image.<br>
     * This actually create a new image which share its data with internal image
     * so any modifications to one affect the other.<br>
     * if <code>(c == -1)</code> then current image is directly returned<br>
     * if <code>((c == 0) || (sizeC == 1))</code> then current image is directly returned<br>
     * if <code>((c < 0) || (c >= sizeC))</code> then it returns <code>null</code>
     * 
     * @see IcyBufferedImageUtil#extractChannel(IcyBufferedImage, int)
     * @since version 1.0.3.3b
     */
    public IcyBufferedImage getImage(int c)
    {
        if (c == -1)
            return this;

        final int sizeC = getSizeC();

        if ((c < 0) || (c >= sizeC))
            return null;
        if (sizeC == 1)
            return this;

        return new IcyBufferedImage(getWidth(), getHeight(), getDataXY(c), isSignedDataType());
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#extractChannel(IcyBufferedImage, int)} instead.
     */
    @Deprecated
    public IcyBufferedImage extractChannel(int channelNumber)
    {
        return IcyBufferedImageUtil.extractChannel(this, channelNumber);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#extractChannels(IcyBufferedImage, List)} instead.
     */
    @Deprecated
    public IcyBufferedImage extractChannels(List<Integer> channelNumbers)
    {
        return IcyBufferedImageUtil.extractChannels(this, channelNumbers);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#extractChannel(IcyBufferedImage, int)} instead
     */
    @Deprecated
    public IcyBufferedImage extractBand(int bandNumber)
    {
        return IcyBufferedImageUtil.extractChannel(this, bandNumber);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#extractChannels(IcyBufferedImage, List)} instead
     */
    @Deprecated
    public IcyBufferedImage extractBands(List<Integer> bandNumbers)
    {
        return IcyBufferedImageUtil.extractChannels(this, bandNumbers);
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#scale(IcyBufferedImage, int, int, boolean, int, int, IcyBufferedImageUtil.FilterType)}
     *             instead.
     */
    @Deprecated
    public IcyBufferedImage getScaledCopy(int width, int height, boolean resizeContent, int xAlign, int yAlign,
            FilterType filterType)
    {
        return IcyBufferedImageUtil.scale(this, width, height, resizeContent, xAlign, yAlign,
                getNewFilterType(filterType));
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#scale(IcyBufferedImage, int, int, boolean, int, int)}
     *             instead.
     */
    @Deprecated
    public IcyBufferedImage getScaledCopy(int width, int height, boolean resizeContent, int xAlign, int yAlign)
    {
        return IcyBufferedImageUtil.scale(this, width, height, resizeContent, xAlign, yAlign);
    }

    /**
     * @deprecated Uses
     *             {@link IcyBufferedImageUtil#scale(IcyBufferedImage, int, int, IcyBufferedImageUtil.FilterType)}
     *             instead.
     */
    @Deprecated
    public IcyBufferedImage getScaledCopy(int width, int height, FilterType filterType)
    {
        return IcyBufferedImageUtil.scale(this, width, height, getNewFilterType(filterType));
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#scale(IcyBufferedImage, int, int)} instead.
     */
    @Deprecated
    public IcyBufferedImage getScaledCopy(int width, int height)
    {
        return IcyBufferedImageUtil.scale(this, width, height);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#translate(IcyBufferedImage, int, int, int)}
     *             instead.
     */
    @Deprecated
    public void translate(int dx, int dy, int channel)
    {
        IcyBufferedImageUtil.translate(this, dx, dy, channel);
    }

    /**
     * @deprecated Use {@link IcyBufferedImageUtil#translate(IcyBufferedImage, int, int)} instead.
     */
    @Deprecated
    public void translate(int dx, int dy)
    {
        IcyBufferedImageUtil.translate(this, dx, dy);
    }

    /**
     * Get calculated image channel bounds (min and max values)
     */
    protected double[] getCalculatedChannelBounds(int channel)
    {
        final DataType dataType = getDataType_();

        final boolean signed = dataType.isSigned();
        final Object data = getDataXY(channel);

        final double min = ArrayMath.min(data, signed);
        final double max = ArrayMath.max(data, signed);

        return new double[] {min, max};
    }

    /**
     * Adjust specified bounds depending internal data type
     */
    protected double[] adjustBoundsForDataType(double[] bounds)
    {
        double min, max;

        min = bounds[0];
        max = bounds[1];

        // only for integer data type
        if (!isFloatDataType())
        {
            // we force min to 0 if > 0
            if (min > 0d)
                min = 0d;
            // we force max to 0 if < 0
            if (max < 0d)
                max = 0d;
        }

        final DataType dataType = getDataType_();

        switch (dataType.getJavaType())
        {
            case BYTE:
                // return default bounds ([0..255] / [-128..127])
                return dataType.getDefaultBounds();

            case SHORT:
            case INT:
            case LONG:
                min = MathUtil.prevPow2((long) min + 1);
                max = MathUtil.nextPow2Mask((long) max);
                break;

            case FLOAT:
            case DOUBLE:
                // if [min..max] is included in [-1..1]
                if ((min >= -1d) && (max <= 1d))
                {
                    min = MathUtil.prevPow10(min);
                    max = MathUtil.nextPow10(max);
                }
                break;
        }

        return new double[] {min, max};
    }

    /**
     * Get the data type minimum value.
     */
    public double getDataTypeMin()
    {
        return getDataType_().getMinValue();
    }

    /**
     * Get the data type maximum value.
     */
    public double getDataTypeMax()
    {
        return getDataType_().getMaxValue();
    }

    /**
     * Get data type bounds (min and max values)
     */
    public double[] getDataTypeBounds()
    {
        return new double[] {getDataTypeMin(), getDataTypeMax()};
    }

    /**
     * Get the minimum type value for the specified channel.
     */
    public double getChannelTypeMin(int channel)
    {
        return getIcyColorModel().getComponentAbsMinValue(channel);
    }

    /**
     * Get the maximum type value for the specified channel.
     */
    public double getChannelTypeMax(int channel)
    {
        return getIcyColorModel().getComponentAbsMaxValue(channel);
    }

    /**
     * Get type bounds (min and max values) for the specified channel.
     */
    public double[] getChannelTypeBounds(int channel)
    {
        return getIcyColorModel().getComponentAbsBounds(channel);
    }

    /**
     * Get type bounds (min and max values) for all channels.
     */
    public double[][] getChannelsTypeBounds()
    {
        final int sizeC = getSizeC();
        final double[][] result = new double[sizeC][];

        for (int c = 0; c < sizeC; c++)
            result[c] = getChannelTypeBounds(c);

        return result;
    }

    /**
     * Get global type bounds (min and max values) for all channels.
     */
    public double[] getChannelsGlobalTypeBounds()
    {
        final int sizeC = getSizeC();
        final double[] result = getChannelTypeBounds(0);

        for (int c = 1; c < sizeC; c++)
        {
            final double[] bounds = getChannelTypeBounds(c);
            result[0] = Math.min(bounds[0], result[0]);
            result[1] = Math.max(bounds[1], result[1]);
        }

        return result;
    }

    /**
     * @deprecated Use {@link #getChannelsGlobalTypeBounds()} instead.
     */
    @Deprecated
    public double[] getChannelTypeGlobalBounds()
    {
        return getChannelsGlobalTypeBounds();
    }

    /**
     * @deprecated Use {@link #getChannelTypeGlobalBounds()} instead.
     */
    @Deprecated
    public double[] getGlobalChannelTypeBounds()
    {
        return getChannelTypeGlobalBounds();
    }

    /**
     * @deprecated Use {@link #getChannelTypeMin(int)} instead.
     */
    @Deprecated
    public double getComponentAbsMinValue(int component)
    {
        return getChannelTypeMin(component);
    }

    /**
     * @deprecated Use {@link #getChannelTypeMax(int)} instead.
     */
    @Deprecated
    public double getComponentAbsMaxValue(int component)
    {
        return getChannelTypeMax(component);
    }

    /**
     * @deprecated Use {@link #getChannelTypeBounds(int)} instead.
     */
    @Deprecated
    public double[] getComponentAbsBounds(int component)
    {
        return getChannelTypeBounds(component);
    }

    /**
     * @deprecated Use {@link #getChannelsTypeBounds()} instead.
     */
    @Deprecated
    public double[][] getComponentsAbsBounds()
    {
        return getChannelsTypeBounds();
    }

    /**
     * @deprecated Use {@link #getGlobalChannelTypeBounds()} instead.
     */
    @Deprecated
    public double[] getGlobalComponentAbsBounds()
    {
        return getChannelTypeGlobalBounds();
    }

    /**
     * Get the minimum value for the specified channel.
     */
    public double getChannelMin(int channel)
    {
        return getIcyColorModel().getComponentUserMinValue(channel);
    }

    /**
     * Get maximum value for the specified channel.
     */
    public double getChannelMax(int channel)
    {
        return getIcyColorModel().getComponentUserMaxValue(channel);
    }

    /**
     * Get bounds (min and max values) for the specified channel.
     */
    public double[] getChannelBounds(int channel)
    {
        return getIcyColorModel().getComponentUserBounds(channel);
    }

    /**
     * Get bounds (min and max values) for all channels.
     */
    public double[][] getChannelsBounds()
    {
        final int sizeC = getSizeC();
        final double[][] result = new double[sizeC][];

        for (int c = 0; c < sizeC; c++)
            result[c] = getChannelBounds(c);

        return result;
    }

    /**
     * Get global bounds (min and max values) for all channels.
     */
    public double[] getChannelsGlobalBounds()
    {
        final int sizeC = getSizeC();
        final double[] result = new double[2];

        result[0] = Double.MAX_VALUE;
        result[1] = -Double.MAX_VALUE;

        for (int c = 0; c < sizeC; c++)
        {
            final double[] bounds = getChannelBounds(c);

            if (bounds[0] < result[0])
                result[0] = bounds[0];
            if (bounds[1] > result[1])
                result[1] = bounds[1];
        }

        return result;
    }

    /**
     * @deprecated Use {@link #getChannelMin(int)} instead.
     */
    @Deprecated
    public double getComponentUserMinValue(int component)
    {
        return getChannelMin(component);
    }

    /**
     * @deprecated Use {@link #getChannelMax(int)} instead.
     */
    @Deprecated
    public double getComponentUserMaxValue(int component)
    {
        return getChannelMax(component);
    }

    /**
     * @deprecated Use {@link #getChannelBounds(int)} instead.
     */
    @Deprecated
    public double[] getComponentUserBounds(int component)
    {
        return getChannelBounds(component);
    }

    /**
     * @deprecated Use {@link #getChannelsBounds()} instead.
     */
    @Deprecated
    public double[][] getComponentsUserBounds()
    {
        return getChannelsBounds();
    }

    /**
     * Set the preferred data type minimum value for the specified channel.
     */
    public void setChannelTypeMin(int channel, double min)
    {
        getIcyColorModel().setComponentAbsMinValue(channel, min);
    }

    /**
     * Set the preferred data type maximum value for the specified channel.
     */
    public void setChannelTypeMax(int channel, double max)
    {
        getIcyColorModel().setComponentAbsMaxValue(channel, max);
    }

    /**
     * /**
     * Set the preferred data type min and max values for the specified channel.
     */
    public void setChannelTypeBounds(int channel, double min, double max)
    {
        getIcyColorModel().setComponentAbsBounds(channel, min, max);
    }

    /**
     * Set the preferred data type bounds (min and max values) for all channels.
     */
    public void setChannelsTypeBounds(double[][] bounds)
    {
        getIcyColorModel().setComponentsAbsBounds(bounds);
    }

    /**
     * @deprecated Use {@link #setChannelTypeMin(int, double)} instead.
     */
    @Deprecated
    public void setComponentAbsMinValue(int component, double min)
    {
        setChannelTypeMin(component, min);
    }

    /**
     * @deprecated Use {@link #setChannelTypeMax(int, double)} instead.
     */
    @Deprecated
    public void setComponentAbsMaxValue(int component, double max)
    {
        setChannelTypeMax(component, max);
    }

    /**
     * @deprecated Use {@link #setChannelTypeBounds(int, double, double)} instead.
     */
    @Deprecated
    public void setComponentAbsBounds(int component, double[] bounds)
    {
        setChannelTypeBounds(component, bounds[0], bounds[1]);
    }

    /**
     * @deprecated Use {@link #setChannelTypeBounds(int, double, double)} instead.
     */
    @Deprecated
    public void setComponentAbsBounds(int component, double min, double max)
    {
        setChannelTypeBounds(component, min, max);
    }

    /**
     * @deprecated Use {@link #setChannelsTypeBounds(double[][])} instead.
     */
    @Deprecated
    public void setComponentsAbsBounds(double[][] bounds)
    {
        setChannelsTypeBounds(bounds);
    }

    /**
     * Set channel minimum value.
     */
    public void setChannelMin(int channel, double min)
    {
        final IcyColorModel cm = getIcyColorModel();

        if ((min < cm.getComponentAbsMinValue(channel)))
            cm.setComponentAbsMinValue(channel, min);
        cm.setComponentUserMinValue(channel, min);
    }

    /**
     * Set channel maximum value.
     */
    public void setChannelMax(int channel, double max)
    {
        final IcyColorModel cm = getIcyColorModel();

        if ((max > cm.getComponentAbsMaxValue(channel)))
            cm.setComponentAbsMinValue(channel, max);
        cm.setComponentUserMaxValue(channel, max);
    }

    /**
     * Set channel bounds (min and max values)
     */
    public void setChannelBounds(int channel, double min, double max)
    {
        final IcyColorModel cm = getIcyColorModel();
        final double[] typeBounds = cm.getComponentAbsBounds(channel);

        if ((min < typeBounds[0]) || (max > typeBounds[1]))
            cm.setComponentAbsBounds(channel, min, max);
        cm.setComponentUserBounds(channel, min, max);
    }

    /**
     * Set all channel bounds (min and max values)
     */
    public void setChannelsBounds(double[][] bounds)
    {
        // we use the setChannelBounds(..) method so we do range check
        for (int c = 0; c < bounds.length; c++)
        {
            final double[] b = bounds[c];
            setChannelBounds(c, b[0], b[1]);
        }
    }

    /**
     * @deprecated Use {@link #setChannelMin(int, double)} instead.
     */
    @Deprecated
    public void setComponentUserMinValue(int component, double min)
    {
        setChannelMin(component, min);
    }

    /**
     * @deprecated Use {@link #setChannelMax(int, double)} instead.
     */
    @Deprecated
    public void setComponentUserMaxValue(int component, double max)
    {
        setChannelMax(component, max);
    }

    /**
     * @deprecated Use {@link #setChannelBounds(int, double, double)} instead.
     */
    @Deprecated
    public void setComponentUserBounds(int component, double[] bounds)
    {
        setChannelBounds(component, bounds[0], bounds[1]);
    }

    /**
     * @deprecated Use {@link #setChannelBounds(int, double, double)} instead
     */
    @Deprecated
    public void setComponentUserBounds(int component, double min, double max)
    {
        setChannelBounds(component, min, max);
    }

    /**
     * @deprecated Use {@link #setChannelsBounds(double[][])} instead.
     */
    @Deprecated
    public void setComponentsUserBounds(double[][] bounds)
    {
        setChannelsBounds(bounds);
    }

    /**
     * Update channels bounds (min and max values).
     */
    public void updateChannelsBounds()
    {
        final IcyColorModel cm = getIcyColorModel();

        if (cm != null)
        {
            final int sizeC = getSizeC();

            for (int c = 0; c < sizeC; c++)
            {
                // get data type bounds
                final double[] bounds = getCalculatedChannelBounds(c);

                cm.setComponentAbsBounds(c, adjustBoundsForDataType(bounds));
                cm.setComponentUserBounds(c, bounds);

                // we do user bounds adjustment on "non ALPHA" component only
                // if (cm.getColorMap(c).getType() != IcyColorMapType.ALPHA)
                // cm.setComponentUserBounds(c, bounds);
            }
        }
    }

    /**
     * @deprecated Use {@link #updateChannelsBounds()} instead.
     */
    @SuppressWarnings("unused")
    @Deprecated
    public void updateComponentsBounds(boolean updateChannelBounds, boolean adjustByteToo)
    {
        updateChannelsBounds();
    }

    /**
     * @deprecated Use {@link #updateChannelsBounds()} instead.
     */
    @SuppressWarnings("unused")
    @Deprecated
    public void updateComponentsBounds(boolean updateUserBounds)
    {
        updateChannelsBounds();
    }

    /**
     * Return true if point is inside the image
     */
    public boolean isInside(Point p)
    {
        return isInside(p.x, p.y);
    }

    /**
     * Return true if point of coordinate (x, y) is inside the image
     */
    public boolean isInside(int x, int y)
    {
        return (x >= 0) && (x < getSizeX()) && (y >= 0) && (y < getSizeY());
    }

    /**
     * Return true if point of coordinate (x, y) is inside the image
     */
    public boolean isInside(double x, double y)
    {
        return (x >= 0) && (x < getSizeX()) && (y >= 0) && (y < getSizeY());
    }

    /**
     * Return the IcyColorModel
     * 
     * @return IcyColorModel
     */
    public IcyColorModel getIcyColorModel()
    {
        return (IcyColorModel) getColorModel();
    }

    /**
     * Return the data type of this image
     * 
     * @return dataType
     * @see DataType
     */
    public DataType getDataType_()
    {
        return getIcyColorModel().getDataType_();
    }

    /**
     * @deprecated use {@link #getDataType_()} instead
     */
    @Deprecated
    public int getDataType()
    {
        return getIcyColorModel().getDataType();
    }

    /**
     * Return true if this is a float data type image
     */
    public boolean isFloatDataType()
    {
        return getDataType_().isFloat();
    }

    /**
     * Return true if this is a signed data type image
     */
    public boolean isSignedDataType()
    {
        return getDataType_().isSigned();
    }

    /**
     * @deprecated Use {@link #getSizeC()} instead.
     */
    @Deprecated
    public int getNumComponents()
    {
        return getSizeC();
    }

    /**
     * @return the number of components of this image
     */
    public int getSizeC()
    {
        return getColorModel().getNumComponents();
    }

    /**
     * @return the width of the image
     */
    public int getSizeX()
    {
        return getWidth();
    }

    /**
     * @return the height of the image
     */
    public int getSizeY()
    {
        return getHeight();
    }

    /**
     * Return 2D dimension of image {sizeX, sizeY}
     */
    public Dimension getDimension()
    {
        return new Dimension(getSizeX(), getSizeY());
    }

    /**
     * Return 2D bounds of image {0, 0, sizeX, sizeY}
     */
    public Rectangle getBounds()
    {
        return new Rectangle(getSizeX(), getSizeY());
    }

    /**
     * Return the number of sample.<br>
     * This is equivalent to<br>
     * <code>getSizeX() * getSizeY() * getSizeC()</code>
     */
    public int getNumSample()
    {
        return getSizeX() * getSizeY() * getSizeC();
    }

    /**
     * Return the offset for specified (x, y) location
     */
    public int getOffset(int x, int y)
    {
        return (y * getWidth()) + x;
    }

    /**
     * create a compatible LUT for this image.
     * 
     * @param createColorModel
     *        set to <code>true</code> to create a LUT using a new compatible ColorModel else it
     *        will use the image
     *        internal ColorModel
     */
    public LUT createCompatibleLUT(boolean createColorModel)
    {
        final IcyColorModel cm;

        if (createColorModel)
            cm = IcyColorModel.createInstance(getIcyColorModel(), false, false);
        else
            cm = getIcyColorModel();

        return new LUT(cm);
    }

    /**
     * create a compatible LUT for this image
     */
    public LUT createCompatibleLUT()
    {
        return createCompatibleLUT(true);
    }

    /**
     * @deprecated No attached LUT to an image.<br/>
     *             Use {@link #createCompatibleLUT(boolean)} instead.
     */
    @Deprecated
    public LUT getLUT()
    {
        return createCompatibleLUT();
    }

    /**
     * Return a direct reference to internal 2D array data [C][XY]
     */
    public Object getDataXYC()
    {
        switch (getDataType_().getJavaType())
        {
            case BYTE:
                return getDataXYCAsByte();
            case SHORT:
                return getDataXYCAsShort();
            case INT:
                return getDataXYCAsInt();
            case FLOAT:
                return getDataXYCAsFloat();
            case DOUBLE:
                return getDataXYCAsDouble();
            default:
                return null;
        }
    }

    /**
     * Return a direct reference to internal 1D array data [XY] for specified c
     */
    public Object getDataXY(int c)
    {
        switch (getDataType_().getJavaType())
        {
            case BYTE:
                return getDataXYAsByte(c);
            case SHORT:
                return getDataXYAsShort(c);
            case INT:
                return getDataXYAsInt(c);
            case FLOAT:
                return getDataXYAsFloat(c);
            case DOUBLE:
                return getDataXYAsDouble(c);
            default:
                return null;
        }
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
     */
    public Object getDataCopyXYC()
    {
        return getDataCopyXYC(null, 0);
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public Object getDataCopyXYC(Object out, int offset)
    {
        switch (getDataType_().getJavaType())
        {
            case BYTE:
                return getDataCopyXYCAsByte((byte[]) out, offset);
            case SHORT:
                return getDataCopyXYCAsShort((short[]) out, offset);
            case INT:
                return getDataCopyXYCAsInt((int[]) out, offset);
            case FLOAT:
                return getDataCopyXYCAsFloat((float[]) out, offset);
            case DOUBLE:
                return getDataCopyXYCAsDouble((double[]) out, offset);
            default:
                return null;
        }
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c
     */
    public Object getDataCopyXY(int c)
    {
        return getDataCopyXY(c, null, 0);
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public Object getDataCopyXY(int c, Object out, int offset)
    {
        switch (getDataType_().getJavaType())
        {
            case BYTE:
                return getDataCopyXYAsByte(c, (byte[]) out, offset);
            case SHORT:
                return getDataCopyXYAsShort(c, (short[]) out, offset);
            case INT:
                return getDataCopyXYAsInt(c, (int[]) out, offset);
            case FLOAT:
                return getDataCopyXYAsFloat(c, (float[]) out, offset);
            case DOUBLE:
                return getDataCopyXYAsDouble(c, (double[]) out, offset);
            default:
                return null;
        }
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]
     */
    public Object getDataCopyCXY()
    {
        return getDataCopyCXY(null, 0);
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public Object getDataCopyCXY(Object out, int offset)
    {
        switch (getDataType_().getJavaType())
        {
            case BYTE:
                return getDataCopyCXYAsByte((byte[]) out, offset);
            case SHORT:
                return getDataCopyCXYAsShort((short[]) out, offset);
            case INT:
                return getDataCopyCXYAsInt((int[]) out, offset);
            case FLOAT:
                return getDataCopyCXYAsFloat((float[]) out, offset);
            case DOUBLE:
                return getDataCopyCXYAsDouble((double[]) out, offset);
            default:
                return null;
        }

    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position
     */
    public Object getDataCopyC(int x, int y)
    {
        return getDataCopyC(x, y, null, 0);
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public Object getDataCopyC(int x, int y, Object out, int offset)
    {
        switch (getDataType_().getJavaType())
        {
            case BYTE:
                return getDataCopyCAsByte(x, y, (byte[]) out, offset);
            case SHORT:
                return getDataCopyCAsShort(x, y, (short[]) out, offset);
            case INT:
                return getDataCopyCAsInt(x, y, (int[]) out, offset);
            case FLOAT:
                return getDataCopyCAsFloat(x, y, (float[]) out, offset);
            case DOUBLE:
                return getDataCopyCAsDouble(x, y, (double[]) out, offset);
            default:
                return null;
        }
    }

    /**
     * Set internal 1D byte array data ([XY]) for specified component
     */
    public void setDataXY(int c, Object values)
    {
        ArrayUtil.arrayToArray(values, getDataXY(c), getDataType_().isSigned());

        // notify data changed
        dataChanged();
    }

    /**
     * Set 1D array data [C] of specified (x, y) position
     */
    public void setDataC(int x, int y, Object values)
    {
        switch (getDataType_().getJavaType())
        {
            case BYTE:
                setDataCAsByte(x, y, (byte[]) values);
                break;

            case SHORT:
                setDataCAsShort(x, y, (short[]) values);
                break;

            case INT:
                setDataCAsInt(x, y, (int[]) values);
                break;

            case FLOAT:
                setDataCAsFloat(x, y, (float[]) values);
                break;

            case DOUBLE:
                setDataCAsDouble(x, y, (double[]) values);
                break;
        }
    }

    /**
     * Return a direct reference to internal 2D array data [C][XY]
     */
    public byte[][] getDataXYCAsByte()
    {
        return ((DataBufferByte) getRaster().getDataBuffer()).getBankData();
    }

    /**
     * Return a direct reference to internal 2D array data [C][XY]
     */
    public short[][] getDataXYCAsShort()
    {
        final DataBuffer db = getRaster().getDataBuffer();
        if (db instanceof DataBufferUShort)
            return ((DataBufferUShort) db).getBankData();
        return ((DataBufferShort) db).getBankData();
    }

    /**
     * Return a direct reference to internal 2D array data [C][XY]
     */
    public int[][] getDataXYCAsInt()
    {
        return ((DataBufferInt) getRaster().getDataBuffer()).getBankData();
    }

    /**
     * Return a direct reference to internal 2D array data [C][XY]
     */
    public float[][] getDataXYCAsFloat()
    {
        return ((DataBufferFloat) getRaster().getDataBuffer()).getBankData();
    }

    /**
     * Return a direct reference to internal 2D array data [C][XY]
     */
    public double[][] getDataXYCAsDouble()
    {
        return ((DataBufferDouble) getRaster().getDataBuffer()).getBankData();
    }

    /**
     * Return a direct reference to internal 1D array data [XY] for specified c
     */
    public byte[] getDataXYAsByte(int c)
    {
        return ((DataBufferByte) getRaster().getDataBuffer()).getData(c);
    }

    /**
     * Return a direct reference to internal 1D array data [XY] for specified c
     */
    public short[] getDataXYAsShort(int c)
    {
        final DataBuffer db = getRaster().getDataBuffer();
        if (db instanceof DataBufferUShort)
            return ((DataBufferUShort) db).getData(c);
        return ((DataBufferShort) db).getData(c);
    }

    /**
     * Return a direct reference to internal 1D array data [XY] for specified c
     */
    public int[] getDataXYAsInt(int c)
    {
        return ((DataBufferInt) getRaster().getDataBuffer()).getData(c);
    }

    /**
     * Return a direct reference to internal 1D array data [XY] for specified c
     */
    public float[] getDataXYAsFloat(int c)
    {
        return ((DataBufferFloat) getRaster().getDataBuffer()).getData(c);
    }

    /**
     * Return a direct reference to internal 1D array data [XY] for specified c
     */
    public double[] getDataXYAsDouble(int c)
    {
        return ((DataBufferDouble) getRaster().getDataBuffer()).getData(c);
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
     */
    public byte[] getDataCopyXYCAsByte()
    {
        return getDataCopyXYCAsByte(null, 0);
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
     * it's used to store result at the specified offset
     */
    public byte[] getDataCopyXYCAsByte(byte[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final byte[][] banks = ((DataBufferByte) getRaster().getDataBuffer()).getBankData();
        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
        int offset = off;

        for (int c = 0; c < sizeC; c++)
        {
            final byte[] src = banks[c];
            System.arraycopy(src, 0, result, offset, (int) len);
            offset += len;
        }

        return result;
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
     */
    public short[] getDataCopyXYCAsShort()
    {
        return getDataCopyXYCAsShort(null, 0);
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
     * it's used to store result at the specified offset
     */
    public short[] getDataCopyXYCAsShort(short[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final DataBuffer db = getRaster().getDataBuffer();
        final short[][] banks;
        if (db instanceof DataBufferUShort)
            banks = ((DataBufferUShort) db).getBankData();
        else
            banks = ((DataBufferShort) db).getBankData();
        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
        int offset = off;

        for (int c = 0; c < sizeC; c++)
        {
            final short[] src = banks[c];
            System.arraycopy(src, 0, result, offset, (int) len);
            offset += len;
        }

        return result;
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
     */
    public int[] getDataCopyXYCAsInt()
    {
        return getDataCopyXYCAsInt(null, 0);
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
     * it's used to store result at the specified offset
     */
    public int[] getDataCopyXYCAsInt(int[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final int[][] banks = ((DataBufferInt) getRaster().getDataBuffer()).getBankData();
        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
        int offset = off;

        for (int c = 0; c < sizeC; c++)
        {
            final int[] src = banks[c];
            System.arraycopy(src, 0, result, offset, (int) len);
            offset += len;
        }

        return result;
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
     */
    public float[] getDataCopyXYCAsFloat()
    {
        return getDataCopyXYCAsFloat(null, 0);
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
     * it's used to store result at the specified offset
     */
    public float[] getDataCopyXYCAsFloat(float[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final float[][] banks = ((DataBufferFloat) getRaster().getDataBuffer()).getBankData();
        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
        int offset = off;

        for (int c = 0; c < sizeC; c++)
        {
            final float[] src = banks[c];
            System.arraycopy(src, 0, result, offset, (int) len);
            offset += len;
        }

        return result;
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
     */
    public double[] getDataCopyXYCAsDouble()
    {
        return getDataCopyXYCAsDouble(null, 0);
    }

    /**
     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
     * it's used to store result at the specified offset
     */
    public double[] getDataCopyXYCAsDouble(double[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final double[][] banks = ((DataBufferDouble) getRaster().getDataBuffer()).getBankData();
        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
        int offset = off;

        for (int c = 0; c < sizeC; c++)
        {
            final double[] src = banks[c];
            System.arraycopy(src, 0, result, offset, (int) len);
            offset += len;
        }

        return result;
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     */
    public byte[] getDataCopyXYAsByte(int c)
    {
        return getDataCopyXYAsByte(c, null, 0);
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public byte[] getDataCopyXYAsByte(int c, byte[] out, int off)
    {
        final int len = getSizeX() * getSizeY();
        final byte[] src = ((DataBufferByte) getRaster().getDataBuffer()).getData(c);
        final byte[] result = Array1DUtil.allocIfNull(out, len);

        System.arraycopy(src, 0, result, off, len);

        return result;
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     */
    public short[] getDataCopyXYAsShort(int c)
    {
        return getDataCopyXYAsShort(c, null, 0);
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public short[] getDataCopyXYAsShort(int c, short[] out, int off)
    {
        final int len = getSizeX() * getSizeY();
        final DataBuffer db = getRaster().getDataBuffer();
        final short[] src;
        if (db instanceof DataBufferUShort)
            src = ((DataBufferUShort) db).getData(c);
        else
            src = ((DataBufferShort) db).getData(c);
        final short[] result = Array1DUtil.allocIfNull(out, len);

        System.arraycopy(src, 0, result, off, len);

        return result;
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     */
    public int[] getDataCopyXYAsInt(int c)
    {
        return getDataCopyXYAsInt(c, null, 0);
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public int[] getDataCopyXYAsInt(int c, int[] out, int off)
    {
        final int len = getSizeX() * getSizeY();
        final int[] src = ((DataBufferInt) getRaster().getDataBuffer()).getData(c);
        final int[] result = Array1DUtil.allocIfNull(out, len);

        System.arraycopy(src, 0, result, off, len);

        return result;
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     */
    public float[] getDataCopyXYAsFloat(int c)
    {
        return getDataCopyXYAsFloat(c, null, 0);
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public float[] getDataCopyXYAsFloat(int c, float[] out, int off)
    {
        final int len = getSizeX() * getSizeY();
        final float[] src = ((DataBufferFloat) getRaster().getDataBuffer()).getData(c);
        final float[] result = Array1DUtil.allocIfNull(out, len);

        System.arraycopy(src, 0, result, off, len);

        return result;
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     */
    public double[] getDataCopyXYAsDouble(int c)
    {
        return getDataCopyXYAsDouble(c, null, 0);
    }

    /**
     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public double[] getDataCopyXYAsDouble(int c, double[] out, int off)
    {
        final int len = getSizeX() * getSizeY();
        final double[] src = ((DataBufferDouble) getRaster().getDataBuffer()).getData(c);
        final double[] result = Array1DUtil.allocIfNull(out, len);

        System.arraycopy(src, 0, result, off, len);

        return result;
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     */
    public byte[] getDataCopyCXYAsByte()
    {
        return getDataCopyCXYAsByte(null, 0);
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public byte[] getDataCopyCXYAsByte(byte[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final byte[][] banks = ((DataBufferByte) getRaster().getDataBuffer()).getBankData();
        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));

        for (int c = 0; c < sizeC; c++)
        {
            final byte[] src = banks[c];
            int offset = c + off;
            for (int i = 0; i < len; i++, offset += sizeC)
                result[offset] = src[i];
        }

        return result;
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     */
    public short[] getDataCopyCXYAsShort()
    {
        return getDataCopyCXYAsShort(null, 0);
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public short[] getDataCopyCXYAsShort(short[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final DataBuffer db = getRaster().getDataBuffer();
        final short[][] banks;
        if (db instanceof DataBufferUShort)
            banks = ((DataBufferUShort) db).getBankData();
        else
            banks = ((DataBufferShort) db).getBankData();
        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));

        for (int c = 0; c < sizeC; c++)
        {
            final short[] src = banks[c];
            int offset = c + off;
            for (int i = 0; i < len; i++, offset += sizeC)
                result[offset] = src[i];
        }

        return result;
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     */
    public int[] getDataCopyCXYAsInt()
    {
        return getDataCopyCXYAsInt(null, 0);
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public int[] getDataCopyCXYAsInt(int[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final int[][] banks = ((DataBufferInt) getRaster().getDataBuffer()).getBankData();
        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));

        for (int c = 0; c < sizeC; c++)
        {
            final int[] src = banks[c];
            int offset = c + off;
            for (int i = 0; i < len; i++, offset += sizeC)
                result[offset] = src[i];
        }

        return result;
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     */
    public float[] getDataCopyCXYAsFloat()
    {
        return getDataCopyCXYAsFloat(null, 0);
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public float[] getDataCopyCXYAsFloat(float[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final float[][] banks = ((DataBufferFloat) getRaster().getDataBuffer()).getBankData();
        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));

        for (int c = 0; c < sizeC; c++)
        {
            final float[] src = banks[c];
            int offset = c + off;
            for (int i = 0; i < len; i++, offset += sizeC)
                result[offset] = src[i];
        }

        return result;
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     */
    public double[] getDataCopyCXYAsDouble()
    {
        return getDataCopyCXYAsDouble(null, 0);
    }

    /**
     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public double[] getDataCopyCXYAsDouble(double[] out, int off)
    {
        final long sizeC = getSizeC();
        final long len = (long) getSizeX() * (long) getSizeY();
        if ((len * sizeC) >= Integer.MAX_VALUE)
            throw new TooLargeArrayException();

        final double[][] banks = ((DataBufferDouble) getRaster().getDataBuffer()).getBankData();
        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));

        for (int c = 0; c < sizeC; c++)
        {
            final double[] src = banks[c];
            int offset = c + off;
            for (int i = 0; i < len; i++, offset += sizeC)
                result[offset] = src[i];
        }

        return result;
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position
     */
    public byte[] getDataCopyCAsByte(int x, int y)
    {
        return getDataCopyCAsByte(x, y, null, 0);
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public byte[] getDataCopyCAsByte(int x, int y, byte[] out, int off)
    {
        final int sizeC = getSizeC();
        final int offset = x + (y * getWidth());
        final byte[][] data = ((DataBufferByte) getRaster().getDataBuffer()).getBankData();
        final byte[] result = Array1DUtil.allocIfNull(out, sizeC);

        for (int c = 0; c < sizeC; c++)
            // ignore band offset as it's always 0 here
            result[c + off] = data[c][offset];

        return result;
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position
     */
    public short[] getDataCopyCAsShort(int x, int y)
    {
        return getDataCopyCAsShort(x, y, null, 0);
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public short[] getDataCopyCAsShort(int x, int y, short[] out, int off)
    {
        final int sizeC = getSizeC();
        final int offset = x + (y * getWidth());
        final DataBuffer db = getRaster().getDataBuffer();
        final short[][] data;
        if (db instanceof DataBufferUShort)
            data = ((DataBufferUShort) db).getBankData();
        else
            data = ((DataBufferShort) db).getBankData();
        final short[] result = Array1DUtil.allocIfNull(out, sizeC);

        for (int c = 0; c < sizeC; c++)
            // ignore band offset as it's always 0 here
            result[c + off] = data[c][offset];

        return result;
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position
     */
    public int[] getDataCopyCAsInt(int x, int y)
    {
        return getDataCopyCAsInt(x, y, null, 0);
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public int[] getDataCopyCAsInt(int x, int y, int[] out, int off)
    {
        final int sizeC = getSizeC();
        final int offset = x + (y * getWidth());
        final int[][] data = ((DataBufferInt) getRaster().getDataBuffer()).getBankData();
        final int[] result = Array1DUtil.allocIfNull(out, sizeC);

        for (int c = 0; c < sizeC; c++)
            // ignore band offset as it's always 0 here
            result[c + off] = data[c][offset];

        return result;
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position
     */
    public float[] getDataCopyCAsFloat(int x, int y)
    {
        return getDataCopyCAsFloat(x, y, null, 0);
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public float[] getDataCopyCAsFloat(int x, int y, float[] out, int off)
    {
        final int sizeC = getSizeC();
        final int offset = x + (y * getWidth());
        final float[][] data = ((DataBufferFloat) getRaster().getDataBuffer()).getBankData();
        final float[] result = Array1DUtil.allocIfNull(out, sizeC);

        for (int c = 0; c < sizeC; c++)
            // ignore band offset as it's always 0 here
            result[c + off] = data[c][offset];

        return result;
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position
     */
    public double[] getDataCopyCAsDouble(int x, int y)
    {
        return getDataCopyCAsDouble(x, y, null, 0);
    }

    /**
     * Return a 1D array data copy [C] of specified (x, y) position<br>
     * If (out != null) then it's used to store result at the specified offset
     */
    public double[] getDataCopyCAsDouble(int x, int y, double[] out, int off)
    {
        final int sizeC = getSizeC();
        final int offset = x + (y * getWidth());
        final double[][] data = ((DataBufferDouble) getRaster().getDataBuffer()).getBankData();
        final double[] result = Array1DUtil.allocIfNull(out, sizeC);

        for (int c = 0; c < sizeC; c++)
            // ignore band offset as it's always 0 here
            result[c + off] = data[c][offset];

        return result;
    }

    /**
     * Set internal 1D byte array data ([XY]) for specified component
     */
    public void setDataXYAsByte(int c, byte[] values)
    {
        System.arraycopy(values, 0, getDataXYAsByte(c), 0, getSizeX() * getSizeY());

        // notify data changed
        dataChanged();
    }

    /**
     * Set internal 1D byte array data ([XY]) for specified component
     */
    public void setDataXYAsShort(int c, short[] values)
    {
        System.arraycopy(values, 0, getDataXYAsShort(c), 0, getSizeX() * getSizeY());

        // notify data changed
        dataChanged();
    }

    /**
     * Set internal 1D byte array data ([XY]) for specified component
     */
    public void setDataXYAsInt(int c, int[] values)
    {
        System.arraycopy(values, 0, getDataXYAsInt(c), 0, getSizeX() * getSizeY());

        // notify data changed
        dataChanged();
    }

    /**
     * Set internal 1D byte array data ([XY]) for specified component
     */
    public void setDataXYAsFloat(int c, float[] values)
    {
        System.arraycopy(values, 0, getDataXYAsFloat(c), 0, getSizeX() * getSizeY());

        // notify data changed
        dataChanged();
    }

    /**
     * Set internal 1D byte array data ([XY]) for specified component
     */
    public void setDataXYAsDouble(int c, double[] values)
    {
        System.arraycopy(values, 0, getDataXYAsDouble(c), 0, getSizeX() * getSizeY());

        // notify data changed
        dataChanged();
    }

    /**
     * Set 1D array data [C] of specified (x, y) position
     */
    public void setDataCAsByte(int x, int y, byte[] values)
    {
        final int offset = x + (y * getWidth());
        final int len = values.length;
        final byte[][] data = ((DataBufferByte) getRaster().getDataBuffer()).getBankData();

        for (int comp = 0; comp < len; comp++)
            // ignore band offset as it's always 0 here
            data[comp][offset] = values[comp];

        // notify data changed
        dataChanged();
    }

    /**
     * Set 1D array data [C] of specified (x, y) position
     */
    public void setDataCAsShort(int x, int y, short[] values)
    {
        final int offset = x + (y * getWidth());
        final int len = values.length;
        final DataBuffer db = getRaster().getDataBuffer();
        final short[][] data;
        if (db instanceof DataBufferUShort)
            data = ((DataBufferUShort) db).getBankData();
        else
            data = ((DataBufferShort) db).getBankData();

        for (int comp = 0; comp < len; comp++)
            // ignore band offset as it's always 0 here
            data[comp][offset] = values[comp];

        // notify data changed
        dataChanged();
    }

    /**
     * Set 1D array data [C] of specified (x, y) position
     */
    public void setDataCAsInt(int x, int y, int[] values)
    {
        final int offset = x + (y * getWidth());
        final int len = values.length;
        final int[][] data = ((DataBufferInt) getRaster().getDataBuffer()).getBankData();

        for (int comp = 0; comp < len; comp++)
            // ignore band offset as it's always 0 here
            data[comp][offset] = values[comp];

        // notify data changed
        dataChanged();
    }

    /**
     * Set 1D array data [C] of specified (x, y) position
     */
    public void setDataCAsFloat(int x, int y, float[] values)
    {
        final int offset = x + (y * getWidth());
        final int len = values.length;
        final float[][] data = ((DataBufferFloat) getRaster().getDataBuffer()).getBankData();

        for (int comp = 0; comp < len; comp++)
            // ignore band offset as it's always 0 here
            data[comp][offset] = values[comp];

        // notify data changed
        dataChanged();
    }

    /**
     * Set 1D array data [C] of specified (x, y) position
     */
    public void setDataCAsDouble(int x, int y, double[] values)
    {
        final int offset = x + (y * getWidth());
        final int len = values.length;
        final double[][] data = ((DataBufferDouble) getRaster().getDataBuffer()).getBankData();

        for (int comp = 0; comp < len; comp++)
            // ignore band offset as it's always 0 here
            data[comp][offset] = values[comp];

        // notify data changed
        dataChanged();
    }

    /**
     * Return the value located at (x, y, c) position as a double
     * whatever is the internal data type
     */
    public double getData(int x, int y, int c)
    {
        return Array1DUtil.getValue(getDataXY(c), getOffset(x, y), getDataType_());
    }

    /**
     * Set the value located at (x, y, c) position as a double
     * whatever is the internal data type
     */
    public void setData(int x, int y, int c, double value)
    {
        Array1DUtil.setValue(getDataXY(c), getOffset(x, y), getDataType_(), value);

        // notify data changed
        dataChanged();
    }

    /**
     * Return the value located at (x, y, c) position
     */
    public byte getDataAsByte(int x, int y, int c)
    {
        // ignore band offset as it's always 0 here
        return (((DataBufferByte) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())];
    }

    /**
     * Set the value located at (x, y, c) position
     */
    public void setDataAsByte(int x, int y, int c, byte value)
    {
        // ignore band offset as it's always 0 here
        (((DataBufferByte) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())] = value;

        // notify data changed
        dataChanged();
    }

    /**
     * Return the value located at (x, y, c) position
     */
    public short getDataAsShort(int x, int y, int c)
    {
        // ignore band offset as it's always 0 here
        final DataBuffer db = getRaster().getDataBuffer();

        if (db instanceof DataBufferUShort)
            return (((DataBufferUShort) db).getData(c))[x + (y * getWidth())];

        return (((DataBufferShort) db).getData(c))[x + (y * getWidth())];
    }

    /**
     * Set the value located at (x, y, c) position
     */
    public void setDataAsShort(int x, int y, int c, short value)
    {
        final DataBuffer db = getRaster().getDataBuffer();
        if (db instanceof DataBufferUShort)
            // ignore band offset as it's always 0 here
            (((DataBufferUShort) db).getData(c))[x + (y * getWidth())] = value;
        else
            (((DataBufferShort) db).getData(c))[x + (y * getWidth())] = value;

        // notify data changed
        dataChanged();
    }

    /**
     * Return the value located at (x, y, c) position
     */
    public int getDataAsInt(int x, int y, int c)
    {
        // ignore band offset as it's always 0 here
        return (((DataBufferInt) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())];
    }

    /**
     * Set the value located at (x, y, c) position
     */
    public void setDataAsInt(int x, int y, int c, int value)
    {
        // ignore band offset as it's always 0 here
        (((DataBufferInt) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())] = value;

        // notify data changed
        dataChanged();
    }

    /**
     * Return the value located at (x, y, c) position
     */
    public float getDataAsFloat(int x, int y, int c)
    {
        // ignore band offset as it's always 0 here
        return (((DataBufferFloat) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())];
    }

    /**
     * Set the value located at (x, y, c) position
     */
    public void setDataAsFloat(int x, int y, int c, float value)
    {
        // ignore band offset as it's always 0 here
        (((DataBufferFloat) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())] = value;

        // notify data changed
        dataChanged();
    }

    /**
     * Return the value located at (x, y, c) position
     */
    public double getDataAsDouble(int x, int y, int c)
    {
        // ignore band offset as it's always 0 here
        return (((DataBufferDouble) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())];
    }

    /**
     * Set the value located at (x, y, c) position
     */
    public void setDataAsDouble(int x, int y, int c, double value)
    {
        // ignore band offset as it's always 0 here
        (((DataBufferDouble) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())] = value;

        // notify data changed
        dataChanged();
    }

    /**
     * Same as getRGB but by using the specified LUT instead of internal one
     * 
     * @see java.awt.image.BufferedImage#getRGB(int, int)
     */
    public int getRGB(int x, int y, LUT lut)
    {
        return getIcyColorModel().getRGB(getRaster().getDataElements(x, y, null), lut);
    }

    /**
     * Internal copy data from an icy image (notify data changed)
     * 
     * @param srcImage
     *        source icy image
     * @param srcRect
     *        source region
     * @param dstPt
     *        destination X,Y position
     * @param srcChannel
     *        source channel
     * @param dstChannel
     *        destination channel
     */
    protected void fastCopyData(IcyBufferedImage srcImage, Rectangle srcRect, Point dstPt, int srcChannel,
            int dstChannel)
    {
        final int srcSizeX = srcImage.getSizeX();
        final int dstSizeX = getSizeX();

        // limit to source image size
        Rectangle adjSrcRect = srcRect.intersection(new Rectangle(srcSizeX, srcImage.getSizeY()));
        // negative destination x position
        if (dstPt.x < 0)
            // adjust source rect
            adjSrcRect.x += -dstPt.x;
        // negative destination y position
        if (dstPt.y < 0)
            // adjust source rect
            adjSrcRect.y += -dstPt.y;

        final Rectangle dstRect = new Rectangle(dstPt.x, dstPt.y, adjSrcRect.width, adjSrcRect.height);
        // limit to destination image size
        final Rectangle adjDstRect = dstRect.intersection(new Rectangle(dstSizeX, getSizeY()));

        final int w = Math.min(adjSrcRect.width, adjDstRect.width);
        final int h = Math.min(adjSrcRect.height, adjDstRect.height);

        // nothing to copy
        if ((w == 0) || (h == 0))
            return;

        final boolean signed = srcImage.getDataType_().isSigned();

        final Object src = srcImage.getDataXY(srcChannel);
        final Object dst = getDataXY(dstChannel);

        int srcOffset = adjSrcRect.x + (adjSrcRect.y * srcSizeX);
        int dstOffset = adjDstRect.x + (adjDstRect.y * dstSizeX);

        for (int y = 0; y < h; y++)
        {
            ArrayUtil.arrayToArray(src, srcOffset, dst, dstOffset, w, signed);
            srcOffset += srcSizeX;
            dstOffset += dstSizeX;
        }

        // notify data changed
        dataChanged();
    }

    /**
     * Internal copy data from a compatible image (notify data changed)
     * 
     * @param srcImage
     *        source image
     */
    protected void internalCopyData(int srcChannel, int dstChannel, DataBuffer src_db, DataBuffer dst_db, int[] indices,
            int[] band_offsets, int[] bank_offsets, int scanlineStride_src, int pixelStride_src, int maxX, int maxY,
            int decOffsetSrc)
    {
        final int scanlineStride_dst = getSizeX();

        final int bank = indices[srcChannel];
        final int offset = band_offsets[srcChannel] + bank_offsets[bank] - decOffsetSrc;

        switch (getDataType_().getJavaType())
        {
            case BYTE:
            {
                final byte[] src;
                final byte[] dst = ((DataBufferByte) dst_db).getData(dstChannel);

                // LOCI use its own buffer classes
                if (src_db instanceof SignedByteBuffer)
                    src = ((SignedByteBuffer) src_db).getData(bank);
                else
                    src = ((DataBufferByte) src_db).getData(bank);

                int offset_src = offset;
                int offset_dst = 0;
                for (int y = 0; y < maxY; y++)
                {
                    int offset_src_pix = offset_src;
                    int offset_dst_pix = offset_dst;

                    for (int x = 0; x < maxX; x++)
                    {
                        dst[offset_dst_pix] = src[offset_src_pix];
                        offset_src_pix += pixelStride_src;
                        offset_dst_pix++;
                    }

                    offset_src += scanlineStride_src;
                    offset_dst += scanlineStride_dst;
                }
                break;
            }

            case SHORT:
            {
                final short[] src;
                final short[] dst;

                // LOCI use its own buffer classes
                if (src_db instanceof SignedShortBuffer)
                    src = ((SignedShortBuffer) src_db).getData(bank);
                else if (src_db instanceof DataBufferShort)
                    src = ((DataBufferShort) src_db).getData(bank);
                else
                    src = ((DataBufferUShort) src_db).getData(bank);

                if (dst_db instanceof DataBufferShort)
                    dst = ((DataBufferShort) dst_db).getData(dstChannel);
                else
                    dst = ((DataBufferUShort) dst_db).getData(dstChannel);

                int offset_src = offset;
                int offset_dst = 0;
                for (int y = 0; y < maxY; y++)
                {
                    int offset_src_pix = offset_src;
                    int offset_dst_pix = offset_dst;

                    for (int x = 0; x < maxX; x++)
                    {
                        dst[offset_dst_pix] = src[offset_src_pix];
                        offset_src_pix += pixelStride_src;
                        offset_dst_pix++;
                    }

                    offset_src += scanlineStride_src;
                    offset_dst += scanlineStride_dst;
                }
                break;
            }

            case INT:
            {
                final int[] src;
                final int[] dst = ((DataBufferInt) dst_db).getData(dstChannel);

                // LOCI use its own buffer classes
                if (src_db instanceof UnsignedIntBuffer)
                    src = ((UnsignedIntBuffer) src_db).getData(bank);
                else
                    src = ((DataBufferInt) src_db).getData(bank);

                int offset_src = offset;
                int offset_dst = 0;
                for (int y = 0; y < maxY; y++)
                {
                    int offset_src_pix = offset_src;
                    int offset_dst_pix = offset_dst;

                    for (int x = 0; x < maxX; x++)
                    {
                        dst[offset_dst_pix] = src[offset_src_pix];
                        offset_src_pix += pixelStride_src;
                        offset_dst_pix++;
                    }

                    offset_src += scanlineStride_src;
                    offset_dst += scanlineStride_dst;
                }
                break;
            }

            case FLOAT:
            {
                final float[] src = ((DataBufferFloat) src_db).getData(bank);
                final float[] dst = ((DataBufferFloat) dst_db).getData(dstChannel);

                int offset_src = offset;
                int offset_dst = 0;
                for (int y = 0; y < maxY; y++)
                {
                    int offset_src_pix = offset_src;
                    int offset_dst_pix = offset_dst;

                    for (int x = 0; x < maxX; x++)
                    {
                        dst[offset_dst_pix] = src[offset_src_pix];
                        offset_src_pix += pixelStride_src;
                        offset_dst_pix++;
                    }

                    offset_src += scanlineStride_src;
                    offset_dst += scanlineStride_dst;
                }
                break;
            }

            case DOUBLE:
            {
                final double[] src = ((DataBufferDouble) src_db).getData(bank);
                final double[] dst = ((DataBufferDouble) dst_db).getData(dstChannel);

                int offset_src = offset;
                int offset_dst = 0;
                for (int y = 0; y < maxY; y++)
                {
                    int offset_src_pix = offset_src;
                    int offset_dst_pix = offset_dst;

                    for (int x = 0; x < maxX; x++)
                    {
                        dst[offset_dst_pix] = src[offset_src_pix];
                        offset_src_pix += pixelStride_src;
                        offset_dst_pix++;
                    }

                    offset_src += scanlineStride_src;
                    offset_dst += scanlineStride_dst;
                }
                break;
            }
        }
    }

    /**
     * Copy channel data from a compatible sample model and writable raster (notify data changed).
     * 
     * @param sampleModel
     *        source sample model
     * @param raster
     *        source writable raster to read data from
     * @param srcChannel
     *        source channel (-1 for all channels)
     * @param dstChannel
     *        destination channel (only significant if source channel != -1)
     * @return <code>true</code> if the copy operation succeed, <code>false</code> otherwise
     */
    public boolean copyData(ComponentSampleModel sampleModel, WritableRaster raster, int srcChannel, int dstChannel)
    {
        // not compatible sample model
        if (DataType.getDataTypeFromDataBufferType(sampleModel.getDataType()) != getDataType_())
            return false;

        final DataBuffer src_db = raster.getDataBuffer();
        final DataBuffer dst_db = getRaster().getDataBuffer();
        final int[] indices = sampleModel.getBankIndices();
        final int[] band_offsets = sampleModel.getBandOffsets();
        final int[] bank_offsets = src_db.getOffsets();
        final int scanlineStride_src = sampleModel.getScanlineStride();
        final int pixelStride_src = sampleModel.getPixelStride();
        final int maxX = Math.min(getSizeX(), sampleModel.getWidth());
        final int maxY = Math.min(getSizeY(), sampleModel.getHeight());
        final int decOffsetSrc = raster.getSampleModelTranslateX()
                + (raster.getSampleModelTranslateY() * scanlineStride_src);

        // all channels
        if (srcChannel == -1)
        {
            final int numBands = sampleModel.getNumBands();

            for (int band = 0; band < numBands; band++)
                internalCopyData(band, band, src_db, dst_db, indices, band_offsets, bank_offsets, scanlineStride_src,
                        pixelStride_src, maxX, maxY, decOffsetSrc);
        }
        else
        {
            internalCopyData(srcChannel, dstChannel, src_db, dst_db, indices, band_offsets, bank_offsets,
                    scanlineStride_src, pixelStride_src, maxX, maxY, decOffsetSrc);
        }

        // notify data changed
        dataChanged();

        return true;
    }

    /**
     * Copy data to specified location from an data array.
     * 
     * @param data
     *        source data array (should be same type than image data type)
     * @param dataDim
     *        source data dimension (array length should be >= Dimension.width * Dimension.heigth)
     * @param signed
     *        if the source data array should be considered as signed data (meaningful for integer
     *        data type only)
     * @param dstPt
     *        destination X,Y position (assume [0,0] if null)
     * @param dstChannel
     *        destination channel
     */
    public void copyData(Object data, Dimension dataDim, boolean signed, Point dstPt, int dstChannel)
    {
        if ((data == null) || (dataDim == null))
            return;

        // source image size
        final Rectangle adjSrcRect = new Rectangle(dataDim);
        // negative destination x position
        if (dstPt.x < 0)
            // adjust source rect
            adjSrcRect.x += -dstPt.x;
        // negative destination y position
        if (dstPt.y < 0)
            // adjust source rect
            adjSrcRect.y += -dstPt.y;

        final Rectangle dstRect = new Rectangle(dstPt.x, dstPt.y, adjSrcRect.width, adjSrcRect.height);
        // limit to destination image size
        final Rectangle adjDstRect = dstRect.intersection(new Rectangle(getSizeX(), getSizeY()));

        final int w = Math.min(adjSrcRect.width, adjDstRect.width);
        final int h = Math.min(adjSrcRect.height, adjDstRect.height);

        // nothing to copy
        if ((w == 0) || (h == 0))
            return;

        final Object dst = getDataXY(dstChannel);
        final int srcSizeX = dataDim.width;
        final int dstSizeX = getSizeX();

        int srcOffset = adjSrcRect.x + (adjSrcRect.y * srcSizeX);
        int dstOffset = adjDstRect.x + (adjDstRect.y * dstSizeX);

        for (int y = 0; y < h; y++)
        {
            // do data copy (and conversion if needed)
            ArrayUtil.arrayToArray(data, srcOffset, dst, dstOffset, w, signed);
            srcOffset += srcSizeX;
            dstOffset += dstSizeX;
        }

        // notify data changed
        dataChanged();
    }

    /**
     * Copy data from an image (notify data changed)
     * 
     * @param srcImage
     *        source image
     * @param srcRect
     *        source region to copy (assume whole image if null)
     * @param dstPt
     *        destination X,Y position (assume [0,0] if null)
     * @param srcChannel
     *        source channel (-1 for all channels)
     * @param dstChannel
     *        destination channel (only significant if source channel != -1)
     */
    public void copyData(IcyBufferedImage srcImage, Rectangle srcRect, Point dstPt, int srcChannel, int dstChannel)
    {
        if (srcImage == null)
            return;

        final Rectangle adjSrcRect;
        final Point adjDstPt;

        if (srcRect == null)
            adjSrcRect = new Rectangle(srcImage.getSizeX(), srcImage.getSizeY());
        else
            adjSrcRect = srcRect;
        if (dstPt == null)
            adjDstPt = new Point(0, 0);
        else
            adjDstPt = dstPt;

        // copy all possible components
        if (srcChannel == -1)
        {
            final int sizeC = Math.min(srcImage.getSizeC(), getSizeC());

            beginUpdate();
            try
            {
                for (int c = 0; c < sizeC; c++)
                    fastCopyData(srcImage, adjSrcRect, adjDstPt, c, c);
            }
            finally
            {
                endUpdate();
            }
        }
        else
            fastCopyData(srcImage, adjSrcRect, adjDstPt, srcChannel, dstChannel);
    }

    /**
     * Copy data from an image (notify data changed)
     * 
     * @param srcImage
     *        source image
     * @param srcRect
     *        source region to copy (assume whole image if null)
     * @param dstPt
     *        destination (assume [0,0] if null)
     */
    public void copyData(IcyBufferedImage srcImage, Rectangle srcRect, Point dstPt)
    {
        if (srcImage == null)
            return;

        copyData(srcImage, srcRect, dstPt, -1, 0);
    }

    /**
     * Copy data from an image (notify data changed)
     * 
     * @param srcImage
     *        source image
     * @param srcChannel
     *        source channel to copy (-1 for all channels)
     * @param dstChannel
     *        destination channel to receive data (only significant if source channel != -1)
     */
    public void copyData(BufferedImage srcImage, int srcChannel, int dstChannel)
    {
        if (srcImage == null)
            return;

        if (srcImage instanceof IcyBufferedImage)
            copyData(((IcyBufferedImage) srcImage), null, null, srcChannel, dstChannel);
        else
        {
            final boolean done;

            // try to use faster copy for compatible image
            if (srcImage.getSampleModel() instanceof ComponentSampleModel)
                done = copyData((ComponentSampleModel) srcImage.getSampleModel(), srcImage.getRaster(), srcChannel,
                        dstChannel);
            else
                done = false;

            if (!done)
            {
                // image not compatible, use generic (and slow) data copy
                srcImage.copyData(getRaster());
                // notify data changed
                dataChanged();
            }
        }
    }

    /**
     * Copy data from an image (notify data changed)
     * 
     * @param srcImage
     *        source image
     */
    public void copyData(BufferedImage srcImage)
    {
        copyData(srcImage, -1, -1);
    }

    /**
     * Return raw data component as an array of byte
     * 
     * @param c
     *        component index
     * @param out
     *        output array (can be null)
     * @param offset
     *        output offset
     * @param little
     *        little endian order
     */
    public byte[] getRawData(int c, byte[] out, int offset, boolean little)
    {
        // alloc output array if needed
        final byte[] result = Array1DUtil.allocIfNull(out,
                offset + (getSizeX() * getSizeY() * getDataType_().getSize()));

        return ByteArrayConvert.toByteArray(getDataXY(c), 0, result, offset, little);
    }

    /**
     * Return raw data component as an array of byte
     * 
     * @param c
     *        component index
     * @param little
     *        little endian order
     */
    public byte[] getRawData(int c, boolean little)
    {
        return getRawData(c, null, 0, little);
    }

    /**
     * Return raw data for all components as an array of byte
     * 
     * @param out
     *        output array (can be null)
     * @param offset
     *        output offset
     * @param little
     *        little endian order
     */
    public byte[] getRawData(byte[] out, int offset, boolean little)
    {
        final int sizeXY = getSizeX() * getSizeY();
        final int sizeC = getSizeC();
        final int sizeType = getDataType_().getSize();

        // alloc output array if needed
        final byte[] result = Array1DUtil.allocIfNull(out, offset + (sizeC * sizeXY * sizeType));

        int outOff = offset;
        for (int c = 0; c < sizeC; c++)
        {
            getRawData(c, result, outOff, little);
            outOff += sizeXY * sizeType;
        }

        return result;
    }

    /**
     * Return raw data for all components as an array of byte
     * 
     * @param little
     *        little endian order
     */
    public byte[] getRawData(boolean little)
    {
        return getRawData(null, 0, little);
    }

    /**
     * Set raw data component from an array of byte (notify data changed)
     * 
     * @param c
     *        component index
     * @param data
     *        data as byte array
     * @param offset
     *        input offset
     * @param little
     *        little endian order
     */
    public void setRawData(int c, byte[] data, int offset, boolean little)
    {
        if (data == null)
            return;

        ByteArrayConvert.byteArrayTo(data, offset, getDataXY(c), 0, -1, little);

        // notify data changed
        dataChanged();
    }

    /**
     * Set raw data component from an array of byte (notify data changed)
     * 
     * @param c
     *        component index
     * @param data
     *        data as byte array
     * @param little
     *        little endian order
     */
    public void setRawData(int c, byte[] data, boolean little)
    {
        setRawData(c, data, 0, little);
    }

    /**
     * Set raw data for all components from an array of byte (notify data changed).<br/>
     * Data are arranged in the following dimension order: XYC
     * 
     * @param data
     *        data as byte array
     * @param offset
     *        input offset
     * @param little
     *        little endian order
     */
    public void setRawData(byte[] data, int offset, boolean little)
    {
        if (data == null)
            return;

        final int sizeXY = getSizeX() * getSizeY();
        final int sizeC = getSizeC();
        final int sizeType = getDataType_().getSize();

        beginUpdate();
        try
        {
            int inOff = offset;
            for (int c = 0; c < sizeC; c++)
            {
                setRawData(c, data, inOff, little);
                inOff += sizeXY * sizeType;
            }
        }
        finally
        {
            endUpdate();
        }
    }

    /**
     * Set raw data for all components from an array of byte (notify data changed)
     * 
     * @param data
     *        data as byte array
     * @param little
     *        little endian order
     */
    public void setRawData(byte[] data, boolean little)
    {
        setRawData(data, 0, little);
    }

    /**
     * Return the colormap of the specified channel.
     */
    public IcyColorMap getColorMap(int channel)
    {
        return getIcyColorModel().getColorMap(channel);
    }

    /**
     * @deprecated Use {@link #getColorMap(int)} instead (different case).
     */
    @Deprecated
    public IcyColorMap getColormap(int channel)
    {
        return getColorMap(channel);
    }

    /**
     * @deprecated Use {@link #setColorMaps(BufferedImage)} instead.
     */
    @Deprecated
    public void copyColormap(BufferedImage srcImage)
    {
        setColorMaps(srcImage);
    }

    /**
     * Set colormaps from specified image.
     */
    public void setColorMaps(BufferedImage srcImage)
    {
        getIcyColorModel().setColorMaps(srcImage.getColorModel());
    }

    /**
     * @deprecated Use {@link #setColorMaps(BufferedImage)} instead (different case).
     */
    @Deprecated
    public void setColormaps(BufferedImage srcImage)
    {
        setColorMaps(srcImage);
    }

    /**
     * Set the colormap for the specified channel.
     * 
     * @param channel
     *        channel we want to set the colormap
     * @param map
     *        source colorspace to copy
     * @param setAlpha
     *        also set the alpha information
     */
    public void setColorMap(int channel, IcyColorMap map, boolean setAlpha)
    {
        getIcyColorModel().setColorMap(channel, map, setAlpha);
    }

    /**
     * @deprecated Use {@link #setColorMap(int, IcyColorMap, boolean)} instead.
     */
    @Deprecated
    public void setColormap(int channel, IcyColorMap map)
    {
        setColorMap(channel, map, true);
    }

    /**
     * notify image data has changed
     */
    public void dataChanged()
    {
        updater.changed(new IcyBufferedImageEvent(this, IcyBufferedImageEventType.DATA_CHANGED));
    }

    /**
     * notify image colorMap has changed
     */
    protected void colormapChanged(int component)
    {
        updater.changed(new IcyBufferedImageEvent(this, IcyBufferedImageEventType.COLORMAP_CHANGED, component));
    }

    /**
     * notify image channels bounds has changed
     */
    public void channelBoundsChanged(int channel)
    {
        updater.changed(new IcyBufferedImageEvent(this, IcyBufferedImageEventType.BOUNDS_CHANGED, channel));
    }

    /**
     * @deprecated Use {@link #channelBoundsChanged(int)} instead.
     */
    @Deprecated
    public void componentBoundsChanged(int component)
    {
        channelBoundsChanged(component);
    }

    /**
     * fire change event
     */
    protected void fireChangeEvent(IcyBufferedImageEvent e)
    {
        for (IcyBufferedImageListener listener : new ArrayList<IcyBufferedImageListener>(listeners))
            listener.imageChanged(e);
    }

    public void addListener(IcyBufferedImageListener listener)
    {
        listeners.add(listener);
    }

    public void removeListener(IcyBufferedImageListener listener)
    {
        listeners.remove(listener);
    }

    public void beginUpdate()
    {
        updater.beginUpdate();
    }

    public void endUpdate()
    {
        updater.endUpdate();
    }

    public boolean isUpdating()
    {
        return updater.isUpdating();
    }

    @Override
    public void onChanged(CollapsibleEvent object)
    {
        IcyBufferedImageEvent event = (IcyBufferedImageEvent) object;

        switch (event.getType())
        {
            // do here global process on image data change
            case DATA_CHANGED:
                // update image components bounds
                if (autoUpdateChannelBounds)
                    updateChannelsBounds();
                break;

            // do here global process on image bounds change
            case BOUNDS_CHANGED:
                break;

            // do here global process on image colormap change
            case COLORMAP_CHANGED:
                break;
        }

        // notify listener we have changed
        fireChangeEvent(event);
    }

    @Override
    public void colorModelChanged(IcyColorModelEvent e)
    {
        switch (e.getType())
        {
            case COLORMAP_CHANGED:
                colormapChanged(e.getComponent());
                break;

            case SCALER_CHANGED:
                channelBoundsChanged(e.getComponent());
                break;
        }
    }
}