/*
* 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.coverage.grid;
import java.awt.image.DataBuffer;
import java.util.Arrays;
import java.util.Map;
import javax.media.jai.LookupTableJAI;
import static java.lang.Math.*;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.operation.TransformException;
import org.geotools.util.WeakValueHashMap;
/**
* A factory for {@link LookupTableJAI} objects built from an array of {@link MathTransform1D}.
* This factory is used internally by {@link GridCoverageViews#create}.
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
final class LookupTableFactory {
/**
* The pool of {@link LookupTableJAI} objects already created.
*/
private static final Map<LookupTableFactory,LookupTableJAI> pool =
new WeakValueHashMap<LookupTableFactory,LookupTableJAI>();
/**
* The source data type. Should be one of {@link DataBuffer} constants.
*/
private final int sourceType;
/**
* The target data type. Should be one of {@link DataBuffer} constants.
*/
private final int targetType;
/**
* The math transforms for this key.
*/
private final MathTransform1D[] transforms;
/**
* Create a new objet to use as a key in the {@link #pool}.
*
* @param sourceType The source data type. Should be one of {@link DataBuffer} constants.
* @param targetType The target data type. Should be one of {@link DataBuffer} constants.
* @param transforms The math transforms to apply.
*/
private LookupTableFactory(final int sourceType,
final int targetType,
final MathTransform1D[] transforms)
{
this.sourceType = sourceType;
this.targetType = targetType;
this.transforms = transforms;
}
/**
* Gets a lookup factory
*
* @param sourceType The source data type. Should be one of {@link DataBuffer} constants.
* @param targetType The target data type. Should be one of {@link DataBuffer} constants.
* @param transforms The math transforms to apply.
* @return The lookup table, or {@code null} if this method can't build a lookup
* table for the supplied parameters.
* @throws TransformException if a transformation failed.
*/
public static LookupTableJAI create(final int sourceType,
final int targetType,
final MathTransform1D[] transforms)
throws TransformException
{
/*
* Argument check. Null values are legal but can't be processed by this method.
*/
final int nbands = transforms.length;
for (int i=0; i<nbands; i++) {
if (transforms[i] == null) {
return null;
}
}
synchronized (pool) {
/*
* Checks if a table is already available in the cache. Since tables may be 64 kb big,
* sharing tables may save a significant amount of memory if there is many images.
*/
final LookupTableFactory key = new LookupTableFactory(sourceType, targetType, transforms);
LookupTableJAI table = pool.get(key);
if (table != null) {
return table;
}
/*
* Computes the table's size according the source datatype. For datatype 'short' (signed
* or unsigned), we will create the table only if the target datatype is 'byte' in order
* to avoid to use too much memory for the table. The memory consumed for a table from
* source datatype 'short' to target datatype 'byte' is 64 ko.
*/
final int length;
final int offset;
switch (sourceType) {
default: {
return null;
}
case DataBuffer.TYPE_BYTE: {
length = 0x100;
offset = 0;
break;
}
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_USHORT: {
if (targetType != DataBuffer.TYPE_BYTE) {
// Avoid to use too much memory for the table.
return null;
}
length = 0x10000;
offset = (sourceType == DataBuffer.TYPE_SHORT) ? Short.MIN_VALUE : 0;
break;
}
}
/*
* Builds the table according the target datatype.
*/
switch (targetType) {
default: {
return null;
}
case DataBuffer.TYPE_DOUBLE: {
final double[][] data = new double[nbands][];
final double[] buffer = new double[length];
for (int i=length; --i>=0;) {
buffer[i] = i;
}
for (int i=nbands; --i>=0;) {
final double[] array = (i==0) ? buffer : buffer.clone();
transforms[i].transform(array, 0, array, 0, array.length);
data[i] = array;
}
table = new LookupTableJAI(data, offset);
break;
}
case DataBuffer.TYPE_FLOAT: {
final float[][] data = new float[nbands][];
final float[] buffer = new float[length];
for (int i=length; --i>=0;) {
buffer[i] = i;
}
for (int i=transforms.length; --i>=0;) {
final float[] array = (i == 0) ? buffer : buffer.clone();
transforms[i].transform(array, 0, array, 0, length);
data[i] = array;
}
table = new LookupTableJAI(data, offset);
break;
}
case DataBuffer.TYPE_INT: {
final int[][] data = new int[nbands][];
for (int i=nbands; --i>=0;) {
final MathTransform1D tr = transforms[i];
final int[] array = new int[length];
for (int j=length; --j>=0;) {
array[j] = (int) min(max(round(tr.transform(j+offset)),
Integer.MIN_VALUE), Integer.MAX_VALUE);
}
data[i] = array;
}
table = new LookupTableJAI(data, offset);
break;
}
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_USHORT: {
final int minimum, maximum;
if (targetType == DataBuffer.TYPE_SHORT) {
minimum = Short.MIN_VALUE;
maximum = Short.MAX_VALUE;
} else {
minimum = 0;
maximum = 0xFFFF;
}
final short[][] data = new short[nbands][];
for (int i=nbands; --i>=0;) {
final MathTransform1D tr = transforms[i];
final short[] array = new short[length];
for (int j=length; --j>=0;) {
array[j] = (short) min(max(round(tr.transform(j+offset)), minimum), maximum);
}
data[i] = array;
}
table = new LookupTableJAI(data, offset, minimum!=0);
break;
}
case DataBuffer.TYPE_BYTE: {
final byte[][] data = new byte[nbands][];
for (int i=nbands; --i>=0;) {
final MathTransform1D tr = transforms[i];
final byte[] array = new byte[length];
for (int j=length; --j>=0;) {
array[j] = (byte) min(max(round(tr.transform(j+offset)), 0), 0xFF);
}
data[i] = array;
}
table = new LookupTableJAI(data, offset);
break;
}
}
pool.put(key, table);
return table;
}
}
/**
* Returns a hash code value for this key. This is for internal use by
* {@code LookupTableFactory} and is public only as an implementation side effect.
*/
@Override
public int hashCode() {
int code = sourceType + 37*targetType;
code += Arrays.hashCode(transforms);
return code;
}
/**
* Compares the specified object with this key for equality. This is for internal use by
* {@code LookupTableFactory} and is public only as an implementation side effect.
*/
@Override
public boolean equals(final Object other) {
if (other instanceof LookupTableFactory) {
final LookupTableFactory that = (LookupTableFactory) other;
return this.sourceType == that.sourceType &&
this.targetType == that.targetType &&
Arrays.equals(this.transforms, that.transforms);
}
return false;
}
}