/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011-2016, Open Source Geospatial Foundation (OSGeo)
* (C) 2014 TOPP - www.openplans.org.
*
* 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.operation;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.jai.BorderExtender;
import javax.media.jai.BorderExtenderConstant;
import javax.media.jai.GeometricOpImage;
import javax.media.jai.ImageLayout;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import javax.media.jai.ROIShape;
import javax.media.jai.RasterAccessor;
import javax.media.jai.RasterFormatTag;
import javax.media.jai.RenderedOp;
import javax.media.jai.TileCache;
import javax.media.jai.iterator.RandomIter;
import javax.media.jai.operator.ConstantDescriptor;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.factory.GeoTools;
import org.geotools.factory.Hints;
import org.geotools.image.ImageWorker;
import org.geotools.referencing.CRS;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.util.Utilities;
import it.geosolutions.jaiext.utilities.ImageLayout2;
import org.opengis.metadata.spatial.PixelOrientation;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.TransformException;
import com.sun.media.jai.util.ImageUtil;
import com.sun.media.jai.util.PropertyGeneratorImpl;
import it.geosolutions.jaiext.iterators.RandomIterFactory;
/**
* A RenderedImage that provides values coming from a source GridCoverage2D, with a backing grid
* addressable as the target GridCoverage2D.
* <P>
* The exposed Layout will be the same as the target, and each Point in the target grid can be used
* in the resulting RenderedImage,
*
*
* @author ETj <etj at geo-solutions.it>
*
* @source $URL$
*/
public class GridCoverage2DRIA extends GeometricOpImage {
private final static Logger LOGGER = Logger.getLogger(GridCoverage2DRIA.class.getName());
private final GridCoverage2D src;
private final GridGeometry2D dst;
private final MathTransform g2wd;
private final MathTransform g2ws;
private final MathTransform w2gd;
private final MathTransform w2gs;
private final MathTransform src2dstCRSTransform;
private final MathTransform dst2srcCRSTransform;
/** Color table representing source's IndexColorModel. */
private byte[][] ctable = null; // ETj: just for keeping compiler quiet: let's see if we really
private ROI roi;
private boolean hasROI;
private Rectangle roiBounds;
private PlanarImage roiImage;
/**
* Wrap the src coverage in the dst layout. <BR>
* The resulting RenderedImage will contain the data in src, and will be accessible via the grid
* specs of dst,
*
* @param src
* the data coverage to be remapped on dst grid
* @param dst
* the provider of the final grid
* @param nodata
* the nodata value to set for cells not covered by src but included in dst. All
* bands will share the same nodata value.
* @return an instance of Coverage2RenderedImageAdapter
*/
public static GridCoverage2DRIA create(final GridCoverage2D src, final GridGeometry2D dst, final double[] nodata) {
return create(src, dst, nodata, null, null);
}
public static GridCoverage2DRIA create(final GridCoverage2D src, final GridCoverage2D dst,
final double[] nodata) {
return create(src, dst, nodata, null, null);
}
public static GridCoverage2DRIA create(GridCoverage2D src, GridGeometry2D dst,
double[] nodata, Hints hints){
return create(src, dst, nodata, hints, null);
}
/**
* Wrap the src coverage in the dst layout. <BR>
* The resulting RenderedImage will contain the data in src, and will be accessible via the grid
* specs of dst,
*
* @param src
* the data coverage to be remapped on dst grid
* @param dst
* the provider of the final grid
* @param nodata
* the nodata values to set for cells not covered by src but included in dst. All
* bands will use the related nodata value.
* @param hints hints to use for the Rendering, Actually only ImageLayout is considered
* @param roi
* @return an instance of Coverage2RenderedImageAdapter
*/
public static GridCoverage2DRIA create(GridCoverage2D src, GridGeometry2D dst,
double[] nodata, Hints hints, ROI roi) {
Utilities.ensureNonNull("dst", dst);
// === Create destination Layout, retaining source tiling to minimize quirks
// TODO allow to override tiling
final GridEnvelope2D destinationRasterDimension = dst.getGridRange2D();
final ImageLayout imageLayout = new ImageLayout();
imageLayout.setMinX(destinationRasterDimension.x).setMinY(destinationRasterDimension.y);
imageLayout.setWidth(destinationRasterDimension.width).setHeight(destinationRasterDimension.height);
//
// SampleModel and ColorModel are related to data itself, so we
// copy them from the source
imageLayout.setColorModel(src.getRenderedImage().getColorModel());
imageLayout.setSampleModel(src.getRenderedImage().getSampleModel());
if(hints != null && hints.containsKey(JAI.KEY_IMAGE_LAYOUT)){
ImageLayout l = (ImageLayout) hints.get(JAI.KEY_IMAGE_LAYOUT);
if(l.isValid(ImageLayout.TILE_HEIGHT_MASK) && l.isValid(ImageLayout.TILE_WIDTH_MASK)){
imageLayout.setTileHeight(Math.min(imageLayout.getHeight(null), l.getTileHeight(null)));
imageLayout.setTileWidth(Math.min(imageLayout.getWidth(null), l.getTileWidth(null)));
}
}
// === BorderExtender
//
// We have yet to check for it usefulness: it might be more convenient
// to check for region overlapping and return a nodata value by hand,
// so to avoid problems with interpolation at source raster borders.
//
BorderExtender extender = new BorderExtenderConstant(new double[] {nodata != null ? nodata[0] : 0d});
// Check if the input coverage contains a ROI
ROI property = CoverageUtilities.getROIProperty(src);
if(property != null){
roi = roi != null ? roi.intersect(property) : property;
}
return new GridCoverage2DRIA(src, dst, vectorize(src.getRenderedImage()), imageLayout,
null, false, extender, Interpolation.getInstance(Interpolation.INTERP_NEAREST),
nodata, roi, hints);
}
// need it
/**
* Wrap the src coverage in the dst layout. <BR>
* The resulting RenderedImage will contain the data in src, and will be accessible via the grid
* specs of dst,
*
* @param src
* the data coverage to be remapped on dst grid
* @param dst
* the provider of the final grid
* @param nodata
* the nodata values to set for cells not covered by src but included in dst. All
* bands will use the related nodata value.
*
* @param roi
* @return an instance of Coverage2RenderedImageAdapter
*/
public static GridCoverage2DRIA create(final GridCoverage2D src, final GridCoverage2D dst,
final double[] nodata, Hints hints, ROI roi) {
// === Create Layout
final ImageLayout imageLayout = new ImageLayout(dst.getRenderedImage());
//
// SampleModel and ColorModel are related to data itself, so we
// copy them from the source
imageLayout.setColorModel(src.getRenderedImage().getColorModel());
imageLayout.setSampleModel(src.getRenderedImage().getSampleModel());
// === BorderExtender
//
// We have yet to check for it usefulness: it might be more convenient
// to check for region overlapping and return a nodata value by hand,
// so to avoid problems with interpolation at source raster borders.
//
BorderExtender extender = new BorderExtenderConstant(new double[] { nodata != null ? nodata[0] : 0d });
// Check if the input coverage contains a ROI
ROI property = CoverageUtilities.getROIProperty(src);
if(property != null){
roi = roi != null ? roi.intersect(property) : property;
}
return new GridCoverage2DRIA(src, dst.getGridGeometry(), vectorize(src.getRenderedImage()), imageLayout,
null, false, extender, Interpolation.getInstance(Interpolation.INTERP_NEAREST),
nodata, roi, hints);
}
protected GridCoverage2DRIA(final GridCoverage2D src, final GridGeometry2D dst,
final Vector sources, final ImageLayout layout, final Map configuration,
final boolean cobbleSources, final BorderExtender extender, final Interpolation interp,
final double[] nodata, ROI roi, Hints hints) {
super(sources, layout, configuration, cobbleSources, extender, interp, nodata);
this.src = src;
this.dst = dst;
// === Take one for all all the transformation we need to pass from
// model, sample, src, target and viceversa.
g2wd = dst.getGridToCRS2D(PixelOrientation.UPPER_LEFT);
try {
w2gd = g2wd.inverse();
} catch (org.opengis.referencing.operation.NoninvertibleTransformException e) {
throw new IllegalArgumentException("Can't compute destination W2G", e);
}
g2ws = src.getGridGeometry().getGridToCRS2D(PixelOrientation.UPPER_LEFT);
try {
w2gs = g2ws.inverse();
} catch (org.opengis.referencing.operation.NoninvertibleTransformException e) {
throw new IllegalArgumentException("Can't compute source W2G", e);
}
try {
CoordinateReferenceSystem sourceCRS = src.getCoordinateReferenceSystem2D();
CoordinateReferenceSystem targetCRS = dst.getCoordinateReferenceSystem2D();
src2dstCRSTransform = CRS.findMathTransform(sourceCRS, targetCRS, true);
dst2srcCRSTransform = src2dstCRSTransform.inverse();
} catch (FactoryException e) {
throw new IllegalArgumentException("Can't create a transform between CRS", e);
} catch (NoninvertibleTransformException e) {
throw new IllegalArgumentException("Can't create a transform between CRS", e);
}
// Input ROI
this.roi = roi;
this.hasROI = roi != null;
if(hasROI){
this.roiBounds = roi.getBounds();
setProperty("roi", roi);
}
// ensure we have tile caching
if(hints != null) {
TileCache tc = (TileCache) hints.get(JAI.KEY_TILE_CACHE);
if(tc != null) {
setTileCache(tc);
} else {
setTileCache(JAI.getDefaultInstance().getTileCache());
}
}
}
@Override
public Raster getTile(int tileX, int tileY) {
return super.getTile(tileX, tileY);
}
@Override
public Point2D mapSourcePoint(Point2D srcPt, int sourceIndex) {
if (srcPt == null) {
throw new IllegalArgumentException("Bad dest pt");// JaiI18N.getString("Generic0"));
} else if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
throw new IndexOutOfBoundsException("Bad src");// JaiI18N.getString("Generic1"));
}
double[] coords = new double[] { srcPt.getX(), srcPt.getY() };
try {
mapSrcPoint(coords);
} catch (TransformException e) {
LOGGER.log(Level.WARNING, "Error transforming coords", e);
return null;
}
Point2D ret = ((Point2D) srcPt.clone());
ret.setLocation(coords[0], coords[1]);
if (inside(ret, src.getRenderedImage()))
return ret;
else {
LOGGER.log(Level.WARNING, "{0} mapped to {1} lies outside {2},{3}+{4}x{5}",
new Object[] { srcPt, ret, src.getRenderedImage().getMinX(),
src.getRenderedImage().getMinX(), src.getRenderedImage().getWidth(),
src.getRenderedImage().getHeight() });
return null;
}
}
private static boolean inside(Point2D point, RenderedImage ri) {
double x = point.getX();
double y = point.getY();
return x >= ri.getMinX() && x <= ri.getMinX() + ri.getWidth() && y >= ri.getMinY()
&& y <= ri.getMinY() + ri.getHeight();
}
/**
* Returns the minimum bounding box of the region of the destination to which a particular
* <code>Rectangle</code> of the specified source will be mapped.
*
* <p>
* The integral source rectangle coordinates should be considered pixel indices. The "energy" of
* each pixel is defined to be concentrated in the continuous plane of pixels at an offset of
* (0.5, 0.5) from the index of the pixel. Forward mappings must take this (0.5, 0.5)
* pixel center into account. Thus given integral source pixel indices as input, the fractional
* destination location, as calculated by functions Xf(xSrc, ySrc), Yf(xSrc, ySrc), is
* given by:
*
* <pre>
*
* xDst = Xf(xSrc+0.5, ySrc+0.5) - 0.5
* yDst = Yf(xSrc+0.5, ySrc+0.5) - 0.5
*
* </pre>
*
* @param sourceRect
* the <code>Rectangle</code> in source coordinates.
* @param sourceIndex
* the index of the source image.
*
* @return a <code>Rectangle</code> indicating the destination bounding box, or
* <code>null</code> if the bounding box is unknown.
*
* @throws IllegalArgumentException
* if <code>sourceIndex</code> is negative or greater than the index of the last
* source.
* @throws IllegalArgumentException
* if <code>sourceRect</code> is <code>null</code>.
*/
@Override
protected Rectangle forwardMapRect(Rectangle pxRect, int i) {
// transformation from out target coverage toward the source one.
// note that source/target names from OpImage are reversed with respect to our
// definitions
// i is not used, only one source raster
float[] pts = rect2PointArr(pxRect);
try {
g2wd.transform(pts, 0, pts, 0, 4);
dst2srcCRSTransform.transform(pts, 0, pts, 0, 4);
w2gs.transform(pts, 0, pts, 0, 4);
} catch (TransformException e) {
LOGGER.log(Level.WARNING, "Error transforming coords", e);
return null;
}
Rectangle srcRect = pointArr2Rect(pts);
return srcRect; // .intersection(src.getGridGeometry().getGridRange2D());
}
@Override
public Point2D mapDestPoint(Point2D destPt, int sourceIndex) {
if (destPt == null) {
throw new IllegalArgumentException("Bad dest pt");// JaiI18N.getString("Generic0"));
} else if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
throw new IndexOutOfBoundsException("Bad src");// JaiI18N.getString("Generic1"));
}
double[] coords = new double[] { destPt.getX(), destPt.getY() };
try {
mapDestPoint(coords);
} catch (TransformException e) {
LOGGER.log(Level.WARNING, "Error transforming coords", e);
return null;
}
Point2D ret = ((Point2D) destPt.clone());
ret.setLocation(coords[0], coords[1]);
if (dst.getEnvelope2D().contains(ret))
return ret;
else
return null;
}
private void mapDestPoint(double[] coords) throws TransformException {
final int npoints = coords.length / 2;
g2ws.transform(coords, 0, coords, 0, npoints);
src2dstCRSTransform.transform(coords, 0, coords, 0, npoints);
w2gd.transform(coords, 0, coords, 0, npoints);
}
private void mapSrcPoint(double[] coords) throws TransformException {
final int npoints = coords.length / 2;
// StringBuilder sb = new StringBuilder();
// sb.append("SRC[").append(coords[0]).append(',').append(coords[1]).append(']').append("--g2wd->");
g2wd.transform(coords, 0, coords, 0, npoints);
// sb.append("[").append(coords[0]).append(',').append(coords[1]).append(']').append("--d2sCRS->");
dst2srcCRSTransform.transform(coords, 0, coords, 0, npoints);
// sb.append('[').append(coords[0]).append(',').append(coords[1]).append(']').append("--w2gs->");
w2gs.transform(coords, 0, coords, 0, npoints);
// sb.append('[').append(coords[0]).append(',').append(coords[1]).append(']');
// System.out.println(sb);
}
/**
* Returns the minimum bounding box of the region of the specified source to which a particular
* <code>Rectangle</code> of the destination will be mapped.
*
* <p>
* The integral destination rectangle coordinates should be considered pixel indices. The
* "energy" of each pixel is defined to be concentrated in the continuous plane of pixels at an
* offset of (0.5, 0.5) from the index of the pixel. Backward mappings must take this
* (0.5, 0.5) pixel center into account. Thus given integral destination pixel indices as
* input, the fractional source location, as calculated by functions Xb(xDst, yDst),
* Yb(xDst, yDst), is given by:
*
* <pre>
*
* xSrc = Xb(xDst+0.5, yDst+0.5) - 0.5
* ySrc = Yb(xDst+0.5, yDst+0.5) - 0.5
*
* </pre>
*
* @param destRect
* the <code>Rectangle</code> in destination coordinates.
* @param sourceIndex
* the index of the source image.
*
* @return a <code>Rectangle</code> indicating the source bounding box, or <code>null</code> if
* the bounding box is unknown.
*
* @throws IllegalArgumentException
* if <code>sourceIndex</code> is negative or greater than the index of the last
* source.
* @throws IllegalArgumentException
* if <code>destRect</code> is <code>null</code>.
*/
@Override
protected Rectangle backwardMapRect(Rectangle destRect, int sourceIndex) {
float[] pts = rect2PointArr(destRect);
try {
g2ws.transform(pts, 0, pts, 0, 4);
src2dstCRSTransform.transform(pts, 0, pts, 0, 4);
w2gd.transform(pts, 0, pts, 0, 4);
} catch (TransformException e) {
LOGGER.log(Level.WARNING, "Error transforming coords", e);
return null;
}
Rectangle pxRect = pointArr2Rect(pts);
return pxRect;
}
private static float[] rect2PointArr(Rectangle rect) {
float dx0 = rect.x;
float dy0 = rect.y;
float dw = (rect.width);
float dh = (rect.height);
return new float[] { dx0, dy0, (dx0 + dw), dy0, (dx0 + dw), (dy0 + dh), dx0, (dy0 + dh) };
}
private Rectangle pointArr2Rect(float[] points) {
float f_sx0 = Float.MAX_VALUE;
float f_sy0 = Float.MAX_VALUE;
float f_sx1 = -Float.MAX_VALUE;
float f_sy1 = -Float.MAX_VALUE;
for (int i = 0; i < 4; i++) {
float px = points[i * 2];
float py = points[i * 2 + 1];
f_sx0 = Math.min(f_sx0, px);
f_sy0 = Math.min(f_sy0, py);
f_sx1 = Math.max(f_sx1, px);
f_sy1 = Math.max(f_sy1, py);
}
int s_x0 = 0, s_y0 = 0, s_x1 = 0, s_y1 = 0;
// Find the bounding box of the source rectangle
if (interp instanceof InterpolationNearest) {
s_x0 = (int) Math.floor(f_sx0);
s_y0 = (int) Math.floor(f_sy0);
// Fix for bug 4485920 was to add " + 0.05" to the following
// two lines. It should be noted that the fix was made based
// on empirical evidence and tested thoroughly, but it is not
// known whether this is the root cause.
s_x1 = (int) Math.ceil(f_sx1 + 0.5);
s_y1 = (int) Math.ceil(f_sy1 + 0.5);
} else {
s_x0 = (int) Math.floor(f_sx0 - 0.5);
s_y0 = (int) Math.floor(f_sy0 - 0.5);
s_x1 = (int) Math.ceil(f_sx1);
s_y1 = (int) Math.ceil(f_sy1);
}
//
// Return the new rectangle
//
return new Rectangle(s_x0, s_y0, s_x1 - s_x0, s_y1 - s_y0);
}
/**
* Warps a rectangle.
*
* Offers Improved performance with support for no data and region of interest.
*/
@Override
protected void computeRect(PlanarImage[] sources, WritableRaster dest, Rectangle destRect) {
RasterFormatTag formatTag = getFormatTags()[1];
RasterAccessor d = new RasterAccessor(dest, destRect, formatTag, getColorModel());
RandomIter roiIter = null;
boolean roiContainsTile = false;
boolean roiDisjointTile = false;
// If a ROI is present, then only the part contained inside the current tile bounds is taken.
if (hasROI) {
Rectangle srcRectExpanded = null;
int x = (int) destRect.getMinX();
int y = (int) destRect.getMinY();
int w = (int) destRect.getWidth();
int h = (int) destRect.getHeight();
float[] src = new float[w * h * 2];
warpRect(x, y, w, h, src);
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
int numP = src.length;
for (int i = 0; i < numP; i = i + 2) {
float xi = src[i];
float yi = src[i + 1];
minX = xi < minX ? xi : minX;
minY = yi < minY ? yi : minY;
maxX = xi > maxX ? xi : maxX;
maxY = yi > maxY ? yi : maxY;
}
srcRectExpanded = new Rectangle((int)minX, (int)minY, (int)(maxX - minX) + 1, (int)(maxY - minY) + 1);
// The tile dimension is extended for avoiding border errors
srcRectExpanded.setRect(
srcRectExpanded.getMinX() - interp.getLeftPadding(),
srcRectExpanded.getMinY() - interp.getTopPadding(),
srcRectExpanded.getWidth() + interp.getRightPadding() + interp.getLeftPadding(),
srcRectExpanded.getHeight() + interp.getBottomPadding() + interp.getTopPadding());
if (!roiBounds.intersects(srcRectExpanded)) {
roiDisjointTile = true;
} else {
roiContainsTile = roi.contains(srcRectExpanded);
if (!roiContainsTile) {
if (!roi.intersects(srcRectExpanded)) {
roiDisjointTile = true;
} else {
PlanarImage roiIMG = getImage();
roiIter = RandomIterFactory.create(roiIMG, null, true, true);
}
}
}
}
if (roiDisjointTile) {
double[] bkg = setBackground ? backgroundValues : new double[dest.getNumBands()];
ImageUtil.fillBackground(dest, destRect, bkg);
return;
}
computeRect(sources[0], d, roiIter, roiContainsTile);
if (d.isDataCopy()) {
d.clampDataArrays();
d.copyDataToRaster();
}
}
private void computeRect(PlanarImage source, RasterAccessor d, RandomIter roiIter,
boolean roiContainsTile) {
switch (d.getDataType()) {
case DataBuffer.TYPE_BYTE:
computeRectByte(source, d, roiIter, roiContainsTile);
break;
case DataBuffer.TYPE_USHORT:
computeRectUShort(source, d, roiIter, roiContainsTile);
break;
case DataBuffer.TYPE_SHORT:
computeRectShort(source, d, roiIter, roiContainsTile);
break;
case DataBuffer.TYPE_INT:
computeRectInt(source, d, roiIter, roiContainsTile);
break;
case DataBuffer.TYPE_FLOAT:
computeRectFloat(source, d, roiIter, roiContainsTile);
break;
case DataBuffer.TYPE_DOUBLE:
computeRectDouble(source, d, roiIter, roiContainsTile);
break;
}
}
private void computeRectByte(PlanarImage src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {
int lpad, rpad, tpad, bpad;
if (interp != null) {
lpad = interp.getLeftPadding();
rpad = interp.getRightPadding();
tpad = interp.getTopPadding();
bpad = interp.getBottomPadding();
} else {
lpad = rpad = tpad = bpad = 0;
}
int minX, maxX, minY, maxY;
RandomIter iter = null;
try {
if (extender != null) {
minX = src.getMinX();
maxX = src.getMaxX();
minY = src.getMinY();
maxY = src.getMaxY();
iter = getRandomIterator(src, lpad, rpad, tpad, bpad, extender);
} else {
minX = src.getMinX() + lpad;
maxX = src.getMaxX() - rpad;
minY = src.getMinY() + tpad;
maxY = src.getMaxY() - bpad;
iter = getRandomIterator(src);
}
int kwidth = interp.getWidth();
int kheight = interp.getHeight();
int dstWidth = dst.getWidth();
int dstHeight = dst.getHeight();
int dstBands = dst.getNumBands();
int lineStride = dst.getScanlineStride();
int pixelStride = dst.getPixelStride();
int[] bandOffsets = dst.getBandOffsets();
byte[][] data = dst.getByteDataArrays();
int precH = 1 << interp.getSubsampleBitsH();
int precV = 1 << interp.getSubsampleBitsV();
float[] warpData = new float[2 * dstWidth];
int[][] samples = new int[kheight][kwidth];
int lineOffset = 0;
byte[] backgroundByte = new byte[dstBands];
for (int i = 0; i < dstBands; i++) {
backgroundByte[i] = (byte) backgroundValues[i];
}
if (ctable == null) { // source does not have IndexColorModel
for (int h = 0; h < dstHeight; h++) {
int pixelOffset = lineOffset;
lineOffset += lineStride;
warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
int count = 0;
for (int w = 0; w < dstWidth; w++) {
float sx = warpData[count++];
float sy = warpData[count++];
int xint = floor(sx);
int yint = floor(sy);
int xfrac = (int) ((sx - xint) * precH);
int yfrac = (int) ((sy - yint) * precV);
if (xint < minX || xint >= maxX || yint < minY || yint >= maxY || !inROI(xint, yint, roiIter, roiContainsTile)) {
/* Fill with a background color. */
if (setBackground) {
for (int b = 0; b < dstBands; b++) {
data[b][pixelOffset + bandOffsets[b]] = backgroundByte[b];
}
}
} else {
xint -= lpad;
yint -= tpad;
for (int b = 0; b < dstBands; b++) {
for (int j = 0; j < kheight; j++) {
for (int i = 0; i < kwidth; i++) {
samples[j][i] = iter.getSample(xint + i, yint + j, b) & 0xFF;
}
}
data[b][pixelOffset + bandOffsets[b]] = ImageUtil.clampByte(interp
.interpolate(samples, xfrac, yfrac));
}
}
pixelOffset += pixelStride;
}
}
} else { // source has IndexColorModel
for (int h = 0; h < dstHeight; h++) {
int pixelOffset = lineOffset;
lineOffset += lineStride;
warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
int count = 0;
for (int w = 0; w < dstWidth; w++) {
float sx = warpData[count++];
float sy = warpData[count++];
int xint = floor(sx);
int yint = floor(sy);
int xfrac = (int) ((sx - xint) * precH);
int yfrac = (int) ((sy - yint) * precV);
if (xint < minX || xint >= maxX || yint < minY || yint >= maxY || !inROI(xint, yint, roiIter, roiContainsTile)) {
/* Fill with a background color. */
if (setBackground) {
for (int b = 0; b < dstBands; b++) {
data[b][pixelOffset + bandOffsets[b]] = backgroundByte[b];
}
}
} else {
xint -= lpad;
yint -= tpad;
for (int b = 0; b < dstBands; b++) {
byte[] t = ctable[b];
for (int j = 0; j < kheight; j++) {
for (int i = 0; i < kwidth; i++) {
samples[j][i] = t[iter.getSample(xint + i, yint + j, 0) & 0xFF] & 0xFF;
}
}
data[b][pixelOffset + bandOffsets[b]] = ImageUtil.clampByte(interp
.interpolate(samples, xfrac, yfrac));
}
}
pixelOffset += pixelStride;
}
}
}
} finally {
if(iter != null) {
iter.done();
}
}
}
private void computeRectUShort(PlanarImage src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {
int lpad, rpad, tpad, bpad;
if (interp != null) {
lpad = interp.getLeftPadding();
rpad = interp.getRightPadding();
tpad = interp.getTopPadding();
bpad = interp.getBottomPadding();
} else {
lpad = rpad = tpad = bpad = 0;
}
int minX, maxX, minY, maxY;
RandomIter iter = null;
try {
if (extender != null) {
minX = src.getMinX();
maxX = src.getMaxX();
minY = src.getMinY();
maxY = src.getMaxY();
iter = getRandomIterator(src, lpad, rpad, tpad, bpad, extender);
} else {
minX = src.getMinX() + lpad;
maxX = src.getMaxX() - rpad;
minY = src.getMinY() + tpad;
maxY = src.getMaxY() - bpad;
iter = getRandomIterator(src);
}
int kwidth = interp.getWidth();
int kheight = interp.getHeight();
int dstWidth = dst.getWidth();
int dstHeight = dst.getHeight();
int dstBands = dst.getNumBands();
int lineStride = dst.getScanlineStride();
int pixelStride = dst.getPixelStride();
int[] bandOffsets = dst.getBandOffsets();
short[][] data = dst.getShortDataArrays();
int precH = 1 << interp.getSubsampleBitsH();
int precV = 1 << interp.getSubsampleBitsV();
float[] warpData = new float[2 * dstWidth];
int[][] samples = new int[kheight][kwidth];
int lineOffset = 0;
short[] backgroundUShort = new short[dstBands];
for (int i = 0; i < dstBands; i++) {
backgroundUShort[i] = (short) backgroundValues[i];
}
for (int h = 0; h < dstHeight; h++) {
int pixelOffset = lineOffset;
lineOffset += lineStride;
warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
int count = 0;
for (int w = 0; w < dstWidth; w++) {
float sx = warpData[count++];
float sy = warpData[count++];
int xint = floor(sx);
int yint = floor(sy);
int xfrac = (int) ((sx - xint) * precH);
int yfrac = (int) ((sy - yint) * precV);
if (xint < minX || xint >= maxX || yint < minY || yint >= maxY || !inROI(xint, yint, roiIter, roiContainsTile)) {
/* Fill with a background color. */
if (setBackground) {
for (int b = 0; b < dstBands; b++) {
data[b][pixelOffset + bandOffsets[b]] = backgroundUShort[b];
}
}
} else {
xint -= lpad;
yint -= tpad;
for (int b = 0; b < dstBands; b++) {
for (int j = 0; j < kheight; j++) {
for (int i = 0; i < kwidth; i++) {
samples[j][i] = iter.getSample(xint + i, yint + j, b) & 0xFFFF;
}
}
data[b][pixelOffset + bandOffsets[b]] = ImageUtil.clampUShort(interp
.interpolate(samples, xfrac, yfrac));
}
}
pixelOffset += pixelStride;
}
}
} finally {
if(iter != null) {
iter.done();
}
}
}
private void computeRectShort(PlanarImage src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {
int lpad, rpad, tpad, bpad;
if (interp != null) {
lpad = interp.getLeftPadding();
rpad = interp.getRightPadding();
tpad = interp.getTopPadding();
bpad = interp.getBottomPadding();
} else {
lpad = rpad = tpad = bpad = 0;
}
int minX, maxX, minY, maxY;
RandomIter iter = null;
try {
if (extender != null) {
minX = src.getMinX();
maxX = src.getMaxX();
minY = src.getMinY();
maxY = src.getMaxY();
iter = getRandomIterator(src, lpad, rpad, tpad, bpad, extender);
} else {
minX = src.getMinX() + lpad;
maxX = src.getMaxX() - rpad;
minY = src.getMinY() + tpad;
maxY = src.getMaxY() - bpad;
iter = getRandomIterator(src);
}
int kwidth = interp.getWidth();
int kheight = interp.getHeight();
int dstWidth = dst.getWidth();
int dstHeight = dst.getHeight();
int dstBands = dst.getNumBands();
int lineStride = dst.getScanlineStride();
int pixelStride = dst.getPixelStride();
int[] bandOffsets = dst.getBandOffsets();
short[][] data = dst.getShortDataArrays();
int precH = 1 << interp.getSubsampleBitsH();
int precV = 1 << interp.getSubsampleBitsV();
float[] warpData = new float[2 * dstWidth];
int[][] samples = new int[kheight][kwidth];
int lineOffset = 0;
short[] backgroundShort = new short[dstBands];
for (int i = 0; i < dstBands; i++) {
backgroundShort[i] = (short) backgroundValues[i];
}
for (int h = 0; h < dstHeight; h++) {
int pixelOffset = lineOffset;
lineOffset += lineStride;
warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
int count = 0;
for (int w = 0; w < dstWidth; w++) {
float sx = warpData[count++];
float sy = warpData[count++];
int xint = floor(sx);
int yint = floor(sy);
int xfrac = (int) ((sx - xint) * precH);
int yfrac = (int) ((sy - yint) * precV);
if (xint < minX || xint >= maxX || yint < minY || yint >= maxY || !inROI(xint, yint, roiIter, roiContainsTile)) {
/* Fill with a background color. */
if (setBackground) {
for (int b = 0; b < dstBands; b++) {
data[b][pixelOffset + bandOffsets[b]] = backgroundShort[b];
}
}
} else {
xint -= lpad;
yint -= tpad;
for (int b = 0; b < dstBands; b++) {
for (int j = 0; j < kheight; j++) {
for (int i = 0; i < kwidth; i++) {
samples[j][i] = iter.getSample(xint + i, yint + j, b);
}
}
data[b][pixelOffset + bandOffsets[b]] = ImageUtil.clampShort(interp
.interpolate(samples, xfrac, yfrac));
}
}
pixelOffset += pixelStride;
}
}
} finally {
if(iter != null) {
iter.done();
}
}
}
private void computeRectInt(PlanarImage src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {
int lpad, rpad, tpad, bpad;
if (interp != null) {
lpad = interp.getLeftPadding();
rpad = interp.getRightPadding();
tpad = interp.getTopPadding();
bpad = interp.getBottomPadding();
} else {
lpad = rpad = tpad = bpad = 0;
}
int minX, maxX, minY, maxY;
RandomIter iter = null;
try {
if (extender != null) {
minX = src.getMinX();
maxX = src.getMaxX();
minY = src.getMinY();
maxY = src.getMaxY();
iter = getRandomIterator(src, lpad, rpad, tpad, bpad, extender);
} else {
minX = src.getMinX() + lpad;
maxX = src.getMaxX() - rpad;
minY = src.getMinY() + tpad;
maxY = src.getMaxY() - bpad;
iter = getRandomIterator(src);
}
int kwidth = interp.getWidth();
int kheight = interp.getHeight();
int dstWidth = dst.getWidth();
int dstHeight = dst.getHeight();
int dstBands = dst.getNumBands();
int lineStride = dst.getScanlineStride();
int pixelStride = dst.getPixelStride();
int[] bandOffsets = dst.getBandOffsets();
int[][] data = dst.getIntDataArrays();
int precH = 1 << interp.getSubsampleBitsH();
int precV = 1 << interp.getSubsampleBitsV();
float[] warpData = new float[2 * dstWidth];
int[][] samples = new int[kheight][kwidth];
int lineOffset = 0;
int[] backgroundInt = new int[dstBands];
for (int i = 0; i < dstBands; i++) {
backgroundInt[i] = (int) backgroundValues[i];
}
for (int h = 0; h < dstHeight; h++) {
int pixelOffset = lineOffset;
lineOffset += lineStride;
warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
int count = 0;
for (int w = 0; w < dstWidth; w++) {
float sx = warpData[count++];
float sy = warpData[count++];
int xint = floor(sx);
int yint = floor(sy);
int xfrac = (int) ((sx - xint) * precH);
int yfrac = (int) ((sy - yint) * precV);
if (xint < minX || xint >= maxX || yint < minY || yint >= maxY || !inROI(xint, yint, roiIter, roiContainsTile)) {
/* Fill with a background color. */
if (setBackground) {
for (int b = 0; b < dstBands; b++) {
data[b][pixelOffset + bandOffsets[b]] = backgroundInt[b];
}
}
} else {
xint -= lpad;
yint -= tpad;
for (int b = 0; b < dstBands; b++) {
for (int j = 0; j < kheight; j++) {
for (int i = 0; i < kwidth; i++) {
samples[j][i] = iter.getSample(xint + i, yint + j, b);
}
}
data[b][pixelOffset + bandOffsets[b]] = interp.interpolate(samples, xfrac,
yfrac);
}
}
pixelOffset += pixelStride;
}
}
} finally {
if(iter != null) {
iter.done();
}
}
}
private void computeRectFloat(PlanarImage src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {
int lpad, rpad, tpad, bpad;
if (interp != null) {
lpad = interp.getLeftPadding();
rpad = interp.getRightPadding();
tpad = interp.getTopPadding();
bpad = interp.getBottomPadding();
} else {
lpad = rpad = tpad = bpad = 0;
}
int minX, maxX, minY, maxY;
RandomIter iter = null;
try {
if (extender != null) {
minX = src.getMinX();
maxX = src.getMaxX();
minY = src.getMinY();
maxY = src.getMaxY();
iter = getRandomIterator(src, lpad, rpad, tpad, bpad, extender);
} else {
minX = src.getMinX() + lpad;
maxX = src.getMaxX() - rpad;
minY = src.getMinY() + tpad;
maxY = src.getMaxY() - bpad;
iter = getRandomIterator(src);
}
int kwidth = interp.getWidth();
int kheight = interp.getHeight();
int dstWidth = dst.getWidth();
int dstHeight = dst.getHeight();
int dstBands = dst.getNumBands();
int lineStride = dst.getScanlineStride();
int pixelStride = dst.getPixelStride();
int[] bandOffsets = dst.getBandOffsets();
float[][] data = dst.getFloatDataArrays();
float[] warpData = new float[2 * dstWidth];
float[][] samples = new float[kheight][kwidth];
int lineOffset = 0;
float[] backgroundFloat = new float[dstBands];
for (int i = 0; i < dstBands; i++) {
backgroundFloat[i] = (float) backgroundValues[i];
}
for (int h = 0; h < dstHeight; h++) {
int pixelOffset = lineOffset;
lineOffset += lineStride;
warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
int count = 0;
for (int w = 0; w < dstWidth; w++) {
float sx = warpData[count++];
float sy = warpData[count++];
int xint = floor(sx);
int yint = floor(sy);
float xfrac = sx - xint;
float yfrac = sy - yint;
if (xint < minX || xint >= maxX || yint < minY || yint >= maxY || !inROI(xint, yint, roiIter, roiContainsTile)) {
/* Fill with a background color. */
if (setBackground) {
for (int b = 0; b < dstBands; b++) {
data[b][pixelOffset + bandOffsets[b]] = backgroundFloat[b];
}
}
} else {
xint -= lpad;
yint -= tpad;
for (int b = 0; b < dstBands; b++) {
for (int j = 0; j < kheight; j++) {
for (int i = 0; i < kwidth; i++) {
samples[j][i] = iter.getSampleFloat(xint + i, yint + j, b);
}
}
data[b][pixelOffset + bandOffsets[b]] = interp.interpolate(samples, xfrac,
yfrac);
}
}
pixelOffset += pixelStride;
}
}
} finally {
if(iter != null) {
iter.done();
}
}
}
private void computeRectDouble(PlanarImage src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {
int lpad, rpad, tpad, bpad;
if (interp != null) {
lpad = interp.getLeftPadding();
rpad = interp.getRightPadding();
tpad = interp.getTopPadding();
bpad = interp.getBottomPadding();
} else {
lpad = rpad = tpad = bpad = 0;
}
int minX, maxX, minY, maxY;
RandomIter iter = null;
try {
if (extender != null) {
minX = src.getMinX();
maxX = src.getMaxX();
minY = src.getMinY();
maxY = src.getMaxY();
iter = getRandomIterator(src, lpad, rpad, tpad, bpad, extender);
} else {
minX = src.getMinX() + lpad;
maxX = src.getMaxX() - rpad;
minY = src.getMinY() + tpad;
maxY = src.getMaxY() - bpad;
iter = getRandomIterator(src);
}
int kwidth = interp.getWidth();
int kheight = interp.getHeight();
int dstWidth = dst.getWidth();
int dstHeight = dst.getHeight();
int dstBands = dst.getNumBands();
int lineStride = dst.getScanlineStride();
int pixelStride = dst.getPixelStride();
int[] bandOffsets = dst.getBandOffsets();
double[][] data = dst.getDoubleDataArrays();
float[] warpData = new float[2 * dstWidth];
double[][] samples = new double[kheight][kwidth];
int lineOffset = 0;
for (int h = 0; h < dstHeight; h++) {
int pixelOffset = lineOffset;
lineOffset += lineStride;
warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
int count = 0;
for (int w = 0; w < dstWidth; w++) {
float sx = warpData[count++];
float sy = warpData[count++];
int xint = floor(sx);
int yint = floor(sy);
float xfrac = sx - xint;
float yfrac = sy - yint;
if (xint < minX || xint >= maxX || yint < minY || yint >= maxY || !inROI(xint, yint, roiIter, roiContainsTile)) {
/* Fill with a background color. */
if (setBackground) {
for (int b = 0; b < dstBands; b++) {
data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
}
}
} else {
xint -= lpad;
yint -= tpad;
for (int b = 0; b < dstBands; b++) {
for (int j = 0; j < kheight; j++) {
for (int i = 0; i < kwidth; i++) {
samples[j][i] = iter.getSampleDouble(xint + i, yint + j, b);
}
}
data[b][pixelOffset + bandOffsets[b]] = interp.interpolate(samples, xfrac,
yfrac);
}
}
pixelOffset += pixelStride;
}
}
} finally {
if(iter != null) {
iter.done();
}
}
}
/** Returns the "floor" value of a float. */
private static final int floor(float f) {
return f >= 0 ? (int) f : (int) f - 1;
}
public float[] warpRect(int x, int y, int width, int height, float[] destRect) {
if (destRect != null && destRect.length < (width * height * 2)) {
throw new IllegalArgumentException("warpRect: bad destRect");// JaiI18N.getString("Warp0"));
}
return warpSparseRect(x, y, width, height, 1, 1, destRect);
}
/**
* @param x0
* The minimum X coordinate of the destination region.
* @param y0
* The minimum Y coordinate of the destination region.
* @param width
* The width of the destination region.
* @param height
* The height of the destination region.
* @param periodX
* The horizontal sampling period.
* @param periodY
* The vertical sampling period.
*
* @param destRect
* A <code>float</code> array containing at least
* <code>2*((width+periodX-1)/periodX)*
* ((height+periodY-1)/periodY)</code> elements, or <code>null</code>. If
* <code>null</code>, a new array will be constructed.
*
* @return A reference to the <code>destRect</code> parameter if it is non-<code>null</code>, or
* a new <code>float</code> array otherwise.
*/
public float[] warpSparseRect(int x0, int y0, int width, int height, int periodX, int periodY,
float[] destRect) {
// XXX: This method should do its calculations in doubles
if (destRect == null) {
destRect = new float[((width + periodX - 1) / periodX)
* ((height + periodY - 1) / periodY) * 2];
}
width += x0;
height += y0;
int index = 0; // destRect index
double[] xy = new double[2];
for (int y = y0; y < height; y += periodY) {
for (int x = x0; x < width; x += periodX) {
xy[0] = x;
xy[1] = y;
try {
mapSrcPoint(xy);
destRect[index++] = (float) xy[0];
destRect[index++] = (float) xy[1];
} catch (TransformException e) {
LOGGER.log(Level.WARNING, "Error transforming {0}", xy);
destRect[index++] = Float.NaN; // ???
destRect[index++] = Float.NaN; // ???
}
}
}
return destRect;
}
private RandomIter getRandomIterator(final PlanarImage src) {
return getRandomIterator(src, 0, 1, 0, 1, extender);
}
private RandomIter getRandomIterator(final PlanarImage src, int leftPad, int rightPad,
int topPad, int bottomPad, BorderExtender extender) {
return ExtendedRandomIter.getRandomIterator(src, leftPad, rightPad, topPad, bottomPad, extender);
}
/**
* This method provides a lazy initialization of the image associated to the ROI.
* The method uses the Double-checked locking in order to maintain thread-safety
*
* @return
*/
private PlanarImage getImage() {
PlanarImage img = roiImage;
if (img == null) {
synchronized (this) {
img = roiImage;
if (img == null) {
roiImage = img = roi.getAsImage();
}
}
}
return img;
}
private boolean inROI(int x, int y, RandomIter roiIter, boolean roiContainsTile){
if(hasROI){
if (roiContainsTile) {
return true;
}
if (!roiBounds.contains(x, y)) {
return false;
} else {
final int sample = roiIter.getSample(x, y, 0);
return sample > 0;
}
} else {
return true;
}
}
/**
* This property generator computes the properties for the operation "GridCoverage2DRIA" dynamically.
*/
static class GridCoverage2DRIAPropertyGenerator extends PropertyGeneratorImpl {
/** Constructor. */
public GridCoverage2DRIAPropertyGenerator() {
super(new String[] { "ROI" }, new Class[] { ROI.class }, new Class[] { GridCoverage2DRIA.class });
}
/**
* Returns the specified property.
*
* @param name Property name.
* @param opNode Operation node.
*/
public Object getProperty(String name, Object opNode) {
validate(name, opNode);
if (opNode instanceof GridCoverage2DRIA && name.equalsIgnoreCase("roi")) {
GridCoverage2DRIA op = (GridCoverage2DRIA) opNode;
// Retrieve the rendered source image and its ROI.
RenderedImage src = op.src.getRenderedImage();
Object property = op.getProperty("ROI");
if (property == null || property.equals(java.awt.Image.UndefinedProperty)
|| !(property instanceof ROI)) {
return java.awt.Image.UndefinedProperty;
}
// Return undefined also if source ROI is empty.
ROI srcROI = (ROI) property;
if (srcROI.getBounds().isEmpty()) {
return java.awt.Image.UndefinedProperty;
}
// Retrieve the Interpolation object.
InterpolationNearest interp = (InterpolationNearest) Interpolation.getInstance(Interpolation.INTERP_NEAREST);
// Determine the effective source bounds.
Rectangle srcBounds = null;
PlanarImage dst = op;
if (dst instanceof GeometricOpImage
&& ((GeometricOpImage) dst).getBorderExtender() == null) {
srcBounds = new Rectangle(src.getMinX() + interp.getLeftPadding(), src.getMinY()
+ interp.getTopPadding(), src.getWidth() - interp.getWidth() + 1,
src.getHeight() - interp.getHeight() + 1);
} else {
srcBounds = new Rectangle(src.getMinX(), src.getMinY(), src.getWidth(),
src.getHeight());
}
// If necessary, clip the ROI to the effective source bounds.
if (!srcBounds.contains(srcROI.getBounds())) {
srcROI = srcROI.intersect(new ROIShape(srcBounds));
}
// Setting constant image to be warped as a ROI
Rectangle dstBounds = op.getBounds();
// Setting layout of the constant image
ImageLayout2 layout = new ImageLayout2();
int minx = (int) srcBounds.getMinX();
int miny = (int) srcBounds.getMinY();
int w = (int) srcBounds.getWidth();
int h = (int) srcBounds.getHeight();
layout.setMinX(minx);
layout.setMinY(miny);
layout.setWidth(w);
layout.setHeight(h);
RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
final PlanarImage constantImage = ConstantDescriptor.create(new Float(w), new Float(h),
new Byte[] { (byte) 255 }, hints);
GridCoverage2D input = new GridCoverageFactory(GeoTools.getDefaultHints()).create(
name, constantImage, op.src.getEnvelope());
PlanarImage roiImage = null;
// Creating warped roi by the same way (Warp, Interpolation, source ROI) we warped the
// input image.
roiImage = create(input, op.dst, new double[]{0d}, null, srcROI);
ROI dstROI = new ROI(roiImage, 1);
// If necessary, clip the warped ROI to the destination bounds.
if (!dstBounds.contains(dstROI.getBounds())) {
dstROI = dstROI.intersect(new ROIShape(dstBounds));
}
// Return the warped and possibly clipped ROI.
return dstROI;
}
return java.awt.Image.UndefinedProperty;
}
}
}