/*
* 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.colorspace;
import icy.common.CollapsibleEvent;
import icy.common.UpdateEventHandler;
import icy.common.listener.ChangeListener;
import icy.image.colormap.FromRGBColorMap;
import icy.image.colormap.IcyColorMap;
import icy.image.colormap.IcyColorMap.IcyColorMapType;
import icy.image.colormap.IcyColorMapEvent;
import icy.image.colormap.IcyColorMapListener;
import icy.image.colormap.LinearColorMap;
import icy.image.colormodel.IcyColorModel;
import icy.type.DataType;
import icy.type.collection.array.ArrayUtil;
import icy.util.ColorUtil;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.util.ArrayList;
import java.util.List;
/**
* @author stephane
*/
public class IcyColorSpace extends ColorSpace implements ChangeListener, IcyColorMapListener
{
/**
*
*/
private static final long serialVersionUID = 6413334779215415163L;
/**
* toRGB colormaps
*/
private final IcyColorMap[] toRGBmaps;
/**
* fromRGB colormaps
*/
private final FromRGBColorMap[] fromRGBmaps;
/**
* use alpha
*/
// private boolean alphaEnabled;
/**
* listeners
*/
private final List<IcyColorSpaceListener> listeners;
/**
* internal updater
*/
private final UpdateEventHandler updater;
/**
* Create an icy colorspace object
*
* @param numComponents
* number of color component
*/
public IcyColorSpace(int numComponents)
{
super((numComponents > 1) ? 10 + numComponents : ColorSpace.TYPE_GRAY, numComponents);
if (numComponents == 0)
throw new IllegalArgumentException("numComponents must be > 0");
// allocating toRGB colormaps
toRGBmaps = new IcyColorMap[numComponents];
for (int i = 0; i < numComponents; i++)
{
final IcyColorMap colormap = new IcyColorMap("component " + i);
toRGBmaps[i] = colormap;
// add listener
colormap.addListener(this);
}
// allocating fromRGB colormaps
fromRGBmaps = new FromRGBColorMap[4];
for (int i = 0; i < 4; i++)
fromRGBmaps[i] = new FromRGBColorMap(numComponents);
listeners = new ArrayList<IcyColorSpaceListener>();
updater = new UpdateEventHandler(this, false);
// // alpha is enabled by default
// alphaEnabled = true;
beginUpdate();
try
{
// single component: gray colormap
if (numComponents == 1)
setColorMap(0, LinearColorMap.white_, true);
else
{
// define default colormaps depending the number of component
for (int i = 0; i < numComponents; i++)
{
switch (i % 7)
{
case 0:
setColorMap(i, LinearColorMap.red_, true);
break;
case 1:
setColorMap(i, LinearColorMap.green_, true);
break;
case 2:
setColorMap(i, LinearColorMap.blue_, true);
break;
case 3:
setColorMap(i, LinearColorMap.cyan_, true);
break;
case 4:
setColorMap(i, LinearColorMap.magenta_, true);
break;
case 5:
setColorMap(i, LinearColorMap.yellow_, true);
break;
case 6:
setColorMap(i, LinearColorMap.white_, true);
break;
}
}
}
}
finally
{
endUpdate();
}
// generate fromRGB maps
generateFromRGBColorMaps();
}
/**
* Return true if the colorspace's colormap contains an alpha component<br>
* This is different from the isAlphaEnabled flag
*/
public boolean hasAlphaComponent()
{
for (int comp = 0; comp < toRGBmaps.length; comp++)
if (toRGBmaps[comp].getType() == IcyColorMapType.ALPHA)
return true;
return false;
}
// /**
// * @return the alphaEnabled
// */
// public boolean isAlphaEnabled()
// {
// return alphaEnabled;
// }
//
// /**
// * @param alphaEnabled
// * the alphaEnabled to set
// */
// public void setAlphaEnabled(boolean alphaEnabled)
// {
// this.alphaEnabled = alphaEnabled;
// }
/**
* Generate FromRGB colormaps from ToRGB ones
*/
private void generateFromRGBColorMaps()
{
for (int comp = 0; comp < toRGBmaps.length; comp++)
{
final IcyColorMap toRGBmap = toRGBmaps[comp];
final float step = 1.0f / FromRGBColorMap.COLORMAP_MAX;
for (float intensity = 0.0f; intensity <= 1.0f; intensity += step)
{
// blue
fromRGBmaps[0].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedBlue(intensity));
// green
fromRGBmaps[1].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedGreen(intensity));
// red
fromRGBmaps[2].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedRed(intensity));
// alpha
fromRGBmaps[3].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedAlpha(intensity));
}
}
}
/**
* @see java.awt.color.ColorSpace#fromCIEXYZ(float[])
*/
@Override
public float[] fromCIEXYZ(float[] colorvalue)
{
return fromRGB(ColorUtil.sRGB.fromCIEXYZ(colorvalue));
}
/**
* @see java.awt.color.ColorSpace#toCIEXYZ(float[])
*/
@Override
public float[] toCIEXYZ(float[] colorvalue)
{
return ColorUtil.sRGB.toCIEXYZ(toRGB(colorvalue));
}
/**
* @see java.awt.color.ColorSpace#fromRGB(float[])
*/
@Override
public float[] fromRGB(float[] rgb)
{
final int numComponents = getNumComponents();
final float[] result = new float[numComponents];
final FromRGBColorMap blueMap = fromRGBmaps[0];
final FromRGBColorMap greenMap = fromRGBmaps[1];
final FromRGBColorMap redMap = fromRGBmaps[2];
final FromRGBColorMap alphaMap = fromRGBmaps[3];
final float blue = rgb[0];
final float green = rgb[1];
final float red = rgb[2];
final float alpha;
if (rgb.length > 3)
alpha = rgb[3];
else
alpha = 1.0f;
for (int comp = 0; comp < numComponents; comp++)
{
result[comp] = blueMap.getFromRGBColor(comp, blue);
result[comp] += greenMap.getFromRGBColor(comp, green);
result[comp] += redMap.getFromRGBColor(comp, red);
result[comp] *= alphaMap.getFromRGBColor(comp, alpha);
// limit
if (result[comp] > 1.0f)
result[comp] = 1.0f;
}
return result;
}
/**
* return normalized component values from ARGB packed integer values
*
* @param rgb
* @return float[]
*/
public float[] fromRGB(int rgb)
{
final int numComponents = getNumComponents();
final float[] result = new float[numComponents];
final FromRGBColorMap blueMap = fromRGBmaps[0];
final FromRGBColorMap greenMap = fromRGBmaps[1];
final FromRGBColorMap redMap = fromRGBmaps[2];
final FromRGBColorMap alphaMap = fromRGBmaps[3];
final int red, grn, blu, alp;
alp = (rgb >> 24) & 0xFF;
red = (rgb >> 16) & 0xFF;
grn = (rgb >> 8) & 0xFF;
blu = rgb & 0xFF;
for (int comp = 0; comp < numComponents; comp++)
{
result[comp] = blueMap.maps[comp][blu];
result[comp] += greenMap.maps[comp][grn];
result[comp] += redMap.maps[comp][red];
result[comp] *= alphaMap.maps[comp][alp];
// limit
if (result[comp] > 1.0f)
result[comp] = 1.0f;
}
return result;
}
/**
* return unnormalized ARGB values from colorMap scaled components values
*
* @param colorvalue
* @return ARGB as int
*/
public int toRGBUnnorm(final int[] colorvalue)
{
final int numComponents = Math.min(getNumComponents(), colorvalue.length);
// default alpha
float alpha = 1f;
// default max local alpha
float maxLocalAlpha = 0f;
// default RGB
int r = 0, g = 0, b = 0;
for (int comp = 0; comp < numComponents; comp++)
{
final IcyColorMap cm = toRGBmaps[comp];
if (cm.isEnabled())
{
final int value = colorvalue[comp];
final float alphaValue = cm.alpha.mapf[value];
// alpha channel ?
if (cm.getType() == IcyColorMapType.ALPHA)
alpha = alphaValue;
else if (alphaValue > maxLocalAlpha)
maxLocalAlpha = alphaValue;
final int premulRGB[] = cm.getPremulRGB()[value];
b += premulRGB[0];
g += premulRGB[1];
r += premulRGB[2];
}
}
// final alpha = alpha component value * maximum local alpha value
final int a = (int) (alpha * maxLocalAlpha * IcyColorMap.MAX_LEVEL);
if (a != 0)
{
final int inv = (1 << (IcyColorMap.COLORMAP_BITS + 8)) / a;
// normalize on alpha
b = (b * inv) >> 8;
g = (g * inv) >> 8;
r = (r * inv) >> 8;
}
return ((b > IcyColorMap.MAX_LEVEL) ? IcyColorMap.MAX_LEVEL : b)
| (((g > IcyColorMap.MAX_LEVEL) ? IcyColorMap.MAX_LEVEL : g) << 8)
| (((r > IcyColorMap.MAX_LEVEL) ? IcyColorMap.MAX_LEVEL : r) << 16) | (a << 24);
}
@Override
public float[] toRGB(float[] colorvalue)
{
final float[] result = new float[4];
final int numComponents = Math.min(getNumComponents(), colorvalue.length);
// default alpha
float alpha = 1f;
// default max local alpha
float maxLocalAlpha = 0f;
// default RGB
float rf = 0f, gf = 0f, bf = 0f;
for (int comp = 0; comp < numComponents; comp++)
{
final IcyColorMap cm = toRGBmaps[comp];
if (cm.isEnabled())
{
final int value = (int) (colorvalue[comp] * IcyColorMap.MAX_INDEX);
final float alphaValue = cm.alpha.mapf[value];
// alpha channel ?
if (cm.getType() == IcyColorMapType.ALPHA)
alpha = alphaValue;
else if (alphaValue > maxLocalAlpha)
maxLocalAlpha = alphaValue;
final float premulRGBNorm[] = cm.getPremulRGBNorm()[value];
bf += premulRGBNorm[0];
gf += premulRGBNorm[1];
rf += premulRGBNorm[2];
}
}
// final alpha = alpha component value * maximum local alpha value
final float af = alpha * maxLocalAlpha;
if (af != 0f)
{
final float inv = 1f / af;
// normalize on alpha
bf *= inv;
gf *= inv;
rf *= inv;
}
result[0] = (bf > 1f) ? 1f : bf;
result[1] = (gf > 1f) ? 1f : gf;
result[2] = (rf > 1f) ? 1f : rf;
result[3] = af;
return result;
}
/**
* Set 8 bit ARGB data in an ARGB buffer from a scaled input buffer
*
* @param unnormSrc
* source buffer containing unnormalized values ([0..255] range) for each component
* @param dest
* ARGB components buffer
*/
public void fillARGBBuffer(int[][] unnormSrc, int[] dest, int offset, int length)
{
final int numComponents = getNumComponents();
if (numComponents > 0)
{
final int[] input = new int[numComponents];
for (int i = 0; i < length; i++)
{
// get data value
for (int comp = 0; comp < numComponents; comp++)
input[comp] = unnormSrc[comp][i];
// convert to RGBA
dest[offset + i] = toRGBUnnorm(input);
}
}
}
/**
* Set 8 bit ARGB data in an ARGB buffer from a scaled input buffer
*
* @param unnormSrc
* source buffer containing unnormalized values ([0..255] range) for each component
* @param dest
* ARGB components buffer
*/
public void fillARGBBuffer(int[][] unnormSrc, int[] dest)
{
if ((unnormSrc == null) || (dest == null))
{
throw new IllegalArgumentException("Parameters 'unnormSrc' and 'destARGB' should not be null !");
}
final int numComponents = getNumComponents();
if (unnormSrc.length != numComponents)
{
throw new IllegalArgumentException("Parameters 'unnormSrc' size is [" + unnormSrc.length + "][..] where ["
+ numComponents + "][..] is expected !");
}
if (numComponents > 0)
{
final int size = unnormSrc[0].length;
final int[] input = new int[numComponents];
for (int i = 0; i < size; i++)
{
// get data value
for (int comp = 0; comp < numComponents; comp++)
input[comp] = unnormSrc[comp][i];
// convert to RGBA
dest[i] = toRGBUnnorm(input);
}
}
}
/**
* Return the number of component of colorSpace
*/
@Override
public int getNumComponents()
{
return toRGBmaps.length;
}
/**
* Return the colormap of the specified component.
*/
public IcyColorMap getColorMap(int component)
{
return toRGBmaps[component];
}
/**
* @deprecated Use {@link #getColorMap(int)} instead (different case).
*/
@Deprecated
public IcyColorMap getColormap(int component)
{
return getColorMap(component);
}
/**
* Set the colormap for the specified component (actually copy the content of source colormap).
*
* @param component
* component we want to set the colormap
* @param colorMap
* source colorMap
* @param setAlpha
* also set the alpha information
*/
public void setColorMap(int component, IcyColorMap colorMap, boolean setAlpha)
{
toRGBmaps[component].copyFrom(colorMap, setAlpha);
}
/**
* @deprecated Use {@link #setColorMap(int, IcyColorMap, boolean)} instead.
*/
@Deprecated
public void setColormap(int component, IcyColorMap map)
{
setColorMap(component, map, true);
}
/**
* @deprecated Use <code>setColormap(channel, map)</code> instead.
*/
@Deprecated
public void copyColormap(int component, IcyColorMap map, boolean copyName, boolean copyAlpha)
{
setColorMap(component, map, copyAlpha);
if (copyName)
toRGBmaps[component].setName(map.getName());
}
/**
* @deprecated Use <code>setColormap(channel, map)</code> instead.
*/
@Deprecated
public void copyColormap(int component, IcyColorMap map, boolean copyName)
{
setColorMap(component, map, true);
if (copyName)
toRGBmaps[component].setName(map.getName());
}
/**
* @deprecated Use <code>setColormap(channel, map)</code> instead.
*/
@Deprecated
public void copyColormap(int component, IcyColorMap map)
{
setColorMap(component, map, true);
}
/**
* Return the RGB inverse colormap for specified RGB component.
*/
public FromRGBColorMap getFromRGBMap(int component)
{
return fromRGBmaps[component];
}
/**
* Set the RGB colormaps from a compatible colorModel.
*/
public void setColorMaps(ColorModel cm)
{
if (cm instanceof IcyColorModel)
setColorMaps((IcyColorSpace) cm.getColorSpace(), true);
else
{
// get datatype and numComponent of source colorModel
final Object srcElem = cm.getDataElements(0x0, null);
final DataType srcDataType = ArrayUtil.getDataType(srcElem);
final int srcNumComponents = ArrayUtil.getLength(srcElem);
final DataType dataType = DataType.getDataTypeFromDataBufferType(cm.getTransferType());
final int numComponents = getNumComponents();
// can't recover colormap if we have different dataType or numComponents
if ((srcNumComponents != numComponents) || (srcDataType != dataType))
return;
final boolean hasAlpha = cm.hasAlpha();
for (int comp = 0; comp < numComponents; comp++)
{
final IcyColorMap map = toRGBmaps[comp];
map.beginUpdate();
try
{
// set type
if (hasAlpha && (comp == (numComponents - 1)))
map.setType(IcyColorMapType.ALPHA);
else
map.setType(IcyColorMapType.RGB);
for (int index = 0; index < IcyColorMap.SIZE; index++)
{
switch (dataType.getJavaType())
{
case BYTE:
{
final byte bvalues[] = new byte[numComponents];
// build an pixel element
for (int i = 0; i < numComponents; i++)
{
if (i == comp)
bvalues[i] = (byte) (index * (1 << 8) / IcyColorMap.SIZE);
else if (hasAlpha && (i == (numComponents - 1)))
bvalues[i] = (byte) (IcyColorMap.MAX_INDEX * (1 << 8) / IcyColorMap.SIZE);
else
bvalues[i] = 0;
}
// set colormap data
map.setAlpha(index, (short) cm.getAlpha(bvalues));
map.setRed(index, (short) cm.getRed(bvalues));
map.setGreen(index, (short) cm.getGreen(bvalues));
map.setBlue(index, (short) cm.getBlue(bvalues));
break;
}
case SHORT:
{
final short svalues[] = new short[numComponents];
// build an pixel element
for (int i = 0; i < numComponents; i++)
{
if (i == comp)
svalues[i] = (short) (index * (1 << 16) / IcyColorMap.SIZE);
else if (hasAlpha && (i == (numComponents - 1)))
svalues[i] = (short) (IcyColorMap.MAX_INDEX * (1 << 16) / IcyColorMap.SIZE);
else
svalues[i] = 0;
}
// set colormap data
map.setAlpha(index, (short) cm.getAlpha(svalues));
map.setRed(index, (short) cm.getRed(svalues));
map.setGreen(index, (short) cm.getGreen(svalues));
map.setBlue(index, (short) cm.getBlue(svalues));
break;
}
case INT:
{
final int ivalues[] = new int[numComponents];
// build an pixel element
for (int i = 0; i < numComponents; i++)
{
if (i == comp)
ivalues[i] = (index * (1 << 32) / IcyColorMap.SIZE);
else if (hasAlpha && (i == (numComponents - 1)))
ivalues[i] = (IcyColorMap.MAX_INDEX * (1 << 32) / IcyColorMap.SIZE);
else
ivalues[i] = 0;
}
// set colormap data
map.setAlpha(index, (short) cm.getAlpha(ivalues));
map.setRed(index, (short) cm.getRed(ivalues));
map.setGreen(index, (short) cm.getGreen(ivalues));
map.setBlue(index, (short) cm.getBlue(ivalues));
break;
}
case LONG:
{
final long lvalues[] = new long[numComponents];
// build an pixel element
for (int i = 0; i < numComponents; i++)
{
if (i == comp)
lvalues[i] = (index * (1 << 32) / IcyColorMap.SIZE);
else if (hasAlpha && (i == (numComponents - 1)))
lvalues[i] = (IcyColorMap.MAX_INDEX * (1 << 32) / IcyColorMap.SIZE);
else
lvalues[i] = 0;
}
// set colormap data
map.setAlpha(index, (short) cm.getAlpha(lvalues));
map.setRed(index, (short) cm.getRed(lvalues));
map.setGreen(index, (short) cm.getGreen(lvalues));
map.setBlue(index, (short) cm.getBlue(lvalues));
break;
}
case FLOAT:
{
final float fvalues[] = new float[numComponents];
// build an pixel element
for (int i = 0; i < numComponents; i++)
{
if (i == comp)
fvalues[i] = (float) index / (float) IcyColorMap.SIZE;
else if (hasAlpha && (i == (numComponents - 1)))
fvalues[i] = (IcyColorMap.MAX_INDEX / (float) IcyColorMap.SIZE);
else
fvalues[i] = 0;
}
// set colormap data
map.setAlpha(index, (short) cm.getAlpha(fvalues));
map.setRed(index, (short) cm.getRed(fvalues));
map.setGreen(index, (short) cm.getGreen(fvalues));
map.setBlue(index, (short) cm.getBlue(fvalues));
break;
}
case DOUBLE:
{
final double dvalues[] = new double[numComponents];
// build an pixel element
for (int i = 0; i < numComponents; i++)
{
if (i == comp)
dvalues[i] = (double) index / (double) IcyColorMap.SIZE;
else if (hasAlpha && (i == (numComponents - 1)))
dvalues[i] = (IcyColorMap.MAX_INDEX / (double) IcyColorMap.SIZE);
else
dvalues[i] = 0;
}
// set colormap data
map.setAlpha(index, (short) cm.getAlpha(dvalues));
map.setRed(index, (short) cm.getRed(dvalues));
map.setGreen(index, (short) cm.getGreen(dvalues));
map.setBlue(index, (short) cm.getBlue(dvalues));
break;
}
default:
break;
}
}
}
finally
{
map.endUpdate();
}
}
}
}
/**
* @deprecated Use {@link #setColorMaps(ColorModel)} instead (different case).
*/
@Deprecated
public void setColormaps(ColorModel cm)
{
setColorMaps(cm);
}
/**
* @deprecated Use {@link #setColorMaps(ColorModel)} instead.
*/
@Deprecated
public void copyColormaps(ColorModel cm)
{
setColorMaps(cm);
}
/**
* Set colormaps from specified colorSpace (do a copy).
*
* @param source
* source colorspace to copy the colormaps from
* @param setAlpha
* also set the alpha information
*/
public void setColorMaps(IcyColorSpace source, boolean setAlpha)
{
final int numComponents = Math.min(source.getNumComponents(), getNumComponents());
beginUpdate();
try
{
// copy colormap
for (int comp = 0; comp < numComponents; comp++)
setColorMap(comp, source.getColorMap(comp), setAlpha);
}
finally
{
endUpdate();
}
}
/**
* @deprecated Use {@link #setColorMaps(IcyColorSpace, boolean)} instead.
*/
@Deprecated
public void setColormaps(IcyColorSpace source)
{
setColorMaps(source, true);
}
/**
* @deprecated Use {@link #setColorMaps(IcyColorSpace, boolean)} instead.
*/
@Deprecated
public void copyColormaps(IcyColorSpace source)
{
setColorMaps(source, true);
}
/**
* get index of the specified colormap
*
* @param colormap
* @return index
*/
public int indexOfColorMap(IcyColorMap colormap)
{
for (int i = 0; i < toRGBmaps.length; i++)
if (toRGBmaps[i].equals(colormap))
return i;
return -1;
}
@Override
public String getName(int idx)
{
// TODO: should get name from metadata
return "Component #" + idx;
}
/**
* Add a listener
*
* @param listener
*/
public void addListener(IcyColorSpaceListener listener)
{
listeners.add(listener);
}
/**
* Remove a listener
*
* @param listener
*/
public void removeListener(IcyColorSpaceListener listener)
{
listeners.remove(listener);
}
/**
* fire event
*/
public void fireEvent(IcyColorSpaceEvent e)
{
for (IcyColorSpaceListener listener : new ArrayList<IcyColorSpaceListener>(listeners))
listener.colorSpaceChanged(e);
}
/**
* called when colorspace has changed (afaik when a colormap has changed)
*/
private void changed(int component)
{
final IcyColorMap colorMap = getColorMap(component);
// we can have only 1 alpha colormap
if (colorMap != null)
{
// alpha type colormap ?
if (colorMap.getType() == IcyColorMapType.ALPHA)
{
// check that others colormap are non alpha
for (IcyColorMap map : toRGBmaps)
{
if (map != colorMap)
{
// we have another ALPHA colormap ?
if (map.getType() == IcyColorMapType.ALPHA)
// set it to RGB
map.setType(IcyColorMapType.RGB);
}
}
}
}
// handle changed via updater object
updater.changed(new IcyColorSpaceEvent(this, component));
}
/**
* process on colorspace change
*/
@Override
public void onChanged(CollapsibleEvent compare)
{
final IcyColorSpaceEvent event = (IcyColorSpaceEvent) compare;
// recalculate fromRGB colormaps
generateFromRGBColorMaps();
// notify listener we have changed
fireEvent(event);
}
@Override
public void colorMapChanged(IcyColorMapEvent e)
{
final int index = indexOfColorMap(e.getColormap());
// colormap found ? raise a "changed" event
if (index != -1)
changed(index);
}
/**
* @see icy.common.UpdateEventHandler#beginUpdate()
*/
public void beginUpdate()
{
updater.beginUpdate();
}
/**
* @see icy.common.UpdateEventHandler#endUpdate()
*/
public void endUpdate()
{
updater.endUpdate();
}
/**
* @see icy.common.UpdateEventHandler#isUpdating()
*/
public boolean isUpdating()
{
return updater.isUpdating();
}
}