/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2003-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotools.resources.image;
// J2SE dependencies
import java.awt.image.BandedSampleModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.Arrays;
/**
* An {@link IndexColorModel} tolerant with image having more than one band.
* <p>
* <strong>Reminder:</strong> {@link #getNumComponents} will returns 3 or 4 no matter
* how many bands were specified to the constructor. This is not specific to this class;
* {@code IndexColorModel} behave that way. So we can't rely on this method for checking
* the number of bands.
*
* @since 2.0
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
* @author Andrea Aime
*/
final class MultiBandsIndexColorModel extends IndexColorModel {
/**
* The number of bands.
*/
final int numBands;
/**
* The visible band.
*/
private final int visibleBand;
/**
* Construct an object with the specified properties.
*
* @param bits The number of bits each pixel occupies.
* @param size The size of the color component arrays.
* @param cmap The array of color components.
* @param start The starting offset of the first color component.
* @param hasalpha Indicates whether alpha values are contained in the {@code cmap} array.
* @param transparent The index of the fully transparent pixel.
* @param transferType The data type of the array used to represent pixel values. The
* data type must be either {@code DataBuffer.TYPE_BYTE} or
* {@code DataBuffer.TYPE_USHORT}.
* @param numBands The number of bands.
* @param visibleBands The band to display.
*
* @throws IllegalArgumentException if {@code bits} is less than 1 or greater than 16.
* @throws IllegalArgumentException if {@code size} is less than 1.
* @throws IllegalArgumentException if {@code transferType} is not one of
* {@code DataBuffer.TYPE_BYTE} or {@code DataBuffer.TYPE_USHORT}.
*/
public MultiBandsIndexColorModel(final int bits,
final int size,
final int[] cmap,
final int start,
final boolean hasAlpha,
final int transparent,
final int transferType,
final int numBands,
final int visibleBand)
{
super(bits, size, cmap, start, hasAlpha, transparent, transferType);
this.numBands = numBands;
this.visibleBand = visibleBand;
}
/**
* Returns a data element array representation of a pixel in this color model,
* given an integer pixel representation in the default RGB color model.
* <p>
* This method returns an array with a length equals to the number of bands specified to
* the constructor ({@code IndexColorModel} would returns an array of length 1). All array
* elements are set to the same value. Replicating the pixel value is a somewhat arbitrary
* choice, but this choice make this image appears as a gray scale image if the underlying
* {@link DataBuffer} were displayed again with a RGB color model instead of this one. Such
* a gray scale image seems more neutral than an image where only the Red component would vary.
* <p>
* All other {@code getDataElement} methods in this color model are ultimately defined in terms
* of this method, so overriding this method should be enough.
*/
public Object getDataElements(final int RGB, Object pixel) {
if (pixel == null) {
switch (transferType) {
case DataBuffer.TYPE_SHORT: // fall through
case DataBuffer.TYPE_USHORT: pixel=new short[numBands]; break;
case DataBuffer.TYPE_BYTE: pixel=new byte [numBands]; break;
case DataBuffer.TYPE_INT: pixel=new int [numBands]; break;
default: throw new UnsupportedOperationException(unsupported());
}
}
pixel = super.getDataElements(RGB, pixel);
switch (transferType) {
case DataBuffer.TYPE_SHORT: // fall through
case DataBuffer.TYPE_USHORT: {
final short[] array = (short[]) pixel;
Arrays.fill(array, 1, numBands, array[0]);
break;
}
case DataBuffer.TYPE_BYTE: {
final byte[] array = (byte[]) pixel;
Arrays.fill(array, 1, numBands, array[0]);
break;
}
case DataBuffer.TYPE_INT: {
final int[] array = (int[]) pixel;
Arrays.fill(array, 1, numBands, array[0]);
break;
}
default: throw new UnsupportedOperationException(unsupported());
}
return pixel;
}
/**
* Returns an array of unnormalized color/alpha components for a specified pixel
* in this color model. This method is the converse of {@link #getDataElements}.
*/
public int[] getComponents(final Object pixel, final int[] components, final int offset) {
final int i;
switch (transferType) {
case DataBuffer.TYPE_SHORT: // Fall through
case DataBuffer.TYPE_USHORT: i=((short[])pixel)[visibleBand] & 0xffff; break;
case DataBuffer.TYPE_BYTE: i=((byte [])pixel)[visibleBand] & 0xff; break;
case DataBuffer.TYPE_INT: i=((int [])pixel)[visibleBand]; break;
default: throw new UnsupportedOperationException(unsupported());
}
return getComponents(i, components, offset);
}
/**
* Returns the red color component for the specified pixel, scaled
* from 0 to 255 in the default RGB {@code ColorSpace}, sRGB.
*/
public int getRed(final Object inData) {
final int pixel;
switch (transferType) {
case DataBuffer.TYPE_BYTE: {
pixel = ((byte[])inData)[visibleBand] & 0xff;
break;
}
case DataBuffer.TYPE_USHORT: {
pixel = ((short[])inData)[visibleBand] & 0xffff;
break;
}
case DataBuffer.TYPE_INT: {
pixel = ((int[])inData)[visibleBand];
break;
}
default: {
throw new UnsupportedOperationException(unsupported());
}
}
return getRed(pixel);
}
/**
* Returns the green color component for the specified pixel, scaled
* from 0 to 255 in the default RGB {@code ColorSpace}, sRGB.
*/
public int getGreen(final Object inData) {
final int pixel;
switch (transferType) {
case DataBuffer.TYPE_BYTE: {
pixel = ((byte[])inData)[visibleBand] & 0xff;
break;
}
case DataBuffer.TYPE_USHORT: {
pixel = ((short[])inData)[visibleBand] & 0xffff;
break;
}
case DataBuffer.TYPE_INT: {
pixel = ((int[])inData)[visibleBand];
break;
}
default: {
throw new UnsupportedOperationException(unsupported());
}
}
return getGreen(pixel);
}
/**
* Returns the blue color component for the specified pixel, scaled
* from 0 to 255 in the default RGB {@code ColorSpace}, sRGB.
*/
public int getBlue(final Object inData) {
final int pixel;
switch (transferType) {
case DataBuffer.TYPE_BYTE: {
pixel = ((byte[])inData)[visibleBand] & 0xff;
break;
}
case DataBuffer.TYPE_USHORT: {
pixel = ((short[])inData)[visibleBand] & 0xffff;
break;
}
case DataBuffer.TYPE_INT: {
pixel = ((int[])inData)[visibleBand];
break;
}
default: {
throw new UnsupportedOperationException(unsupported());
}
}
return getBlue(pixel);
}
/**
* Returns the alpha component for the specified pixel, scaled from 0 to 255.
*/
public int getAlpha(final Object inData) {
final int pixel;
switch (transferType) {
case DataBuffer.TYPE_BYTE: {
pixel = ((byte[])inData)[visibleBand] & 0xff;
break;
}
case DataBuffer.TYPE_USHORT: {
pixel = ((short[])inData)[visibleBand] & 0xffff;
break;
}
case DataBuffer.TYPE_INT: {
pixel = ((int[])inData)[visibleBand];
break;
}
default: {
throw new UnsupportedOperationException(unsupported());
}
}
return getAlpha(pixel);
}
/**
* Returns an error message for unsupported operation exception.
*/
private String unsupported() {
return "This method has not been implemented for transferType " + transferType;
}
/**
* Creates a {@code WritableRaster} with the specified width
* and height that has a data layout ({@code SampleModel})
* compatible with this {@code ColorModel}.
*/
public WritableRaster createCompatibleWritableRaster(final int width, final int height) {
return Raster.createBandedRaster(transferType, width, height, numBands, null);
}
/**
* Returns {@code true} if {@code raster} is compatible
* with this {@code ColorModel}.
*/
public boolean isCompatibleRaster(final Raster raster) {
return isCompatibleSampleModel(raster.getSampleModel());
}
/**
* Creates a {@code SampleModel} with the specified
* width and height that has a data layout compatible with
* this {@code ColorModel}.
*/
public SampleModel createCompatibleSampleModel(final int width, final int height) {
return new BandedSampleModel(transferType, width, height, numBands);
}
/**
* Checks if the specified {@code SampleModel} is compatible
* with this {@code ColorModel}.
*/
public boolean isCompatibleSampleModel(final SampleModel sm) {
return (sm instanceof ComponentSampleModel) &&
sm.getTransferType() == transferType &&
sm.getNumBands() == numBands &&
(1 << sm.getSampleSize(visibleBand)) >= getMapSize();
}
}