/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.worldwind.util;
import org.geotools.image.TransfertRectIter;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.resources.i18n.Loggings;
import org.geotools.util.logging.Logging;
import org.opengis.referencing.operation.TransformException;
import javax.media.jai.*;
import javax.media.jai.iterator.RectIterFactory;
import javax.media.jai.iterator.WritableRectIter;
import javax.media.jai.registry.RenderedRegistryMode;
import java.awt.*;
import java.awt.image.RasterFormatException;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
/**
* An image operation that translates pixels in a raster to a different value. The intended use case
* is to recode no data values to a different value (e.g. translate -32768 to -9999), but the
* operation is not limited to recoding no data.
*
* @author Parker Abercrombie
*/
public class RecodeRaster extends PointOpImage
{
private static final Logger LOGGER = Logging.getLogger(RecodeRaster.class);
/**
* The operation name.
*/
public static final String OPERATION_NAME = "org.geotools.RecodeNoData";
/**
* No data value in the source image.
*/
private final double srcVal;
/**
* No data value in the target image. Pixels equal to srcVal will be recoded to this value.
*/
private final double destVal;
public RecodeRaster(final RenderedImage image, final double srcVal, final double destVal,
final RenderingHints hints)
{
super(image, (ImageLayout) hints.get(JAI.KEY_IMAGE_LAYOUT), hints, false);
this.srcVal = srcVal;
this.destVal = destVal;
permitInPlaceOperation();
}
@Override
protected void computeRect(final PlanarImage[] sources,
final WritableRaster dest,
final Rectangle destRect)
{
final PlanarImage source = sources[0];
final Rectangle bounds = destRect.intersection(source.getBounds());
if (!destRect.equals(bounds))
{
// TODO: Check if this case occurs sometime, and fill pixel values if it does.
// If it happen to occurs, we will need to fix other GeoTools operations
// as well.
Logging.getLogger(TransformException.class).warning(
"Bounds mismatch: " + destRect + " and " + bounds);
}
WritableRectIter iterator = RectIterFactory.createWritable(dest, bounds);
// TODO: Detect if source and destination rasters are the same. If they are
// the same, we should skip this block. Iteration will then be faster.
iterator = TransfertRectIter.create(RectIterFactory.create(source, bounds), iterator);
if (!iterator.finishedBands())
{
do
{
recode(iterator);
}
while (!iterator.nextBandDone());
}
}
private void recode(WritableRectIter iterator) throws RasterFormatException
{
iterator.startLines();
if (!iterator.finishedLines())
{
do
{
iterator.startPixels();
if (!iterator.finishedPixels())
{
do
{
double value = iterator.getSampleDouble();
if (value == srcVal)
{
iterator.setSample(destVal);
} else
{
iterator.setSample(value);
}
}
while (!iterator.nextPixelDone());
}
}
while (!iterator.nextLineDone());
}
}
/////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// REGISTRATION OF "Recode" IMAGE OPERATION ////////
//////// ////////
/////////////////////////////////////////////////////////////////////////////////
/**
* The operation descriptor for the "RecodeNoData" operation. This operation translates pixels
* equal to the no data value to a different value.
*/
private static final class Descriptor extends OperationDescriptorImpl
{
/**
* Construct the descriptor.
*/
public Descriptor()
{
super(new String[][]{{"GlobalName", OPERATION_NAME},
{"LocalName", OPERATION_NAME},
{"Vendor", "Geotools 2"},
{"Description", "Translate pixels from one value to another."},
{"DocURL", "http://www.geotools.org/"},
{"Version", "1.0"}},
new String[]{RenderedRegistryMode.MODE_NAME}, 1,
new String[]{"srcVal", "destVal"}, // Argument names
new Class[]{Number.class, Number.class}, // Argument classes
new Object[]{NO_PARAMETER_DEFAULT, NO_PARAMETER_DEFAULT}, // Default values
null // No restriction on valid parameter values.
);
}
}
/**
* The {@link java.awt.image.renderable.RenderedImageFactory} for the {@code "RecodeNoData"}
* operation.
*/
private static final class CRIF extends CRIFImpl
{
/**
* Creates a {@link RenderedImage} representing the results of an imaging operation for a
* given {@link ParameterBlock} and {@link RenderingHints}.
*/
@Override
public RenderedImage create(final ParameterBlock param, final RenderingHints hints)
{
final RenderedImage image = (RenderedImage) param.getSource(0);
final Number srcVal = (Number) param.getObjectParameter(0);
final Number destVal = (Number) param.getObjectParameter(1);
return new RecodeRaster(image, srcVal.doubleValue(), destVal.doubleValue(), hints);
}
}
/**
* Register the "RecodeNoData" image operation to the operation registry of the specified JAI
* instance.
*/
public static void register(final JAI jai)
{
final OperationRegistry registry = jai.getOperationRegistry();
try
{
registry.registerDescriptor(new Descriptor());
registry.registerFactory(RenderedRegistryMode.MODE_NAME, OPERATION_NAME,
"geotools.org", new CRIF());
} catch (IllegalArgumentException exception)
{
final LogRecord record = Loggings.format(Level.SEVERE,
LoggingKeys.CANT_REGISTER_JAI_OPERATION_$1, OPERATION_NAME);
record.setSourceMethodName("<classinit>");
record.setThrown(exception);
record.setLoggerName(LOGGER.getName());
LOGGER.log(record);
}
}
}