/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-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.image.io;
import java.awt.Color;
import java.awt.image.*;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.IIOException;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.image.ColorUtilities;
/**
* A set of RGB colors created by a {@linkplain PaletteFactory palette factory} from a name.
* A palette can creates an {@linkplain IndexColorModel index color model} or an {@linkplain
* ImageTypeSpecifier image type specifier} from the RGB colors. The color model is retained
* by the palette as a {@linkplain WeakReference weak reference} (<strong>not</strong> as a
* {@linkplain java.lang.ref.SoftReference soft reference}) because it may consume up to 256
* kb. The purpose of the weak reference is to share existing instances in order to reduce
* memory usage; the purpose is not to provide caching.
*
* @since 2.4
* @source $URL$
* @version $Id$
* @author Antoine Hnawia
* @author Martin Desruisseaux (IRD)
*/
final class IndexedPalette extends Palette {
/**
* The maximal allowed value, corresponding to the maximum value for unsigned 16 bits integer.
* DO NOT EDIT: this value <strong>MUST</strong> be {@code 0xFFFF}.
*/
private static final int MAX_UNSIGNED = 0xFFFF;
/**
* Index of the first valid element (inclusive) in the {@linkplain IndexColorModel
* index color model} to be created. Pixels in the range 0 inclusive to {@code lower}
* exclusive will be reserved for "no data" values.
* <p>
* Strictly speaking, this index should be non-negative because {@link IndexColorModel}
* do not supports negative index. However this {@code Palette} implementation accepts
* negative values provided that {@link #upper} is not greater than {@value Short#MAX_VALUE}.
* If this condition holds, then {@code Palette} will transpose negative values as positive
* values in the range {@code 0x80000} to {@code 0xFFFF} inclusive. Be aware that such
* approach consume the maximal amount of memory, i.e. 256 kilobytes for each color model.
*/
protected final int lower;
/**
* Index of the last valid element (exclusive) in the {@linkplain IndexColorModel
* index color model} to be created. Pixels in the range {@code upper} inclusive
* to {@link #size} exclusive will be reserved for "no data" values. This value
* is always greater than {@link #lower} (note that it may be negative).
*/
protected final int upper;
/**
* The size of the {@linkplain IndexColorModel index color model} to be created.
* This is the value to be returned by {@link IndexColorModel#getMapSize}. This
* value is always positive.
*/
protected final int size;
/**
* Creates a palette with the specified name and size. The RGB colors will be distributed
* in the range {@code lower} inclusive to {@code upper} exclusive. Remaining pixel values
* (if any) will be left to a black or transparent color by default.
*
* @param factory The originating factory.
* @param name The palette name.
* @param lower Index of the first valid element (inclusive) in the
* {@linkplain IndexColorModel index color model} to be created.
* @param upper Index of the last valid element (exclusive) in the
* {@linkplain IndexColorModel index color model} to be created.
* @param size The size of the {@linkplain IndexColorModel index color model} to be created.
* This is the value to be returned by {@link IndexColorModel#getMapSize}.
* @param numBands The number of bands (usually 1).
* @param visibleBand The band to use for color computations (usually 0).
*/
protected IndexedPalette(final PaletteFactory factory, final String name, final int lower,
final int upper, int size, final int numBands, final int visibleBand)
{
super(factory, name, numBands, visibleBand);
final int minAllowed, maxAllowed; // inclusives
if (lower < 0) {
minAllowed = Short.MIN_VALUE;
maxAllowed = Short.MAX_VALUE;
size = (size <= 0x100) ? 0x100 : (MAX_UNSIGNED + 1);
// 'size' must be FF or FFFF in order to rool negative values.
} else {
minAllowed = 0;
maxAllowed = MAX_UNSIGNED;
}
ensureInsideBounds(lower, minAllowed, maxAllowed);
ensureInsideBounds(upper, minAllowed, maxAllowed + 1);
ensureInsideBounds(size, upper, MAX_UNSIGNED + 1);
if (lower >= upper) {
throw new IllegalArgumentException(factory.getErrorResources().getString(
ErrorKeys.BAD_RANGE_$2, lower, upper));
}
this.lower = lower;
this.upper = upper;
this.size = size;
}
/**
* Returns the scale from <cite>normalized values</cite> (values in the range [0..1])
* to values in the range of this palette.
*/
@Override
double getScale() {
return upper - lower;
}
/**
* Returns the offset from <cite>normalized values</cite> (values in the range [0..1])
* to values in the range of this palette.
*/
@Override
double getOffset() {
return lower;
}
/**
* Creates and returns ARGB values for the {@linkplain IndexColorModel index color model} to be
* created. This method is invoked automatically the first time the color model is required, or
* when it need to be rebuilt.
*
* @throws FileNotFoundException If the RGB values need to be read from a file and this file
* (typically inferred from {@link #name}) is not found.
* @throws IOException If an other find of I/O error occured.
* @throws IIOException If an other kind of error prevent this method to complete.
*/
private int[] createARGB() throws IOException {
final Color[] colors = factory.getColors(name);
if (colors == null) {
throw new FileNotFoundException(factory.getErrorResources().getString(
ErrorKeys.FILE_DOES_NOT_EXIST_$1, name));
}
final int[] ARGB = new int[size];
if (lower >= 0) {
ColorUtilities.expand(colors, ARGB, lower, upper);
} else {
ColorUtilities.expand(colors, ARGB, 0, upper - lower);
final int negativeStart = size + lower;
final int negativeCount = -lower;
final int[] negatives = new int[negativeCount];
System.arraycopy(ARGB, 0, negatives, 0, negativeCount);
System.arraycopy(ARGB, negativeCount, ARGB, 0, negativeStart);
System.arraycopy(negatives, 0, ARGB, negativeStart, negativeCount);
}
return ARGB;
}
/**
* Returns the image type specifier for this palette. This method tries to reuse existing
* color model if possible, since it may consume a significant amount of memory.
*
* @throws FileNotFoundException If the RGB values need to be read from a file and this file
* (typically inferred from {@linkplain #name name}) is not found.
* @throws IOException If an other find of I/O error occured.
* @throws IIOException If an other kind of error prevent this method to complete.
*/
public synchronized ImageTypeSpecifier getImageTypeSpecifier() throws IOException {
/*
* First checks the weak references.
*/
ImageTypeSpecifier its = queryCache();
if (its != null) {
return its;
}
/*
* Nothing reacheable. Rebuild the specifier.
*/
final int[] ARGB = createARGB();
final int bits = ColorUtilities.getBitCount(ARGB.length);
final int type = (bits <= 8) ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT;
final boolean packed = (bits==1 || bits==2 || bits==4);
final boolean dense = (packed || bits==8 || bits==16);
if (dense && (1 << bits) == ARGB.length && numBands == 1) {
final byte[] A = new byte[ARGB.length];
final byte[] R = new byte[ARGB.length];
final byte[] G = new byte[ARGB.length];
final byte[] B = new byte[ARGB.length];
for (int i=0; i<ARGB.length; i++) {
int code = ARGB[i];
B[i] = (byte) ((code ) & 0xFF);
G[i] = (byte) ((code >>>= 8) & 0xFF);
R[i] = (byte) ((code >>>= 8) & 0xFF);
A[i] = (byte) ((code >>>= 8) & 0xFF);
}
its = ImageTypeSpecifier.createIndexed(R,G,B,A, bits, type);
} else {
/*
* The "ImageTypeSpecifier.createIndexed(...)" method is too strict. The IndexColorModel
* constructor is more flexible. This block mimic the "ImageTypeSpecifier.createIndexed"
* work without the constraints imposed by "createIndexed". Being more flexible consume
* less memory for the color palette, since we don't force it to be 64 kb in the USHORT
* data type case.
*/
final IndexColorModel colors = ColorUtilities.getIndexColorModel(ARGB, numBands, visibleBand);
final SampleModel samples;
if (packed) {
samples = new MultiPixelPackedSampleModel(type, 1, 1, bits);
} else {
samples = new PixelInterleavedSampleModel(type, 1, 1, 1, 1, new int[1]);
}
its = new ImageTypeSpecifier(colors, samples);
}
cache(its);
return its;
}
/**
* Returns a hash value for this palette.
*/
@Override
public int hashCode() {
return super.hashCode() + 37*(lower + 37*(upper + 37*size));
}
/**
* Compares this palette with the specified object for equality.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
if (super.equals(object)) {
final IndexedPalette that = (IndexedPalette) object;
return this.lower == that.lower &&
this.upper == that.upper &&
this.size == that.size;
}
return false;
}
/**
* Returns a string representation of this palette. Used for debugging purpose only.
*/
@Override
public String toString() {
return name + " [" + lower + "..." + (upper-1) + "] size=" + size;
}
}