/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
* http://www.geo-solutions.it/
* Copyright 2014 GeoSolutions
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.geosolutions.jaiext.scale;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.util.Map;
import javax.media.jai.BorderExtender;
import javax.media.jai.GeometricOpImage;
import javax.media.jai.ImageLayout;
import javax.media.jai.IntegerSequence;
import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.OpImage;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import javax.media.jai.RenderedOp;
import javax.media.jai.WarpOpImage;
import com.sun.media.jai.util.ImageUtil;
import com.sun.media.jai.util.Rational;
import it.geosolutions.jaiext.interpolators.InterpolationBicubic;
import it.geosolutions.jaiext.interpolators.InterpolationBilinear;
import it.geosolutions.jaiext.interpolators.InterpolationNearest;
import it.geosolutions.jaiext.range.Range;
/**
* A class extending <code>WarpOpImage</code> for use by further extension classes that perform image scaling. Image scaling operations require
* rectilinear backwards mapping and padding by the resampling filter dimensions.
*
* <p>
* When applying scale factors of scaleX, scaleY to a source image with the upper left pixel at (srcMinX, srcMinY) and width of srcWidth and height of
* srcHeight, the resulting image is defined to have the following bounds:
*
* <code>
* dstMinX = ceil(A), where A = srcMinX * scaleX - 0.5 + transX,
* dstMinY = ceil(B), where B = srcMinY * scaleY - 0.5 + transY,
* dstMaxX = ceil(C), where C = (srcMaxX + 1) * scaleX - 1.5 + transX
* and srcMaxX = srcMinX + srcWidth - 1
* dstMaxY = ceil(D), where D = (srcMaxY + 1) * scaleY - 1.5 + transY
* and srcMaxY = srcMinY + srcHeight - 1
* dstWidth = dstMaxX - dstMinX + 1
* dstHeight = dstMaxY - dstMinY + 1
* </code>
*
* <p>
* In the case where source's upper left pixel is located is (0, 0), the formulae simplify to
*
* <code>
* dstMinX = 0
* dstMinY = 0
* dstWidth = ceil (srcWidth * scaleX - 0.5 + transX)
* dstHeight = ceil (srcHeight * scaleY - 0.5 + transY)
* </code>
*
* <p>
* In the case where the source's upper left pixel is located at (0, 0) and the scaling factors are integers, the formulae further simplify to
*
* <code>
* dstMinX = 0
* dstMinY = 0
* dstWidth = ceil (srcWidth * scaleX + transX)
* dstWidth = ceil (srcHeight * scaleY + transY)
* </code>
*
* <p>
* When interpolations which require padding the source such as Bilinear or Bicubic interpolation are specified, the source needs to be extended such
* that it has the extra pixels needed to compute all the destination pixels. This extension is performed via the <code>BorderExtender</code> class.
* The type of border extension can be specified as a <code>RenderingHint</code> to the <code>JAI.create</code> method.
*
* <p>
* If no <code>BorderExtender</code> is specified, the source will not be extended. The scaled image size is still calculated according to the formula
* specified above. However since there is not enough source to compute all the destination pixels, only that subset of the destination image's pixels
* which can be computed, will be written in the destination. The rest of the destination will be set to zeros.
*
* <p>
* It may be noted that the minX, minY, width and height hints as specified through the <code>JAI.KEY_IMAGE_LAYOUT</code> hint in the
* <code>RenderingHints</code> object are not honored, as this operator calculates the destination image bounds itself. The other
* <code>ImageLayout</code> hints, like tileWidth and tileHeight, however are honored.
*
* It should be noted that the superclass <code>GeometricOpImage</code> automatically adds a value of <code>Boolean.TRUE</code> for the
* <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> to the given <code>configuration</code> and passes it up to its superclass constructor so that
* geometric operations are performed on the pixel values instead of being performed on the indices into the color map for those operations whose
* source(s) have an <code>IndexColorModel</code>. This addition will take place only if a value for the
* <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> has not already been provided by the user. Note that the <code>configuration</code> Map is cloned
* before the new hint is added to it. Regarding the value for the <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> <code>RenderingHints</code>, the
* operator itself can be smart based on the parameters, i.e. while the default value for the <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> is
* <code>Boolean.TRUE</code> for operations that extend this class, in some cases the operator could set the default.
*
* @see WarpOpImage
* @see OpImage
*
*/
public abstract class ScaleOpImage extends GeometricOpImage {
/** The horizontal scale factor. */
protected float scaleX;
/** The vertical scale factor. */
protected float scaleY;
/** Thee horizontal translation factor */
protected float transX;
/** The vertical translation factor */
protected float transY;
/*** Rational representations */
protected Rational scaleXRational, scaleYRational;
protected long scaleXRationalNum, scaleXRationalDenom;
protected long scaleYRationalNum, scaleYRationalDenom;
protected Rational invScaleXRational, invScaleYRational;
protected long invScaleXRationalNum, invScaleXRationalDenom;
protected long invScaleYRationalNum, invScaleYRationalDenom;
protected Rational transXRational, transYRational;
protected long transXRationalNum, transXRationalDenom;
protected long transYRationalNum, transYRationalDenom;
protected static float rationalTolerance = 0.000001F;
// Padding
private int lpad, rpad, tpad, bpad;
/** Source ROI */
protected final ROI srcROI;
/** ROI image */
protected final PlanarImage srcROIImage;
/** Boolean indicating if a ROI object is used */
protected final boolean hasROI;
/** Rectangle containing ROI bounds */
protected final Rectangle roiBounds;
/** Value indicating if roi RasterAccessor should be used on computations */
protected boolean useRoiAccessor;
/** Inverse scale value X */
protected long invScaleXInt;
/** Inverse scale fractional value X */
protected long invScaleXFrac;
/** Inverse scale value Y */
protected long invScaleYInt;
/** Inverse scale fractional value Y */
protected long invScaleYFrac;
/** Interpolator provided to the Scale operator */
protected Interpolation interpolator = null;
/** Boolean for checking if the image is binary or not */
protected boolean isBinary;
/** Subsample bits used for binary and bicubic interpolation */
protected int subsampleBits;
/** Value used for calculating the fractional part of the y position */
protected int one;
/** Interpolation kernel width */
protected int interp_width;
/** Interpolation kernel heigth */
protected int interp_height;
/** Interpolation kernel left padding */
protected int interp_left;
/** Interpolation kernel top padding */
protected int interp_top;
/** Value used for calculating the bilinear interpolation for integer dataTypes */
protected int shift;
/** Value used for calculating the bilinear interpolation */
protected int shift2;
/** The value of 0.5 scaled by 2^subsampleBits */
protected int round2;
/** Precision bits used for bicubic interpolation */
protected int precisionBits;
/** The value of 0.5 scaled by 2^precisionBits */
protected int round;
/** Image dataType */
protected int dataType;
/** No Data Range */
protected Range noData;
/** Boolean for checking if no data range is present */
protected boolean hasNoData = false;
/** Destination value for No Data byte */
protected byte[] destinationNoDataByte;
/** Destination value for No Data ushort */
protected short[] destinationNoDataUShort;
/** Destination value for No Data short */
protected short[] destinationNoDataShort;
/** Destination value for No Data int */
protected int[] destinationNoDataInt;
/** Destination value for No Data float */
protected float[] destinationNoDataFloat;
/** Destination value for No Data double */
protected double[] destinationNoDataDouble;
/** Boolean for checking if the no data is negative infinity */
protected boolean isNegativeInf = false;
/** Boolean for checking if the no data is positive infinity */
protected boolean isPositiveInf = false;
/** Boolean for checking if the no data is NaN */
protected boolean isRangeNaN = false;
/** Boolean for checking if the interpolator is Nearest */
protected boolean isNearestNew = false;
/** Boolean for checking if the interpolator is Bilinear */
protected boolean isBilinearNew = false;
/** Boolean for checking if the interpolator is Bicubic */
protected boolean isBicubicNew = false;
/** Boolean indicating if No Data and ROI are not used */
protected boolean caseA;
/** Boolean indicating if only the ROI is used */
protected boolean caseB;
/** Boolean indicating if only the No Data are used */
protected boolean caseC;
/** Extended ROI image*/
protected RenderedOp srcROIImgExt;
/** Extended source Image*/
protected RenderedOp extendedIMG;
/** The extended bounds used by the roi iterator */
protected Rectangle roiRect;
/** ROI Border Extender */
final static BorderExtender roiExtender = BorderExtender
.createInstance(BorderExtender.BORDER_ZERO);
// FORMULAE FOR FORWARD MAP are derived as follows
// Nearest
// Minimum:
// srcMin = floor ((dstMin + 0.5 - trans) / scale)
// srcMin <= (dstMin + 0.5 - trans) / scale < srcMin + 1
// srcMin*scale <= dstMin + 0.5 - trans < (srcMin + 1)*scale
// srcMin*scale - 0.5 + trans
// <= dstMin < (srcMin + 1)*scale - 0.5 + trans
// Let A = srcMin*scale - 0.5 + trans,
// Let B = (srcMin + 1)*scale - 0.5 + trans
//
// dstMin = ceil(A)
//
// Maximum:
// Note that srcMax is defined to be srcMin + dimension - 1
// srcMax = floor ((dstMax + 0.5 - trans) / scale)
// srcMax <= (dstMax + 0.5 - trans) / scale < srcMax + 1
// srcMax*scale <= dstMax + 0.5 - trans < (srcMax + 1)*scale
// srcMax*scale - 0.5 + trans
// <= dstMax < (srcMax+1) * scale - 0.5 + trans
// Let float A = (srcMax + 1) * scale - 0.5 + trans
//
// dstMax = floor(A), if floor(A) < A, else
// dstMax = floor(A) - 1
// OR dstMax = ceil(A - 1)
//
// Other interpolations
//
// First the source should be shrunk by the padding that is
// required for the particular interpolation. Then the
// shrunk source should be forward mapped as follows:
//
// Minimum:
// srcMin = floor (((dstMin + 0.5 - trans)/scale) - 0.5)
// srcMin <= ((dstMin + 0.5 - trans)/scale) - 0.5 < srcMin+1
// (srcMin+0.5)*scale <= dstMin+0.5-trans <
// (srcMin+1.5)*scale
// (srcMin+0.5)*scale - 0.5 + trans
// <= dstMin < (srcMin+1.5)*scale - 0.5 + trans
// Let A = (srcMin+0.5)*scale - 0.5 + trans,
// Let B = (srcMin+1.5)*scale - 0.5 + trans
//
// dstMin = ceil(A)
//
// Maximum:
// srcMax is defined as srcMin + dimension - 1
// srcMax = floor (((dstMax + 0.5 - trans) / scale) - 0.5)
// srcMax <= ((dstMax + 0.5 - trans)/scale) - 0.5 < srcMax+1
// (srcMax+0.5)*scale <= dstMax + 0.5 - trans <
// (srcMax+1.5)*scale
// (srcMax+0.5)*scale - 0.5 + trans
// <= dstMax < (srcMax+1.5)*scale - 0.5 + trans
// Let float A = (srcMax+1.5)*scale - 0.5 + trans
//
// dstMax = floor(A), if floor(A) < A, else
// dstMax = floor(A) - 1
// OR dstMax = ceil(A - 1)
//
private static ImageLayout layoutHelper(RenderedImage source, float scaleX, float scaleY,
float transX, float transY, Interpolation interp, ImageLayout il) {
// Represent the scale factors as Rational numbers.
// Since a value of 1.2 is represented as 1.200001 which
// throws the forward/backward mapping in certain situations.
// Convert the scale and translation factors to Rational numbers
Rational scaleXRational = Rational.approximate(scaleX, rationalTolerance);
Rational scaleYRational = Rational.approximate(scaleY, rationalTolerance);
long scaleXRationalNum = (long) scaleXRational.num;
long scaleXRationalDenom = (long) scaleXRational.denom;
long scaleYRationalNum = (long) scaleYRational.num;
long scaleYRationalDenom = (long) scaleYRational.denom;
Rational transXRational = Rational.approximate(transX, rationalTolerance);
Rational transYRational = Rational.approximate(transY, rationalTolerance);
long transXRationalNum = (long) transXRational.num;
long transXRationalDenom = (long) transXRational.denom;
long transYRationalNum = (long) transYRational.num;
long transYRationalDenom = (long) transYRational.denom;
ImageLayout layout = (il == null) ? new ImageLayout() : (ImageLayout) il.clone();
int x0 = source.getMinX();
int y0 = source.getMinY();
int w = source.getWidth();
int h = source.getHeight();
// Variables to store the calculated destination upper left coordinate
long dx0Num, dx0Denom, dy0Num, dy0Denom;
// Variables to store the calculated destination bottom right
// coordinate
long dx1Num, dx1Denom, dy1Num, dy1Denom;
// Start calculations for destination
dx0Num = x0;
dx0Denom = 1;
dy0Num = y0;
dy0Denom = 1;
// Formula requires srcMaxX + 1 = (x0 + w - 1) + 1 = x0 + w
dx1Num = x0 + w;
dx1Denom = 1;
// Formula requires srcMaxY + 1 = (y0 + h - 1) + 1 = y0 + h
dy1Num = y0 + h;
dy1Denom = 1;
dx0Num *= scaleXRationalNum;
dx0Denom *= scaleXRationalDenom;
dy0Num *= scaleYRationalNum;
dy0Denom *= scaleYRationalDenom;
dx1Num *= scaleXRationalNum;
dx1Denom *= scaleXRationalDenom;
dy1Num *= scaleYRationalNum;
dy1Denom *= scaleYRationalDenom;
// Equivalent to subtracting 0.5
dx0Num = 2 * dx0Num - dx0Denom;
dx0Denom *= 2;
dy0Num = 2 * dy0Num - dy0Denom;
dy0Denom *= 2;
// Equivalent to subtracting 1.5
dx1Num = 2 * dx1Num - 3 * dx1Denom;
dx1Denom *= 2;
dy1Num = 2 * dy1Num - 3 * dy1Denom;
dy1Denom *= 2;
// Adding translation factors
// Equivalent to float dx0 += transX
dx0Num = dx0Num * transXRationalDenom + transXRationalNum * dx0Denom;
dx0Denom *= transXRationalDenom;
// Equivalent to float dy0 += transY
dy0Num = dy0Num * transYRationalDenom + transYRationalNum * dy0Denom;
dy0Denom *= transYRationalDenom;
// Equivalent to float dx1 += transX
dx1Num = dx1Num * transXRationalDenom + transXRationalNum * dx1Denom;
dx1Denom *= transXRationalDenom;
// Equivalent to float dy1 += transY
dy1Num = dy1Num * transYRationalDenom + transYRationalNum * dy1Denom;
dy1Denom *= transYRationalDenom;
// Get the integral coordinates
int l_x0, l_y0, l_x1, l_y1;
l_x0 = Rational.ceil(dx0Num, dx0Denom);
l_y0 = Rational.ceil(dy0Num, dy0Denom);
l_x1 = Rational.ceil(dx1Num, dx1Denom);
l_y1 = Rational.ceil(dy1Num, dy1Denom);
// Set the top left coordinate of the destination
layout.setMinX(l_x0);
layout.setMinY(l_y0);
int width = l_x1 - l_x0 + 1;
int height = l_y1 - l_y0 + 1;
if (width < 1)
width = 1;
if (height < 1)
height = 1;
// Width and height
layout.setWidth(width);
layout.setHeight(height);
return layout;
}
// This method precompute the integer and fractional position of every pixel
protected final void preComputePositionsInt(Rectangle destRect, int srcRectX, int srcRectY,
int srcPixelStride, int srcScanlineStride, int xpos[], int ypos[], int[] xfracvalues,
int[] yfracvalues, int roiScanlineStride, int[] yposRoi) {
// Destination Rectangle position
final int dwidth = destRect.width;
final int dheight = destRect.height;
// Loop variables based on the destination rectangle to be calculated.
final int dx = destRect.x;
final int dy = destRect.y;
// Initially the y source value is calculated by the destination value and then performing the inverse
// scale operation on it.
long syNum = dy, syDenom = 1;
// Subtract the X translation factor sy -= transY
syNum = syNum * transYRationalDenom - transYRationalNum * syDenom;
syDenom *= transYRationalDenom;
// Add 0.5
syNum = 2 * syNum + syDenom;
syDenom *= 2;
// Multply by invScaleX
syNum *= invScaleYRationalNum;
syDenom *= invScaleYRationalDenom;
if (isBilinearNew || isBicubicNew) {
// Subtract 0.5
syNum = 2 * syNum - syDenom;
syDenom *= 2;
}
// Separate the y source coordinate into integer and fractional part
int srcYInt = Rational.floor(syNum, syDenom);
long srcYFrac = syNum % syDenom;
if (srcYInt < 0) {
srcYFrac = syDenom + srcYFrac;
}
// Normalize - Get a common denominator for the fracs of
// src and invScaleY
final long commonYDenom = syDenom * invScaleYRationalDenom;
srcYFrac *= invScaleYRationalDenom;
final long newInvScaleYFrac = invScaleYFrac * syDenom;
// Initially the x source value is calculated by the destination value and then performing the inverse
// scale operation on it.
long sxNum = dx, sxDenom = 1;
// Subtract the X translation factor sx -= transX
sxNum = sxNum * transXRationalDenom - transXRationalNum * sxDenom;
sxDenom *= transXRationalDenom;
// Add 0.5
sxNum = 2 * sxNum + sxDenom;
sxDenom *= 2;
// Multply by invScaleX
sxNum *= invScaleXRationalNum;
sxDenom *= invScaleXRationalDenom;
if (isBilinearNew || isBicubicNew) {
// Subtract 0.5
sxNum = 2 * sxNum - sxDenom;
sxDenom *= 2;
}
// Separate the x source coordinate into integer and fractional part
int srcXInt = Rational.floor(sxNum, sxDenom);
long srcXFrac = sxNum % sxDenom;
if (srcXInt < 0) {
srcXFrac = sxDenom + srcXFrac;
}
// Normalize - Get a common denominator for the fracs of
// src and invScaleX
final long commonXDenom = sxDenom * invScaleXRationalDenom;
srcXFrac *= invScaleXRationalDenom;
final long newInvScaleXFrac = invScaleXFrac * sxDenom;
// Store of the x positions
for (int i = 0; i < dwidth; i++) {
if (isBinary) {
xpos[i] = srcXInt;
} else {
xpos[i] = (srcXInt - srcRectX) * srcPixelStride;
}
// Calculate the yfrac value
if (isBilinearNew || isBicubicNew) {
xfracvalues[i] = (int) (((1.0f * srcXFrac) / commonXDenom) * one);
}
// Move onto the next source pixel.
// Add the integral part of invScaleX to the integral part
// of srcX
srcXInt += invScaleXInt;
// Add the fractional part of invScaleX to the fractional part
// of srcX
srcXFrac += newInvScaleXFrac;
// If the fractional part is now greater than equal to the
// denominator, divide so as to reduce the numerator to be less
// than the denominator and add the overflow to the integral part.
if (srcXFrac >= commonXDenom) {
srcXInt += 1;
srcXFrac -= commonXDenom;
}
}
// Store of the y positions
for (int i = 0; i < dheight; i++) {
// Calculate the source position in the source data array.
if (isBinary) {
ypos[i] = srcYInt;
} else {
ypos[i] = (srcYInt - srcRectY) * srcScanlineStride;
}
// If roi is present, the y position roi value is calculated
if (useRoiAccessor) {
if (isBinary) {
yposRoi[i] = srcYInt;
} else {
yposRoi[i] = (srcYInt - srcRectY) * roiScanlineStride;
}
}
// Calculate the yfrac value
if (isBilinearNew || isBicubicNew) {
yfracvalues[i] = (int) ((1.0f * srcYFrac / commonYDenom) * one);
}
// yfracvalues[i] = (int) ((1.0f *srcYFrac / commonYDenom) * one);
// Move onto the next source pixel.
// Add the integral part of invScaleY to the integral part
// of srcY
srcYInt += invScaleYInt;
// Add the fractional part of invScaleY to the fractional part
// of srcY
srcYFrac += newInvScaleYFrac;
// If the fractional part is now greater than equal to the
// denominator, divide so as to reduce the numerator to be less
// than the denominator and add the overflow to the integral part.
if (srcYFrac >= commonYDenom) {
srcYInt += 1;
srcYFrac -= commonYDenom;
}
}
}
protected final void preComputePositionsFloat(Rectangle destRect, int srcRectX, int srcRectY,
int srcPixelStride, int srcScanlineStride, int xpos[], int ypos[], float[] xfracvalues,
float[] yfracvalues, int roiScanlineStride, int[] yposRoi) {
// Destination Rectangle position
final int dwidth = destRect.width;
final int dheight = destRect.height;
// Loop variables based on the destination rectangle to be calculated.
final int dx = destRect.x;
final int dy = destRect.y;
// Initially the y source value is calculated by the destination value and then performing the inverse
// scale operation on it.
long syNum = dy, syDenom = 1;
// Subtract the X translation factor sy -= transY
syNum = syNum * transYRationalDenom - transYRationalNum * syDenom;
syDenom *= transYRationalDenom;
// Add 0.5
syNum = 2 * syNum + syDenom;
syDenom *= 2;
// Multply by invScaleX
syNum *= invScaleYRationalNum;
syDenom *= invScaleYRationalDenom;
if (isBilinearNew || isBicubicNew) {
// Subtract 0.5
syNum = 2 * syNum - syDenom;
syDenom *= 2;
}
// Separate the y source coordinate into integer and fractional part
int srcYInt = Rational.floor(syNum, syDenom);
long srcYFrac = syNum % syDenom;
if (srcYInt < 0) {
srcYFrac = syDenom + srcYFrac;
}
// Normalize - Get a common denominator for the fracs of
// src and invScaleY
final long commonYDenom = syDenom * invScaleYRationalDenom;
srcYFrac *= invScaleYRationalDenom;
final long newInvScaleYFrac = invScaleYFrac * syDenom;
// Initially the x source value is calculated by the destination value and then performing the inverse
// scale operation on it.
long sxNum = dx, sxDenom = 1;
// Subtract the X translation factor sx -= transX
sxNum = sxNum * transXRationalDenom - transXRationalNum * sxDenom;
sxDenom *= transXRationalDenom;
// Add 0.5
sxNum = 2 * sxNum + sxDenom;
sxDenom *= 2;
// Multply by invScaleX
sxNum *= invScaleXRationalNum;
sxDenom *= invScaleXRationalDenom;
if (isBilinearNew || isBicubicNew) {
// Subtract 0.5
sxNum = 2 * sxNum - sxDenom;
sxDenom *= 2;
}
// Separate the x source coordinate into integer and fractional part
int srcXInt = Rational.floor(sxNum, sxDenom);
long srcXFrac = sxNum % sxDenom;
if (srcXInt < 0) {
srcXFrac = sxDenom + srcXFrac;
}
// Normalize - Get a common denominator for the fracs of
// src and invScaleX
final long commonXDenom = sxDenom * invScaleXRationalDenom;
srcXFrac *= invScaleXRationalDenom;
final long newInvScaleXFrac = invScaleXFrac * sxDenom;
// Store of the x positions
for (int i = 0; i < dwidth; i++) {
xpos[i] = (srcXInt - srcRectX) * srcPixelStride;
// Calculate the yfrac value
if (isBilinearNew || isBicubicNew) {
xfracvalues[i] = (1.0f * srcXFrac) / commonXDenom;
}
// xfracvalues[i] = (1.0f * srcXFrac) / commonXDenom;
// Move onto the next source pixel.
// Add the integral part of invScaleX to the integral part
// of srcX
srcXInt += invScaleXInt;
// Add the fractional part of invScaleX to the fractional part
// of srcX
srcXFrac += newInvScaleXFrac;
// If the fractional part is now greater than equal to the
// denominator, divide so as to reduce the numerator to be less
// than the denominator and add the overflow to the integral part.
if (srcXFrac >= commonXDenom) {
srcXInt += 1;
srcXFrac -= commonXDenom;
}
}
// Store of the y positions
for (int i = 0; i < dheight; i++) {
// Calculate the source position in the source data array.
ypos[i] = (srcYInt - srcRectY) * srcScanlineStride;
// If roi is present, the y position roi value is calculated
if (useRoiAccessor) {
yposRoi[i] = (srcYInt - srcRectY) * roiScanlineStride;
}
// Calculate the yfrac value
if (isBilinearNew || isBicubicNew) {
yfracvalues[i] = 1.0f * srcYFrac / commonYDenom;
}
// yfracvalues[i] = (float) srcYFrac / (float) commonYDenom;
// Move onto the next source pixel.
// Add the integral part of invScaleY to the integral part
// of srcY
srcYInt += invScaleYInt;
// Add the fractional part of invScaleY to the fractional part
// of srcY
srcYFrac += newInvScaleYFrac;
// If the fractional part is now greater than equal to the
// denominator, divide so as to reduce the numerator to be less
// than the denominator and add the overflow to the integral part.
if (srcYFrac >= commonYDenom) {
srcYInt += 1;
srcYFrac -= commonYDenom;
}
}
}
private static Map<Object, Object> configHelper(RenderedImage source,
Map<Object, Object> configuration, Interpolation interp) {
Map<Object, Object> config = configuration;
// If source image is binary and the interpolation is either nearest
// or bilinear, do not expand
if (ImageUtil.isBinary(source.getSampleModel())
&& (interp == null || interp instanceof InterpolationNearest || interp instanceof InterpolationBilinear)) {
// Set to false
if (configuration == null) {
config = new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
} else {
// If the user specified a value for this hint, we don't
// want to change that
if (!config.containsKey(JAI.KEY_REPLACE_INDEX_COLOR_MODEL)) {
RenderingHints hints = new RenderingHints(null);
// This is effectively a clone of configuration
hints.putAll(configuration);
config = hints;
config.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.TRUE);
}
}
}
return config;
}
/**
* Constructs a <code>ScaleOpImage</code> from a <code>RenderedImage</code> source, an optional <code>BorderExtender</code>, x and y scale and
* translation factors, and an <code>Interpolation</code> object. The image dimensions are determined by forward-mapping the source bounds, and
* are passed to the superclass constructor by means of the <code>layout</code> parameter. Other fields of the layout are passed through
* unchanged. If <code>layout</code> is <code>null</code>, a new <code>ImageLayout</code> will be constructor to hold the bounds information.
*
* Note that the scale factors are represented internally as Rational numbers in order to workaround inexact device specific representation of
* floating point numbers. For instance the floating point number 1.2 is internally represented as 1.200001, which can throw the calculations off
* during a forward/backward map.
*
* <p>
* The Rational approximation is valid upto the sixth decimal place.
*
* @param layout an <code>ImageLayout</code> optionally containing the tile grid layout, <code>SampleModel</code>, and <code>ColorModel</code>, or
* <code>null</code>.
* @param source a <code>RenderedImage</code>.
* @param configuration Configurable attributes of the image including configuration variables indexed by <code>RenderingHints.Key</code>s and
* image properties indexed by <code>String</code>s or <code>CaselessStringKey</code>s. This is simply forwarded to the superclass
* constructor.
* @param cobbleSources a boolean indicating whether <code>computeRect</code> expects contiguous sources.
* @param extender a <code>BorderExtender</code>, or <code>null</code>.
* @param interp an <code>Interpolation</code> object to use for resampling.
* @param scaleX scale factor along x axis.
* @param scaleY scale factor along y axis.
* @param transX translation factor along x axis.
* @param transY translation factor along y axis.
*
* @throws IllegalArgumentException if <code>source</code> is <code>null</code>.
* @throws IllegalArgumentException if combining the source bounds with the layout parameter results in negative output width or height.
*
* @since JAI 1.1
*/
public ScaleOpImage(RenderedImage source, ImageLayout layout, Map configuration,
boolean cobbleSources, BorderExtender extender, Interpolation interp, float scaleX,
float scaleY, float transX, float transY, boolean useRoiAccessor, double[] backgroundValues) {
super(vectorize(source), // vectorize() checks for null source.
layoutHelper(source, scaleX, scaleY, transX, transY, interp, layout), configHelper(
source, configuration, interp), cobbleSources, extender, interp, backgroundValues);
this.scaleX = scaleX;
this.scaleY = scaleY;
this.transX = transX;
this.transY = transY;
// Represent the scale factors as Rational numbers.
// Since a value of 1.2 is represented as 1.200001 which
// throws the forward/backward mapping in certain situations.
// Convert the scale and translation factors to Rational numbers
this.scaleXRational = Rational.approximate(scaleX, rationalTolerance);
this.scaleYRational = Rational.approximate(scaleY, rationalTolerance);
this.scaleXRationalNum = (long) this.scaleXRational.num;
this.scaleXRationalDenom = (long) this.scaleXRational.denom;
this.scaleYRationalNum = (long) this.scaleYRational.num;
this.scaleYRationalDenom = (long) this.scaleYRational.denom;
this.transXRational = Rational.approximate(transX, rationalTolerance);
this.transYRational = Rational.approximate(transY, rationalTolerance);
this.transXRationalNum = (long) this.transXRational.num;
this.transXRationalDenom = (long) this.transXRational.denom;
this.transYRationalNum = (long) this.transYRational.num;
this.transYRationalDenom = (long) this.transYRational.denom;
// Inverse scale factors as Rationals
invScaleXRational = new Rational(scaleXRational);
invScaleXRational.invert();
invScaleYRational = new Rational(scaleYRational);
invScaleYRational.invert();
invScaleXRationalNum = invScaleXRational.num;
invScaleXRationalDenom = invScaleXRational.denom;
invScaleYRationalNum = invScaleYRational.num;
invScaleYRationalDenom = invScaleYRational.denom;
lpad = interp.getLeftPadding();
rpad = interp.getRightPadding();
tpad = interp.getTopPadding();
bpad = interp.getBottomPadding();
if (extender == null) {
// Get the source dimensions
int x0 = source.getMinX();
int y0 = source.getMinY();
int w = source.getWidth();
int h = source.getHeight();
// The first source pixel (x0, y0)
long dx0Num, dx0Denom, dy0Num, dy0Denom;
// The first pixel (x1, y1) that is just outside the source
long dx1Num, dx1Denom, dy1Num, dy1Denom;
if (interp instanceof InterpolationNearest) {
// First point inside the source
dx0Num = x0;
dx0Denom = 1;
dy0Num = y0;
dy0Denom = 1;
// First point outside source
// for nearest, x1 = x0 + w, y1 = y0 + h
// since anything >= a but < (a+1) maps to a for nearest
// Equivalent to float d_x1 = x0 + w
dx1Num = x0 + w;
dx1Denom = 1;
// Equivalent to float d_y1 = y0 + h
dy1Num = y0 + h;
dy1Denom = 1;
} else {
// First point inside the source
dx0Num = 2 * x0 + 1; // x0 + 0.5
dx0Denom = 2;
dy0Num = 2 * y0 + 1; // y0 + 0.5
dy0Denom = 2;
// for other interpolations, x1 = x0+w+0.5, y1 = y0+h+0.5
// as derived in the formulae derivation above.
dx1Num = 2 * x0 + 2 * w + 1;
dx1Denom = 2;
dy1Num = 2 * y0 + 2 * h + 1;
dy1Denom = 2;
// Equivalent to x0 += lpad;
dx0Num += dx0Denom * lpad;
// Equivalent to y0 += tpad;
dy0Num += dy0Denom * tpad;
// Equivalent to x1 -= rpad;
dx1Num -= dx1Denom * rpad;
// Equivalent to y1 += bpad;
dy1Num -= dy1Denom * bpad;
}
// Forward map the first and last source points
// Equivalent to float d_x0 = x0 * scaleX;
dx0Num *= scaleXRationalNum;
dx0Denom *= scaleXRationalDenom;
// Add the X translation factor d_x0 += transX
dx0Num = dx0Num * transXRationalDenom + transXRationalNum * dx0Denom;
dx0Denom *= transXRationalDenom;
// Equivalent to float d_y0 = y0 * scaleY;
dy0Num *= scaleYRationalNum;
dy0Denom *= scaleYRationalDenom;
// Add the Y translation factor, float d_y0 += transY
dy0Num = dy0Num * transYRationalDenom + transYRationalNum * dy0Denom;
dy0Denom *= transYRationalDenom;
// Equivalent to float d_x1 = x1 * scaleX;
dx1Num *= scaleXRationalNum;
dx1Denom *= scaleXRationalDenom;
// Add the X translation factor d_x1 += transX
dx1Num = dx1Num * transXRationalDenom + transXRationalNum * dx1Denom;
dx1Denom *= transXRationalDenom;
// Equivalent to float d_y1 = y1 * scaleY;
dy1Num *= scaleYRationalNum;
dy1Denom *= scaleYRationalDenom;
// Add the Y translation factor, float d_y1 += transY
dy1Num = dy1Num * transYRationalDenom + transYRationalNum * dy1Denom;
dy1Denom *= transYRationalDenom;
// Get the integral coordinates
int l_x0, l_y0, l_x1, l_y1;
// Subtract 0.5 from dx0, dy0
dx0Num = 2 * dx0Num - dx0Denom;
dx0Denom *= 2;
dy0Num = 2 * dy0Num - dy0Denom;
dy0Denom *= 2;
l_x0 = Rational.ceil(dx0Num, dx0Denom);
l_y0 = Rational.ceil(dy0Num, dy0Denom);
// Subtract 0.5 from dx1, dy1
dx1Num = 2 * dx1Num - dx1Denom;
dx1Denom *= 2;
dy1Num = 2 * dy1Num - dy1Denom;
dy1Denom *= 2;
l_x1 = (int) Rational.floor(dx1Num, dx1Denom);
// l_x1 must be less than but not equal to (dx1Num/dx1Denom)
if ((l_x1 * dx1Denom) == dx1Num) {
l_x1 -= 1;
}
l_y1 = (int) Rational.floor(dy1Num, dy1Denom);
if (l_y1 * dy1Denom == dy1Num) {
l_y1 -= 1;
}
computableBounds = new Rectangle(l_x0, l_y0, (l_x1 - l_x0 + 1), (l_y1 - l_y0 + 1));
} else {
// If extender is present we can write the entire destination.
computableBounds = getBounds();
// Padding of the input image in order to avoid the call of the getExtendedData() method.
// Extend the Source image
ParameterBlock pb = new ParameterBlock();
pb.setSource(source, 0);
int leftPadding = lpad;
int topPadding = tpad;
if (interp instanceof InterpolationBilinear || interp instanceof InterpolationBicubic) {
// add an extrapixel for left and top padding since the border extender will fill them
// with zero otherwise
leftPadding++;
topPadding++;
}
pb.set(leftPadding, 0);
pb.set(rpad, 1);
pb.set(topPadding, 2);
pb.set(bpad, 3);
pb.set(extender, 4);
extendedIMG = JAI.create("border", pb);
}
// SG Retrieve the rendered source image and its ROI.
Object property = source.getProperty("ROI");
if (property instanceof ROI) {
srcROI = (ROI) property;
srcROIImage = srcROI.getAsImage();
// FIXME can we use smaller bounds here?
roiRect = new Rectangle(srcROIImage.getMinX() - lpad,
srcROIImage.getMinY() - tpad, srcROIImage.getWidth() + lpad + rpad,
srcROIImage.getHeight() + tpad + bpad);
// Padding of the input ROI image in order to avoid the call of the getExtendedData() method
// Calculate the padding between the ROI and the source image padded
roiBounds = srcROIImage.getBounds();
Rectangle srcRect = new Rectangle(source.getMinX() - lpad , source.getMinY() - tpad,
source.getWidth() + lpad + rpad, source.getHeight() + tpad + bpad);
int deltaX0 = (roiBounds.x - srcRect.x);
int leftP = deltaX0 > 0 ? deltaX0 : 0;
int deltaY0 = (roiBounds.y - srcRect.y);
int topP = deltaY0 > 0 ? deltaY0 : 0;
int deltaX1 = (srcRect.x + srcRect.width - roiBounds.x - roiBounds.width);
int rightP = deltaX1 > 0 ? deltaX1 : 0;
int deltaY1 = (srcRect.y + srcRect.height - roiBounds.y - roiBounds.height);
int bottomP = deltaY1 > 0 ? deltaY1 : 0;
// Extend the ROI image
ParameterBlock pb = new ParameterBlock();
pb.setSource(srcROIImage, 0);
pb.set(leftP, 0);
pb.set(rightP, 1);
pb.set(topP, 2);
pb.set(bottomP, 3);
pb.set(roiExtender, 4);
srcROIImgExt = JAI.create("border", pb);
hasROI = true;
this.useRoiAccessor = useRoiAccessor;
} else {
srcROI = null;
srcROIImage = null;
roiBounds = null;
hasROI = false;
}
}
/**
* Computes the position in the specified source that best matches the supplied destination image position.
*
* <p>
* The implementation in this class returns the value of <code>pt</code> in the following code snippet:
*
* <pre>
* Point2D pt = (Point2D) destPt.clone();
* pt.setLocation((destPt.getX() - transX + 0.5) / scaleX - 0.5, (destPt.getY() - transY + 0.5)
* / scaleY - 0.5);
* </pre>
*
* Subclasses requiring different behavior should override this method.
* </p>
*
* @param destPt the position in destination image coordinates to map to source image coordinates.
* @param sourceIndex the index of the source image.
*
* @return a <code>Point2D</code> of the same class as <code>destPt</code>.
*
* @throws IllegalArgumentException if <code>destPt</code> is <code>null</code>.
* @throws IndexOutOfBoundsException if <code>sourceIndex</code> is non-zero.
*
* @since JAI 1.1.2
*/
public Point2D mapDestPoint(Point2D destPt, int sourceIndex) {
if (destPt == null) {
throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
} else if (sourceIndex != 0) {
throw new IndexOutOfBoundsException(JaiI18N.getString("Generic1"));
}
Point2D pt = (Point2D) destPt.clone();
pt.setLocation((destPt.getX() - transX + 0.5) / scaleX - 0.5,
(destPt.getY() - transY + 0.5) / scaleY - 0.5);
return pt;
}
/**
* Computes the position in the destination that best matches the supplied source image position.
*
* <p>
* The implementation in this class returns the value of <code>pt</code> in the following code snippet:
*
* <pre>
* Point2D pt = (Point2D) sourcePt.clone();
* pt.setLocation(scaleX * (sourcePt.getX() + 0.5) + transX - 0.5, scaleY * (sourcePt.getY() + 0.5)
* + transY - 0.5);
* </pre>
*
* Subclasses requiring different behavior should override this method.
* </p>
*
* @param sourcePt the position in source image coordinates to map to destination image coordinates.
* @param sourceIndex the index of the source image.
*
* @return a <code>Point2D</code> of the same class as <code>sourcePt</code>.
*
* @throws IllegalArgumentException if <code>sourcePt</code> is <code>null</code>.
* @throws IndexOutOfBoundsException if <code>sourceIndex</code> is non-zero.
*
* @since JAI 1.1.2
*/
public Point2D mapSourcePoint(Point2D sourcePt, int sourceIndex) {
if (sourcePt == null) {
throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
} else if (sourceIndex != 0) {
throw new IndexOutOfBoundsException(JaiI18N.getString("Generic1"));
}
Point2D pt = (Point2D) sourcePt.clone();
pt.setLocation(scaleX * (sourcePt.getX() + 0.5) + transX - 0.5, scaleY
* (sourcePt.getY() + 0.5) + transY - 0.5);
return pt;
}
/**
* 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.
*
* @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>.
*
* @since JAI 1.1
*/
protected Rectangle forwardMapRect(Rectangle sourceRect, int sourceIndex) {
if (sourceRect == null) {
throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
}
if (sourceIndex != 0) {
throw new IllegalArgumentException(JaiI18N.getString("Generic1"));
}
// Get the source dimensions
int x0 = sourceRect.x;
int y0 = sourceRect.y;
int w = sourceRect.width;
int h = sourceRect.height;
// Variables to represent the first pixel inside the destination.
long dx0Num, dx0Denom, dy0Num, dy0Denom;
// Variables to represent the last destination pixel.
long dx1Num, dx1Denom, dy1Num, dy1Denom;
if (interp instanceof InterpolationNearest) {
// First point inside the source
dx0Num = x0;
dx0Denom = 1;
dy0Num = y0;
dy0Denom = 1;
// First point outside source
// for nearest, x1 = x0 + w, y1 = y0 + h
// since anything >= a and < a+1 maps to a for nearest, since
// we use floor to calculate the integral source position
// Equivalent to float d_x1 = x0 + w
dx1Num = x0 + w;
dx1Denom = 1;
// Equivalent to float d_y1 = y0 + h
dy1Num = y0 + h;
dy1Denom = 1;
} else {
// First point inside the source (x0 + 0.5, y0 + 0.5)
dx0Num = 2 * x0 + 1;
dx0Denom = 2;
dy0Num = 2 * y0 + 1;
dy0Denom = 2;
// for other interpolations, x1 = x0 + w + 0.5, y1 = y0 + h + 0.5
// as derived in the formulae derivation above.
dx1Num = 2 * x0 + 2 * w + 1;
dx1Denom = 2;
dy1Num = 2 * y0 + 2 * h + 1;
dy1Denom = 2;
}
// Forward map first and last source positions
// Equivalent to float d_x0 = x0 * scaleX;
dx0Num = dx0Num * scaleXRationalNum;
dx0Denom *= scaleXRationalDenom;
// Equivalent to float d_y0 = y0 * scaleY;
dy0Num = dy0Num * scaleYRationalNum;
dy0Denom *= scaleYRationalDenom;
// Equivalent to float d_x1 = x1 * scaleX;
dx1Num = dx1Num * scaleXRationalNum;
dx1Denom *= scaleXRationalDenom;
// Equivalent to float d_y1 = y1 * scaleY;
dy1Num = dy1Num * scaleYRationalNum;
dy1Denom *= scaleYRationalDenom;
// Add the translation factors.
// Equivalent to float d_x0 += transX
dx0Num = dx0Num * transXRationalDenom + transXRationalNum * dx0Denom;
dx0Denom *= transXRationalDenom;
// Equivalent to float d_y0 += transY
dy0Num = dy0Num * transYRationalDenom + transYRationalNum * dy0Denom;
dy0Denom *= transYRationalDenom;
// Equivalent to float d_x1 += transX
dx1Num = dx1Num * transXRationalDenom + transXRationalNum * dx1Denom;
dx1Denom *= transXRationalDenom;
// Equivalent to float d_y1 += transY
dy1Num = dy1Num * transYRationalDenom + transYRationalNum * dy1Denom;
dy1Denom *= transYRationalDenom;
// Get the integral coordinates
int l_x0, l_y0, l_x1, l_y1;
// Subtract 0.5 from dx0, dy0
dx0Num = 2 * dx0Num - dx0Denom;
dx0Denom *= 2;
dy0Num = 2 * dy0Num - dy0Denom;
dy0Denom *= 2;
l_x0 = Rational.ceil(dx0Num, dx0Denom);
l_y0 = Rational.ceil(dy0Num, dy0Denom);
// Subtract 0.5 from dx1, dy1
dx1Num = 2 * dx1Num - dx1Denom;
dx1Denom *= 2;
dy1Num = 2 * dy1Num - dy1Denom;
dy1Denom *= 2;
l_x1 = (int) Rational.floor(dx1Num, dx1Denom);
if ((l_x1 * dx1Denom) == dx1Num) {
l_x1 -= 1;
}
l_y1 = (int) Rational.floor(dy1Num, dy1Denom);
if ((l_y1 * dy1Denom) == dy1Num) {
l_y1 -= 1;
}
return new Rectangle(l_x0, l_y0, (l_x1 - l_x0 + 1), (l_y1 - l_y0 + 1));
}
/**
* 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.
*
* @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>.
*
* @since JAI 1.1
*/
protected Rectangle backwardMapRect(Rectangle destRect, int sourceIndex) {
if (destRect == null) {
throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
}
if (sourceIndex != 0) {
throw new IllegalArgumentException(JaiI18N.getString("Generic1"));
}
// Get the destination rectangle coordinates and dimensions
int x0 = destRect.x;
int y0 = destRect.y;
int w = destRect.width;
int h = destRect.height;
// Variables that will eventually hold the source pixel
// positions which are the result of the backward map
long sx0Num, sx0Denom, sy0Num, sy0Denom;
// First destination point that will be backward mapped
// will be dx0 + 0.5, dy0 + 0.5
sx0Num = (x0 * 2 + 1);
sx0Denom = 2;
sy0Num = (y0 * 2 + 1);
sy0Denom = 2;
// The last destination pixel to be backward mapped will be
// dx0 + w - 1 + 0.5, dy0 + h - 1 + 0.5 i.e.
// dx0 + w - 0.5, dy0 + h - 0.5
long sx1Num, sx1Denom, sy1Num, sy1Denom;
// Equivalent to float sx1 = dx0 + dw - 0.5;
sx1Num = 2 * x0 + 2 * w - 1;
sx1Denom = 2;
// Equivalent to float sy1 = dy0 + dh - 0.5;
sy1Num = 2 * y0 + 2 * h - 1;
sy1Denom = 2;
// Subtract the translation factors.
sx0Num = sx0Num * transXRationalDenom - transXRationalNum * sx0Denom;
sx0Denom *= transXRationalDenom;
sy0Num = sy0Num * transYRationalDenom - transYRationalNum * sy0Denom;
sy0Denom *= transYRationalDenom;
sx1Num = sx1Num * transXRationalDenom - transXRationalNum * sx1Denom;
sx1Denom *= transXRationalDenom;
sy1Num = sy1Num * transYRationalDenom - transYRationalNum * sy1Denom;
sy1Denom *= transYRationalDenom;
// Backward map both the destination positions
// Equivalent to float sx0 = x0 / scaleX;
sx0Num *= invScaleXRationalNum;
sx0Denom *= invScaleXRationalDenom;
sy0Num *= invScaleYRationalNum;
sy0Denom *= invScaleYRationalDenom;
sx1Num *= invScaleXRationalNum;
sx1Denom *= invScaleXRationalDenom;
sy1Num *= invScaleYRationalNum;
sy1Denom *= invScaleYRationalDenom;
int s_x0 = 0, s_y0 = 0, s_x1 = 0, s_y1 = 0;
if (interp instanceof InterpolationNearest) {
// Floor sx0, sy0
s_x0 = Rational.floor(sx0Num, sx0Denom);
s_y0 = Rational.floor(sy0Num, sy0Denom);
// Equivalent to (int)Math.floor(sx1)
s_x1 = Rational.floor(sx1Num, sx1Denom);
// Equivalent to (int)Math.floor(sy1)
s_y1 = Rational.floor(sy1Num, sy1Denom);
} else {
// For all other interpolations
// Equivalent to (int) Math.floor(sx0 - 0.5)
s_x0 = Rational.floor(2 * sx0Num - sx0Denom, 2 * sx0Denom);
// Equivalent to (int) Math.floor(sy0 - 0.5)
s_y0 = Rational.floor(2 * sy0Num - sy0Denom, 2 * sy0Denom);
// Calculate the last source point
s_x1 = Rational.floor(2 * sx1Num - sx1Denom, 2 * sx1Denom);
// Equivalent to (int)Math.ceil(sy1 - 0.5)
s_y1 = Rational.floor(2 * sy1Num - sy1Denom, 2 * sy1Denom);
}
return new Rectangle(s_x0, s_y0, (s_x1 - s_x0 + 1), (s_y1 - s_y0 + 1));
}
/**
* Computes a tile. If source cobbling was requested at construction time, the source tile boundaries are overlayed onto the destination, cobbling
* is performed for areas that intersect multiple source tiles, and <code>computeRect(Raster[], WritableRaster, Rectangle)</code> is called for
* each of the resulting regions. Otherwise, <code>computeRect(PlanarImage[], WritableRaster,
* Rectangle)</code> is called once to compute the entire active area of the tile.
*
* <p>
* The image bounds may be larger than the bounds of the source image. In this case, samples for which there are no corresponding sources are set
* to zero.
*
* <p>
* The following steps are performed in order to compute the tile:
* <ul>
* <li>The destination tile is backward mapped to compute the needed source.
* <li>This source is then split on tile boundaries to produce rectangles that do not cross tile boundaries.
* <li>These source rectangles are then forward mapped to produce destination rectangles, and the computeRect method is called for each
* corresponding pair of source and destination rectangles.
* <li>For higher order interpolations, some source cobbling across tile boundaries does occur.
* </ul>
*
* @param tileX The X index of the tile.
* @param tileY The Y index of the tile.
*
* @return The tile as a <code>Raster</code>.
*/
public Raster computeTile(int tileX, int tileY) {
if (!cobbleSources) {
return super.computeTile(tileX, tileY);
}
// X and Y coordinate of the pixel pixel of the tile.
int orgX = tileXToX(tileX);
int orgY = tileYToY(tileY);
// Create a new WritableRaster to represent this tile.
WritableRaster dest = createWritableRaster(sampleModel, new Point(orgX, orgY));
Rectangle rect = new Rectangle(orgX, orgY, tileWidth, tileHeight);
// Clip dest rectangle against the part of the destination
// rectangle that can be written.
Rectangle destRect = rect.intersection(computableBounds);
if ((destRect.width <= 0) || (destRect.height <= 0)) {
// If empty rectangle, return empty tile.
return dest;
}
// Get the source rectangle required to compute the destRect
Rectangle srcRect = mapDestRect(destRect, 0);
Raster[] sources = new Raster[1];
// Split the source on tile boundaries.
// Get the new pairs of src & dest Rectangles
// The tileWidth and tileHeight of the source image
// may differ from this tileWidth and tileHeight.
PlanarImage source0 = getSourceImage(0);
IntegerSequence srcXSplits = new IntegerSequence();
IntegerSequence srcYSplits = new IntegerSequence();
source0.getSplits(srcXSplits, srcYSplits, srcRect);
// Note that the getExtendedData() method is not called because the input images are padded.
// For each image there is a check if the rectangle is contained inside the source image;
// if this not happen, the data is taken from the padded image.
if (srcXSplits.getNumElements() == 1 && srcYSplits.getNumElements() == 1) {
// If the source is fully contained within
// a tile there is no need to split it any further.
if (extender == null) {
sources[0] = source0.getData(srcRect);
} else {
if(source0.getBounds().contains(srcRect)){
sources[0] = source0.getData(srcRect);
}else{
sources[0] = extendedIMG.getData(srcRect);
}
}
// Compute the destination tile.
computeRect(sources, dest, destRect);
} else {
// Source Rect straddles 2 or more tiles
// Get Source Tilewidth & height
int srcTileWidth = source0.getTileWidth();
int srcTileHeight = source0.getTileHeight();
srcYSplits.startEnumeration();
while (srcYSplits.hasMoreElements()) {
// Along Y TileBoundaries
int ysplit = srcYSplits.nextElement();
srcXSplits.startEnumeration();
while (srcXSplits.hasMoreElements()) {
// Along X TileBoundaries
int xsplit = srcXSplits.nextElement();
// Construct a pseudo tile for intersection purposes
Rectangle srcTile = new Rectangle(xsplit, ysplit, srcTileWidth, srcTileHeight);
// Intersect the tile with the source Rectangle
Rectangle newSrcRect = srcRect.intersection(srcTile);
//
// The new source rect could be of size less than or equal
// to the interpolation kernel dimensions. In which case
// the forward map produces null destination rectangles.
// Hence we need to deal with these cases in the manner
// implemented below (grow the source before the map. This
// would result in source cobbling). Not an issue for
// Nearest-Neighbour.
//
if (!(interp instanceof InterpolationNearest)) {
if (newSrcRect.width <= interp.getWidth()) {
//
// Need to forward map this source rectangle.
// Since we need a minimum of 2 * (lpad + rpad + 1)
// in order to process, we contsruct a source
// rectangle of that size, forward map and then
// process the resulting destination rectangle.
//
Rectangle wSrcRect = new Rectangle();
Rectangle wDestRect;
wSrcRect.x = newSrcRect.x;
wSrcRect.y = newSrcRect.y - tpad - 1;
wSrcRect.width = 2 * (lpad + rpad + 1);
wSrcRect.height = newSrcRect.height + bpad + tpad + 2;
wSrcRect = wSrcRect.intersection(source0.getBounds());
wDestRect = mapSourceRect(wSrcRect, 0);
//
// Make sure this destination rectangle is
// within the bounds of our original writable
// destination rectangle
//
wDestRect = wDestRect.intersection(destRect);
if ((wDestRect.width > 0) && (wDestRect.height > 0)) {
// Do the operations with these new rectangles
if (extender == null) {
sources[0] = source0.getData(wSrcRect);
} else {
if(source0.getBounds().contains(srcRect)){
sources[0] = source0.getData(srcRect);
} else {
sources[0] = extendedIMG.getData(srcRect);
}
}
// Compute the destination tile.
computeRect(sources, dest, wDestRect);
}
}
if (newSrcRect.height <= interp.getHeight()) {
//
// Need to forward map this source rectangle.
// Since we need a minimum of 2 * (tpad + bpad + 1)
// in order to process, we create a source
// rectangle of that size, forward map and then
// process the resulting destinaltion rectangle
//
Rectangle hSrcRect = new Rectangle();
Rectangle hDestRect;
hSrcRect.x = newSrcRect.x - lpad - 1;
hSrcRect.y = newSrcRect.y;
hSrcRect.width = newSrcRect.width + lpad + rpad + 2;
hSrcRect.height = 2 * (tpad + bpad + 1);
hSrcRect = hSrcRect.intersection(source0.getBounds());
hDestRect = mapSourceRect(hSrcRect, 0);
//
// Make sure this destination rectangle is
// within the bounds of our original writable
// destination rectangle
//
hDestRect = hDestRect.intersection(destRect);
if ((hDestRect.width > 0) && (hDestRect.height > 0)) {
// Do the operations with these new rectangles
if (extender == null) {
sources[0] = source0.getData(hSrcRect);
} else {
if(source0.getBounds().contains(srcRect)){
sources[0] = source0.getData(srcRect);
}else{
sources[0] = extendedIMG.getData(srcRect);
}
}
// Compute the destination tile.
computeRect(sources, dest, hDestRect);
}
}
}
// Process source rectangle
if ((newSrcRect.width > 0) && (newSrcRect.height > 0)) {
// Forward map this source rectangle
// to get the destination rectangle
Rectangle newDestRect = mapSourceRect(newSrcRect, 0);
// Make sure this destination rectangle is
// within the bounds of our original writable
// destination rectangle
newDestRect = newDestRect.intersection(destRect);
if ((newDestRect.width > 0) && (newDestRect.height > 0)) {
// Do the operations with these new rectangles
if (extender == null) {
sources[0] = source0.getData(newSrcRect);
} else {
if(source0.getBounds().contains(srcRect)){
sources[0] = source0.getData(srcRect);
}else{
sources[0] = extendedIMG.getData(srcRect);
}
}
// Compute the destination tile.
computeRect(sources, dest, newDestRect);
}
//
// Since mapSourceRect (forward map) shrinks the
// source rectangle before the map, there are areas
// of this rectangle which never get mapped.
//
// These occur at the tile boundaries between
// rectangles. The following algorithm handles
// these edge conditions.
//
// The cases :
// Right edge
// Bottom edge
// Lower Right Corner
//
if (!(interp instanceof InterpolationNearest)) {
Rectangle RTSrcRect = new Rectangle();
Rectangle RTDestRect;
// Right Edge
RTSrcRect.x = newSrcRect.x + newSrcRect.width - 1 - rpad - lpad;
RTSrcRect.y = newSrcRect.y;
//
// The amount of src not used from the end of
// the first tile is rpad + 0.5. The amount
// not used from the beginning of the next tile
// is lpad + 0.5. Since we cannot start mapping
// at 0.5, we need to get the area of the half
// pixel on both sides. So we get another 0,5
// from both sides. In total (rpad + 0.5 +
// 0.5) + (lpad + 0.5 + 0.5)
// Since mapSourceRect subtracts rpad + 0.5 and
// lpad + 0.5 from the source before the
// forward map, we need to add that in.
//
RTSrcRect.width = 2 * (lpad + rpad + 1);
RTSrcRect.height = newSrcRect.height;
RTDestRect = mapSourceRect(RTSrcRect, 0);
// Clip this against the whole destrect
RTDestRect = RTDestRect.intersection(destRect);
// RTSrcRect may be out of image bounds;
// map one more time
RTSrcRect = mapDestRect(RTDestRect, 0);
if (RTDestRect.width > 0 && RTDestRect.height > 0) {
// Do the operations with these new rectangles
if (extender == null) {
sources[0] = source0.getData(RTSrcRect);
} else {
if(source0.getBounds().contains(srcRect)){
sources[0] = source0.getData(srcRect);
}else{
sources[0] = extendedIMG.getData(srcRect);
}
}
// Compute the destination tile.
computeRect(sources, dest, RTDestRect);
}
// Bottom Edge
Rectangle BTSrcRect = new Rectangle();
Rectangle BTDestRect;
BTSrcRect.x = newSrcRect.x;
BTSrcRect.y = newSrcRect.y + newSrcRect.height - 1 - bpad - tpad;
//
// The amount of src not used from the end of
// the first tile is tpad + 0.5. The amount
// not used from the beginning of the next tile
// is bpad + 0.5. Since we cannot start mapping
// at 0.5, we need to get the area of the half
// pixel on both sides. So we get another 0,5
// from both sides. In total (tpad + 0.5 +
// 0.5) + (bpad + 0.5 + 0.5)
// Since mapSourceRect subtracts tpad + 0.5 and
// bpad + 0.5 from the source before the
// forward map, we need to add that in.
//
BTSrcRect.width = newSrcRect.width;
BTSrcRect.height = 2 * (tpad + bpad + 1);
BTDestRect = mapSourceRect(BTSrcRect, 0);
// Clip this against the whole destrect
BTDestRect = BTDestRect.intersection(destRect);
// BTSrcRect maybe out of bounds
// map one more time
BTSrcRect = mapDestRect(BTDestRect, 0);
// end
if (BTDestRect.width > 0 && BTDestRect.height > 0) {
// Do the operations with these new rectangles
if (extender == null) {
sources[0] = source0.getData(BTSrcRect);
} else {
if(source0.getBounds().contains(srcRect)){
sources[0] = source0.getData(srcRect);
}else{
sources[0] = extendedIMG.getData(srcRect);
}
}
// Compute the destination tile.
computeRect(sources, dest, BTDestRect);
}
// Lower Right Area
Rectangle LRTSrcRect = new Rectangle();
Rectangle LRTDestRect;
LRTSrcRect.x = newSrcRect.x + newSrcRect.width - 1 - rpad - lpad;
LRTSrcRect.y = newSrcRect.y + newSrcRect.height - 1 - bpad - tpad;
// Comment forthcoming
LRTSrcRect.width = 2 * (rpad + lpad + 1);
LRTSrcRect.height = 2 * (tpad + bpad + 1);
LRTDestRect = mapSourceRect(LRTSrcRect, 0);
// Clip this against the whole destrect
LRTDestRect = LRTDestRect.intersection(destRect);
// LRTSrcRect may still be out of bounds
LRTSrcRect = mapDestRect(LRTDestRect, 0);
if (LRTDestRect.width > 0 && LRTDestRect.height > 0) {
// Do the operations with these new rectangles
if (extender == null) {
sources[0] = source0.getData(LRTSrcRect);
} else {
if(source0.getBounds().contains(srcRect)){
sources[0] = source0.getData(srcRect);
}else{
sources[0] = extendedIMG.getData(srcRect);
}
}
// Compute the destination tile.
computeRect(sources, dest, LRTDestRect);
}
}
}
}
}
}
// Return the written destination raster
return dest;
}
@Override
public synchronized void dispose() {
if (srcROIImage != null) {
srcROIImage.dispose();
}
if(srcROIImgExt != null) {
srcROIImgExt.dispose();
}
super.dispose();
}
}