/*
* 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.colormodel;
import icy.common.CollapsibleEvent;
import icy.common.UpdateEventHandler;
import icy.common.listener.ChangeListener;
import icy.image.colormap.IcyColorMap;
import icy.image.colormodel.IcyColorModelEvent.IcyColorModelEventType;
import icy.image.colorspace.IcyColorSpace;
import icy.image.colorspace.IcyColorSpaceEvent;
import icy.image.colorspace.IcyColorSpaceListener;
import icy.image.lut.LUT;
import icy.math.Scaler;
import icy.math.ScalerEvent;
import icy.math.ScalerListener;
import icy.type.DataType;
import icy.type.TypeUtil;
import icy.type.collection.array.Array1DUtil;
import icy.util.ReflectionUtil;
import java.awt.image.BandedSampleModel;
import java.awt.image.ColorModel;
import java.awt.image.ComponentSampleModel;
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.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* @author stephane
*/
public abstract class IcyColorModel extends ColorModel implements ScalerListener, IcyColorSpaceListener, ChangeListener
{
/**
* scalers for normalization
*/
protected final Scaler[] normalScalers;
/**
* scalers for colorMap
*/
protected final Scaler[] colormapScalers;
/**
* data type
*/
protected final DataType dataType;
/**
* overridden variables
*/
protected final int numComponents;
/**
* listeners
*/
private final List<IcyColorModelListener> listeners;
/**
* internal updater
*/
private final UpdateEventHandler updater;
/**
* Default constructor
*/
IcyColorModel(int numComponents, DataType dataType, int[] bits)
{
super(dataType.getBitSize(), bits, new IcyColorSpace(numComponents), true, false, TRANSLUCENT, dataType
.toDataBufferType());
if (numComponents == 0)
throw new IllegalArgumentException("Number of components should be > 0");
// overridden variable
this.numComponents = numComponents;
listeners = new ArrayList<IcyColorModelListener>();
updater = new UpdateEventHandler(this, false);
// data type information
this.dataType = dataType;
// get default min and max for datatype
final double[] defaultBounds = dataType.getDefaultBounds();
final double min = defaultBounds[0];
final double max = defaultBounds[1];
// float type flag
final boolean isFloat = dataType.isFloat();
// allocating scalers
normalScalers = new Scaler[numComponents];
colormapScalers = new Scaler[numComponents];
// defining scalers
for (int i = 0; i < numComponents; i++)
{
// scale for normalization
normalScalers[i] = new Scaler(min, max, 0f, 1f, !isFloat);
// scale for colormap
colormapScalers[i] = new Scaler(min, max, 0f, IcyColorMap.MAX_INDEX, !isFloat);
// add listener to the colormap scalers only
colormapScalers[i].addListener(this);
}
// add the listener to colorSpace
getIcyColorSpace().addListener(this);
}
/**
* @deprecated use {@link #IcyColorModel(int, DataType, int[])} instead
*/
@Deprecated
IcyColorModel(int numComponents, int dataType, boolean signed, int[] bits)
{
this(numComponents, DataType.getDataType(dataType, signed), bits);
}
/**
* Creates a new ColorModel with the given color component and image data type
*
* @param numComponents
* number of component
* @param dataType
* the type of image data (see {@link DataType})
* @return a IcyColorModel object
*/
public static IcyColorModel createInstance(int numComponents, DataType dataType)
{
// define bits size
final int bits = dataType.getBitSize();
// we have to fake one more extra component for alpha in ColorModel class
final int numComponentFixed = numComponents + 1;
final int[] componentBits = new int[numComponentFixed];
for (int i = 0; i < numComponentFixed; i++)
componentBits[i] = bits;
switch (dataType)
{
case UBYTE:
return new UByteColorModel(numComponents, componentBits);
case BYTE:
return new ByteColorModel(numComponents, componentBits);
case USHORT:
return new UShortColorModel(numComponents, componentBits);
case SHORT:
return new ShortColorModel(numComponents, componentBits);
case UINT:
return new UIntColorModel(numComponents, componentBits);
case INT:
return new IntColorModel(numComponents, componentBits);
case ULONG:
return new ULongColorModel(numComponents, componentBits);
case LONG:
return new LongColorModel(numComponents, componentBits);
case FLOAT:
return new FloatColorModel(numComponents, componentBits);
case DOUBLE:
return new DoubleColorModel(numComponents, componentBits);
default:
throw new IllegalArgumentException("Unsupported data type !");
}
}
/**
* @deprecated use {@link #createInstance(int, DataType)} instead
*/
@Deprecated
public static IcyColorModel createInstance(int numComponents, int dataType, boolean signed)
{
return createInstance(numComponents, DataType.getDataType(dataType, signed));
}
/**
* Creates a new ColorModel from a given icyColorModel
*
* @param colorModel
* icyColorModel
* @param copyColormap
* flag to indicate if we want to copy colormaps from the given icyColorModel
* @param copyBounds
* flag to indicate if we want to copy bounds from the given icyColorModel
* @return a IcyColorModel object
*/
public static IcyColorModel createInstance(IcyColorModel colorModel, boolean copyColormap, boolean copyBounds)
{
final IcyColorModel result = IcyColorModel.createInstance(colorModel.getNumComponents(),
colorModel.getDataType_());
result.beginUpdate();
try
{
// copy colormaps from colorModel ?
if (copyColormap)
result.setColorMaps(colorModel);
// copy bounds from colorModel ?
if (copyBounds)
result.setBounds(colorModel);
}
finally
{
result.endUpdate();
}
return result;
}
/**
* Create default ColorModel : 4 components, unsigned byte data type
*/
public static IcyColorModel createInstance()
{
return createInstance(4, DataType.UBYTE);
}
@Override
public SampleModel createCompatibleSampleModel(int w, int h)
{
return new BandedSampleModel(transferType, w, h, getNumComponents());
}
@Override
public WritableRaster createCompatibleWritableRaster(int w, int h)
{
final SampleModel sm = createCompatibleSampleModel(w, h);
return Raster.createWritableRaster(sm, sm.createDataBuffer(), null);
}
/**
* Create a writable raster from specified data and size.<br>
*/
public WritableRaster createWritableRaster(Object[] data, int w, int h)
{
final SampleModel sm = createCompatibleSampleModel(w, h);
switch (dataType)
{
case UBYTE:
case BYTE:
return Raster.createWritableRaster(sm, new DataBufferByte((byte[][]) data, w * h), null);
case SHORT:
return Raster.createWritableRaster(sm, new DataBufferShort((short[][]) data, w * h), null);
case USHORT:
return Raster.createWritableRaster(sm, new DataBufferUShort((short[][]) data, w * h), null);
case UINT:
case INT:
return Raster.createWritableRaster(sm, new DataBufferInt((int[][]) data, w * h), null);
case FLOAT:
return Raster.createWritableRaster(sm, new DataBufferFloat((float[][]) data, w * h), null);
case DOUBLE:
return Raster.createWritableRaster(sm, new DataBufferDouble((double[][]) data, w * h), null);
default:
throw new IllegalArgumentException(
"IcyColorModel.createWritableRaster(..) error : unsupported data type : " + dataType);
}
}
/**
* Set bounds from specified {@link IcyColorModel}
*/
public void setBounds(IcyColorModel source)
{
beginUpdate();
try
{
for (int i = 0; i < numComponents; i++)
{
final Scaler srcNormalScaler = source.getNormalScalers()[i];
final Scaler dstNormalScaler = normalScalers[i];
final Scaler srcColorMapScaler = source.getColormapScalers()[i];
final Scaler dstColorMapScaler = colormapScalers[i];
dstNormalScaler.beginUpdate();
try
{
dstNormalScaler.setAbsLeftRightIn(srcNormalScaler.getAbsLeftIn(), srcNormalScaler.getAbsRightIn());
dstNormalScaler.setLeftRightIn(srcNormalScaler.getLeftIn(), srcNormalScaler.getRightIn());
dstNormalScaler.setLeftRightOut(srcNormalScaler.getLeftOut(), srcNormalScaler.getRightOut());
}
finally
{
dstNormalScaler.endUpdate();
}
dstColorMapScaler.beginUpdate();
try
{
dstColorMapScaler.setAbsLeftRightIn(srcColorMapScaler.getAbsLeftIn(),
srcColorMapScaler.getAbsRightIn());
dstColorMapScaler.setLeftRightIn(srcColorMapScaler.getLeftIn(), srcColorMapScaler.getRightIn());
dstColorMapScaler.setLeftRightOut(srcColorMapScaler.getLeftOut(), srcColorMapScaler.getRightOut());
}
finally
{
dstColorMapScaler.endUpdate();
}
}
}
finally
{
endUpdate();
}
}
/**
* @deprecated Use {@link #setBounds(IcyColorModel)} instead.
*/
@Deprecated
public void copyBounds(IcyColorModel source)
{
setBounds(source);
}
/**
* Return the toRGB colormap of specified RGB component
*/
public IcyColorMap getColorMap(int component)
{
return getIcyColorSpace().getColorMap(component);
}
/**
* @deprecated Use {@link #getColorMap(int)} instead (different case).
*/
@Deprecated
public IcyColorMap getColormap(int component)
{
return getColorMap(component);
}
/**
* Set the toRGB colormaps from a compatible colorModel.
*
* @param source
* source ColorModel to copy colormap from
*/
public void setColorMaps(ColorModel source)
{
getIcyColorSpace().setColorMaps(source);
}
/**
* @deprecated Use {@link #setColorMaps(ColorModel)} instead (different case).
*/
@Deprecated
public void setColormaps(ColorModel source)
{
setColorMaps(source);
}
/**
* @deprecated Use {@link #setColorMaps(ColorModel)} instead.
*/
@Deprecated
public void copyColormap(ColorModel source)
{
setColorMaps(source);
}
/**
* Set the toRGB colormap of specified component (actually copy the content).
*
* @param component
* component we want to set the colormap
* @param map
* source colormap to copy
* @param setAlpha
* also set the alpha information
*/
public void setColorMap(int component, IcyColorMap map, boolean setAlpha)
{
getIcyColorSpace().setColorMap(component, map, setAlpha);
}
/**
* @deprecated Use {@link #setColorMap(int, IcyColorMap, boolean)} instead.
*/
@Deprecated
public void setColormap(int component, IcyColorMap map)
{
setColorMap(component, map, true);
}
/**
* @see java.awt.image.ColorModel#getAlpha(int)
*/
@Override
public int getAlpha(int pixel)
{
throw new IllegalArgumentException("Argument type not supported for this color model");
}
/**
* @see java.awt.image.ColorModel#getBlue(int)
*/
@Override
public int getBlue(int pixel)
{
throw new IllegalArgumentException("Argument type not supported for this color model");
}
/**
* @see java.awt.image.ColorModel#getGreen(int)
*/
@Override
public int getGreen(int pixel)
{
throw new IllegalArgumentException("Argument type not supported for this color model");
}
/**
* @see java.awt.image.ColorModel#getRed(int)
*/
@Override
public int getRed(int pixel)
{
throw new IllegalArgumentException("Argument type not supported for this color model");
}
@Override
public abstract int getRGB(Object inData);
public abstract int getRGB(Object pixel, LUT lut);
/**
*
*/
@Override
public int getBlue(Object pixel)
{
return getRGB(pixel) & 0xFF;
}
/**
*
*/
@Override
public int getGreen(Object pixel)
{
return (getRGB(pixel) >> 8) & 0xFF;
}
/**
*
*/
@Override
public int getRed(Object pixel)
{
return (getRGB(pixel) >> 16) & 0xFF;
}
/**
*
*/
@Override
public int getAlpha(Object pixel)
{
return (getRGB(pixel) >> 24) & 0xFF;
}
/**
* @see java.awt.image.ColorModel#getComponents(int, int[], int)
*/
@Override
public int[] getComponents(int pixel, int[] components, int offset)
{
throw new IllegalArgumentException("Not supported in this ColorModel");
}
/**
* @see java.awt.image.ColorModel#getComponents(Object, int[], int)
*/
@Override
public abstract int[] getComponents(Object pixel, int[] components, int offset);
/**
* @see java.awt.image.ColorModel#getNormalizedComponents(Object, float[], int)
*/
@Override
public abstract float[] getNormalizedComponents(Object pixel, float[] normComponents, int normOffset);
/**
* @see java.awt.image.ColorModel#getNormalizedComponents(int[], int, float[], int)
*/
@Override
public float[] getNormalizedComponents(int[] components, int offset, float[] normComponents, int normOffset)
{
if ((components.length - offset) < numComponents)
throw new IllegalArgumentException("Incorrect number of components. Expecting " + numComponents);
final float[] result = Array1DUtil.allocIfNull(normComponents, numComponents + normOffset);
for (int i = 0; i < numComponents; i++)
result[normOffset + i] = (float) normalScalers[i].scale(components[offset + i]);
return result;
}
/**
* @see java.awt.image.ColorModel#getUnnormalizedComponents(float[], int, int[], int)
*/
@Override
public int[] getUnnormalizedComponents(float[] normComponents, int normOffset, int[] components, int offset)
{
if ((normComponents.length - normOffset) < numComponents)
throw new IllegalArgumentException("Incorrect number of components. Expecting " + numComponents);
final int[] result = Array1DUtil.allocIfNull(components, numComponents + offset);
for (int i = 0; i < numComponents; i++)
result[offset + i] = (int) normalScalers[i].unscale(normComponents[normOffset + i]);
return result;
}
/**
* @see java.awt.image.ColorModel#getDataElement(int[], int)
*/
@Override
public int getDataElement(int[] components, int offset)
{
throw new IllegalArgumentException("Not supported in this ColorModel");
}
/**
* @see java.awt.image.ColorModel#getDataElement(float[], int)
*/
@Override
public int getDataElement(float[] normComponents, int normOffset)
{
throw new IllegalArgumentException("Not supported in this ColorModel");
}
/**
* @see java.awt.image.ColorModel#getDataElements(int[], int, Object)
*/
@Override
public abstract Object getDataElements(int[] components, int offset, Object obj);
/**
* @see java.awt.image.ColorModel#getDataElements(int, Object)
*/
@Override
public Object getDataElements(int rgb, Object pixel)
{
return getDataElements(getIcyColorSpace().fromRGB(rgb), 0, pixel);
}
/**
* @see java.awt.image.ColorModel#getDataElements(float[], int, Object)
*/
@Override
public abstract Object getDataElements(float[] normComponents, int normOffset, Object obj);
/**
*
*/
@Override
public ColorModel coerceData(WritableRaster raster, boolean isAlphaPremultiplied)
{
// nothing to do
return this;
}
/**
* Scale input value for colormap indexing
*/
protected double colormapScale(int component, double value)
{
return colormapScalers[component].scale(value);
}
/**
* Tests if the specified <code>Object</code> is an instance of <code>ColorModel</code> and if
* it equals this <code>ColorModel</code>.
*
* @param obj
* the <code>Object</code> to test for equality
* @return <code>true</code> if the specified <code>Object</code> is an instance of <code>ColorModel</code> and
* equals this <code>ColorModel</code>; <code>false</code> otherwise.
*/
@Override
public boolean equals(Object obj)
{
if (obj instanceof IcyColorModel)
return isCompatible((IcyColorModel) obj);
return false;
}
/**
*
*/
public boolean isCompatible(IcyColorModel cm)
{
return (getNumComponents() == cm.getNumComponents()) && (getDataType_() == cm.getDataType_());
}
/**
*
*/
@Override
public boolean isCompatibleRaster(Raster raster)
{
final SampleModel sm = raster.getSampleModel();
final int[] bits = getComponentSize();
if (sm instanceof ComponentSampleModel)
{
if (sm.getNumBands() != numComponents)
return false;
for (int i = 0; i < bits.length; i++)
if (sm.getSampleSize(i) < bits[i])
return false;
return (raster.getTransferType() == transferType);
}
return false;
}
/**
*
*/
@Override
public boolean isCompatibleSampleModel(SampleModel sm)
{
// Must have the same number of components
if (numComponents != sm.getNumBands())
return false;
if (sm.getTransferType() != transferType)
return false;
return true;
}
/**
* @return the IcyColorSpace
*/
public IcyColorSpace getIcyColorSpace()
{
return (IcyColorSpace) getColorSpace();
}
/**
* Change the colorspace of the color model.<br/>
* <b>You should never use this method directly (internal use only)</b>
*/
public void setColorSpace(IcyColorSpace colorSpace)
{
final IcyColorSpace cs = getIcyColorSpace();
if (cs != colorSpace)
{
try
{
final Field csField = ReflectionUtil.getField(ColorModel.class, "colorSpace", true);
// set new colorSpace value
csField.set(this, colorSpace);
cs.removeListener(this);
colorSpace.addListener(this);
}
catch (Exception e)
{
System.err.println("Warning: Couldn't change colorspace of IcyColorModel...");
}
}
// do not notify about the change here as this method is only called internally
}
/**
* @return the normalScalers
*/
public Scaler[] getNormalScalers()
{
return normalScalers;
}
/**
* @return the colormapScalers
*/
public Scaler[] getColormapScalers()
{
return colormapScalers;
}
/**
* Returns the number of components in this <code>ColorModel</code>.<br>
* Note that alpha is embedded so we always have NumColorComponent = NumComponent
*
* @return the number of components in this <code>ColorModel</code>
*/
@Override
public int getNumComponents()
{
return numComponents;
}
/**
* @deprecated use {@link #getDataType_()} instead
*/
@Deprecated
public int getDataType()
{
return TypeUtil.dataBufferTypeToDataType(transferType);
}
/**
* Return data type for this colormodel
*
* @see DataType
*/
public DataType getDataType_()
{
return dataType;
}
/**
* return default component bounds for this colormodel
*/
public double[] getDefaultComponentBounds()
{
return dataType.getDefaultBounds();
}
/**
* Get component absolute minimum value
*/
public double getComponentAbsMinValue(int component)
{
// use the normal scaler
return normalScalers[component].getAbsLeftIn();
}
/**
* Get component absolute maximum value
*/
public double getComponentAbsMaxValue(int component)
{
// use the normal scaler
return normalScalers[component].getAbsRightIn();
}
/**
* Get component absolute bounds (min and max values)
*/
public double[] getComponentAbsBounds(int component)
{
final double[] result = new double[2];
result[0] = getComponentAbsMinValue(component);
result[1] = getComponentAbsMaxValue(component);
return result;
}
/**
* Get component user minimum value
*/
public double getComponentUserMinValue(int component)
{
// use the normal scaler
return normalScalers[component].getLeftIn();
}
/**
* Get user component user maximum value
*/
public double getComponentUserMaxValue(int component)
{
// use the normal scaler
return normalScalers[component].getRightIn();
}
/**
* Get component user bounds (min and max values)
*/
public double[] getComponentUserBounds(int component)
{
final double[] result = new double[2];
result[0] = getComponentUserMinValue(component);
result[1] = getComponentUserMaxValue(component);
return result;
}
/**
* Set component absolute minimum value
*/
public void setComponentAbsMinValue(int component, double min)
{
// update both scalers
normalScalers[component].setAbsLeftIn(min);
colormapScalers[component].setAbsLeftIn(min);
}
/**
* Set component absolute maximum value
*/
public void setComponentAbsMaxValue(int component, double max)
{
// update both scalers
normalScalers[component].setAbsRightIn(max);
colormapScalers[component].setAbsRightIn(max);
}
/**
* Set component absolute bounds (min and max values)
*/
public void setComponentAbsBounds(int component, double[] bounds)
{
setComponentAbsBounds(component, bounds[0], bounds[1]);
}
/**
* Set component absolute bounds (min and max values)
*/
public void setComponentAbsBounds(int component, double min, double max)
{
// update both scalers
normalScalers[component].setAbsLeftRightIn(min, max);
colormapScalers[component].setAbsLeftRightIn(min, max);
}
/**
* Set component user minimum value
*/
public void setComponentUserMinValue(int component, double min)
{
// update both scalers
normalScalers[component].setLeftIn(min);
colormapScalers[component].setLeftIn(min);
}
/**
* Set component user maximum value
*/
public void setComponentUserMaxValue(int component, double max)
{
// update both scalers
normalScalers[component].setRightIn(max);
colormapScalers[component].setRightIn(max);
}
/**
* Set component user bounds (min and max values)
*/
public void setComponentUserBounds(int component, double[] bounds)
{
setComponentUserBounds(component, bounds[0], bounds[1]);
}
/**
* Set component user bounds (min and max values)
*/
public void setComponentUserBounds(int component, double min, double max)
{
// update both scalers
normalScalers[component].setLeftRightIn(min, max);
colormapScalers[component].setLeftRightIn(min, max);
}
/**
* Set components absolute bounds (min and max values)
*/
public void setComponentsAbsBounds(double[][] bounds)
{
final int numComponents = getNumComponents();
if (bounds.length != numComponents)
throw new IllegalArgumentException("bounds.length != ColorModel.numComponents");
for (int component = 0; component < numComponents; component++)
setComponentAbsBounds(component, bounds[component]);
}
/**
* Set components user bounds (min and max values)
*/
public void setComponentsUserBounds(double[][] bounds)
{
final int numComponents = getNumComponents();
if (bounds.length != numComponents)
throw new IllegalArgumentException("bounds.length != ColorModel.numComponents");
for (int component = 0; component < numComponents; component++)
setComponentUserBounds(component, bounds[component]);
}
/**
* Return true if colorModel is float data type
*/
public boolean isFloatDataType()
{
return dataType.isFloat();
}
/**
* Return true if colorModel data type is signed
*/
public boolean isSignedDataType()
{
return dataType.isSigned();
}
/**
* Return true if color maps associated to this {@link IcyColorModel} are all linear map.
*
* @see IcyColorMap#isLinear()
*/
public boolean hasLinearColormaps()
{
for (int c = 0; c < numComponents; c++)
if (!getColorMap(c).isLinear())
return false;
return true;
}
/**
* Returns the <code>String</code> representation of the contents of this <code>ColorModel</code>object.
*
* @return a <code>String</code> representing the contents of this <code>ColorModel</code> object.
*/
@Override
public String toString()
{
return new String("ColorModel: dataType = " + dataType + " numComponents = " + numComponents
+ " color space = " + getColorSpace());
}
/**
* Add a listener
*
* @param listener
*/
public void addListener(IcyColorModelListener listener)
{
listeners.add(listener);
}
/**
* Remove a listener
*
* @param listener
*/
public void removeListener(IcyColorModelListener listener)
{
listeners.remove(listener);
}
/**
* fire event
*
* @param e
*/
public void fireEvent(IcyColorModelEvent e)
{
for (IcyColorModelListener listener : new ArrayList<IcyColorModelListener>(listeners))
listener.colorModelChanged(e);
}
/**
* process on colormodel change
*/
@Override
public void onChanged(CollapsibleEvent compare)
{
final IcyColorModelEvent event = (IcyColorModelEvent) compare;
// notify listener we have changed
fireEvent(event);
}
@Override
public void scalerChanged(ScalerEvent e)
{
// only listening colormapScalers
final int ind = Scaler.indexOf(colormapScalers, e.getScaler());
// handle changed via updater object
if (ind != -1)
updater.changed(new IcyColorModelEvent(this, IcyColorModelEventType.SCALER_CHANGED, ind));
}
@Override
public void colorSpaceChanged(IcyColorSpaceEvent e)
{
// handle changed via updater object
updater.changed(new IcyColorModelEvent(this, IcyColorModelEventType.COLORMAP_CHANGED, e.getComponent()));
}
public void beginUpdate()
{
updater.beginUpdate();
}
public void endUpdate()
{
updater.endUpdate();
}
public boolean isUpdating()
{
return updater.isUpdating();
}
}