/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2015, 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.processing;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.util.Map;
import javax.media.jai.BorderExtender;
import javax.media.jai.ImageLayout;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.OperationDescriptor;
import javax.media.jai.PlanarImage;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.referencing.operation.transform.ConcatenatedTransform;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.resources.image.ImageUtilities;
import org.opengis.coverage.processing.OperationNotFoundException;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.util.InternationalString;
/**
* Base class for providing capabilities to scale {@link GridCoverage2D} objects
* using JAI scale operations.
*
* <p>
* This class tries to handles all the problems related to scaling index-color
* images in order to avoid strange results in the smoothest possible way by
* performing color expansions under the hood as needed. It may also apply some
* optimizations in case we were dealing with non-geo view of coverage.
*
* @author Simone Giannecchini, GeoSolutions.
*
*
* @source $URL$
* @since 2.5
*/
public abstract class BaseScaleOperationJAI extends OperationJAI {
public static final String AFFINE = "Affine";
public static final String SCALE = "Scale";
public static final String TRANSLATE = "Translate";
public static final String WARP = "Warp";
public static final String ROI = "roi";
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 1L;
/**
* Constructor for {@link BaseScaleOperationJAI}.
*
* @param operation name of the {@link JAI} operation we wrap.
* @throws OperationNotFoundException
*/
public BaseScaleOperationJAI(String operation)
throws OperationNotFoundException {
super(operation);
}
/**
* Constructor for {@link BaseScaleOperationJAI}.
*
* @param operation {@link OperationDescriptor} of the {@link JAI} operation we wrap.
*/
public BaseScaleOperationJAI(OperationDescriptor operation) {
super(operation);
}
/**
* Constructor for {@link BaseScaleOperationJAI}.
* @param operation {@link OperationDescriptor} of the {@link JAI} operation we wrap.
* @param descriptor
*/
public BaseScaleOperationJAI(OperationDescriptor operation,
ParameterDescriptorGroup descriptor) {
super(operation, descriptor);
}
@Override
protected GridCoverage2D deriveGridCoverage(GridCoverage2D[] sources, Parameters parameters) {
//
// Getting the input parameters we might need
//
int indexOfInterpolationParam;
try{
indexOfInterpolationParam=parameters.parameters
.indexOfParam("interpolation");
}
catch (IllegalArgumentException e) {
indexOfInterpolationParam=-1;
}
int indexOfBorderExtenderParam;
try{
indexOfBorderExtenderParam=parameters.parameters
.indexOfParam("BorderExtender");
}
catch (IllegalArgumentException e) {
indexOfBorderExtenderParam=-1;
}
final Interpolation interpolation =(Interpolation) (indexOfInterpolationParam==-1?
new InterpolationNearest():
parameters.parameters.getObjectParameter("interpolation")); ;
final BorderExtender borderExtender=
(BorderExtender) (indexOfBorderExtenderParam!=-1?
parameters.parameters.getObjectParameter("BorderExtender"):
ImageUtilities.DEFAULT_BORDER_EXTENDER);
// /////////////////////////////////////////////////////////////////////
//
// Getting the source coverage
//
// /////////////////////////////////////////////////////////////////////
GridCoverage2D sourceCoverage = sources[PRIMARY_SOURCE_INDEX];
RenderedImage sourceImage = sourceCoverage.getRenderedImage();
// /////////////////////////////////////////////////////////////////////
//
// Do we need to explode the Palette to RGB(A)?
//
// /////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////
//
// Managing Hints, especially for output coverage's layout purposes.
//
// It is worthwhile to point out that layout hints for minx, miny, width
// and height are honored by the warp and affine operation. The other
// ImageLayout hints, like tileWidth and tileHeight, are also
// honored.
// /////////////////////////////////////////////////////////////////////
RenderingHints targetHints = parameters.hints!=null?parameters.hints:ImageUtilities.getRenderingHints(sourceImage);
if (targetHints == null)
targetHints = new RenderingHints(null);
if (parameters.hints != null)
targetHints.add(parameters.hints);
ImageLayout layout = (ImageLayout) targetHints.get(JAI.KEY_IMAGE_LAYOUT);
if (layout != null) {
layout = (ImageLayout) layout.clone();
} else {
layout = new ImageLayout(sourceImage);
layout.unsetTileLayout();
// At this point, only the color model and sample model are left
// valid.
}
if ((layout.getValidMask() & (ImageLayout.TILE_WIDTH_MASK
| ImageLayout.TILE_HEIGHT_MASK
| ImageLayout.TILE_GRID_X_OFFSET_MASK | ImageLayout.TILE_GRID_Y_OFFSET_MASK)) == 0) {
layout.setTileGridXOffset(layout.getMinX(sourceImage));
layout.setTileGridYOffset(layout.getMinY(sourceImage));
final int width = layout.getWidth(sourceImage);
final int height = layout.getHeight(sourceImage);
if (layout.getTileWidth(sourceImage) > width)
layout.setTileWidth(width);
if (layout.getTileHeight(sourceImage) > height)
layout.setTileHeight(height);
}
targetHints.put(JAI.KEY_IMAGE_LAYOUT, layout);
targetHints.put(JAI.KEY_BORDER_EXTENDER,borderExtender);
// it is crucial to correctly manage the Hints to control the
// replacement of IndexColorModel. It is worth to point out that setting
// the JAI.KEY_REPLACE_INDEX_COLOR_MODEL hint to true is not enough to
// force the operators to do an expansion.
// If we explicitly provide an ImageLayout built with the source image
// where the CM and the SM are valid. those will be employed overriding
// a the possibility to expand the color model.
final boolean asPhotographicStrategy = sourceImage.getColorModel() instanceof IndexColorModel;
if (!(asPhotographicStrategy))
targetHints.add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
else {
targetHints.add(ImageUtilities.REPLACE_INDEX_COLOR_MODEL);
layout.unsetValid(ImageLayout.COLOR_MODEL_MASK);
layout.unsetValid(ImageLayout.SAMPLE_MODEL_MASK);
}
// /////////////////////////////////////////////////////////////////////
//
// Creating final grid coverage.
//
// /////////////////////////////////////////////////////////////////////
RenderedImage image= createRenderedImage(parameters.parameters,targetHints);
// /////////////////////////////////////////////////////////////////////
//
// Preparing the resulting grid to world transformation
//
//
////
//
// This step is crucial for making a leap towards a more robust
// implementation of the scaling operations and also towards their
// integration as operation JAI subclasses.
//
// What we do here is quite trivial, we take into account the initial
// Grid to World transform and then we concatenate to it a
// transformation that takes into account the scaling we just performed.
//
// /////////////////////////////////////////////////////////////////////
final double scaleX=image.getWidth()/ (1.0 * sourceImage.getWidth());
final double scaleY=image.getHeight()/ (1.0 * sourceImage.getHeight());
final double tX=image.getMinX()-sourceImage.getMinX()*scaleX;
final double tY=image.getMinY()-sourceImage.getMinY()*scaleY;
final AffineTransform scaleTranslate= new AffineTransform(
scaleX,
0,
0,
scaleY,
tX,
tY);
final MathTransform finalTransform;
try{
scaleTranslate.invert();
scaleTranslate.preConcatenate(CoverageUtilities.CENTER_TO_CORNER);
final AffineTransform2D tr= new AffineTransform2D(scaleTranslate);
finalTransform= ConcatenatedTransform.create(tr,sourceCoverage.getGridGeometry().getGridToCRS2D());
} catch (Exception e) {
throw new RuntimeException(e);
}
// /////////////////////////////////////////////////////////////////////
//
// Preparing the resulting coverage
//
// /////////////////////////////////////////////////////////////////////
/*
* Performs the operation using JAI and construct the new grid coverage.
* Uses the coordinate system from the main source coverage in order to
* preserve the extra dimensions (if any). The first two dimensions should
* be equal to the coordinate system set in the 'parameters' block.
*/
final InternationalString name = deriveName(sources, 0, parameters);
final Map<String,?> properties = getProperties(image,parameters.crs,name,finalTransform,sources,parameters);
final GridGeometry2D gg2D = new GridGeometry2D(
new GridEnvelope2D(PlanarImage.wrapRenderedImage(image).getBounds()),
PixelInCell.CELL_CORNER,
finalTransform,
parameters.crs,
null);
final GridCoverage2D result = getFactory(parameters.hints)
.create(name, // The grid coverage name
image, // The underlying data
gg2D,
(GridSampleDimension[]) (asPhotographicStrategy ? null
: sourceCoverage.getSampleDimensions().clone()), // The sample dimensions
sources, // The source grid coverage.
properties); // Properties
// now let's see what we need to do in order to clean things up
return result;
}
}