in endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ResampledImage.java [686:921]
protected Raster computeTile(final int tileX, final int tileY, WritableRaster tile) throws TransformException {
if (tile == null) {
tile = createTile(tileX, tileY);
}
final int numBands = tile.getNumBands();
final int scanline = tile.getWidth();
final int tileMinX = tile.getMinX();
final int tileMinY = tile.getMinY();
final int tileMaxX = Math.addExact(tileMinX, scanline);
final int tileMaxY = Math.addExact(tileMinY, tile.getHeight());
final int tgtDim = toSourceSupport.getTargetDimensions();
final double[] coordinates = new double[scanline * Math.max(BIDIMENSIONAL, tgtDim)];
/*
* Compute the bounds of pixel coordinates that we can use for setting iterator positions in the source image.
* The iterator bounds are slightly smaller than the image bounds because it needs to keep a margin for giving
* enough pixels to interpolators (for example bilinear interpolations require 2×2 pixels).
*
* The (xmin, ymin) and (xmax, ymax) coordinates are integers and inclusive. Because integer pixel coordinates
* are located at pixel centers, the image area is actually wider by 0.5 pixel (or 1.5, 2.5, …) on image sides.
* This expansion is taken in account in (xlim, ylim), which are the limit values than we can interpolate.
*/
final double xmin, ymin, xmax, ymax, xlim, ylim, xoff, yoff;
final PixelIterator it;
{ // For keeping temporary variables locale.
final Dimension support = interpolation.getSupportSize();
it = new PixelIterator.Builder().setWindowSize(support).create(getSource());
final Rectangle domain = it.getDomain(); // Source image bounds.
xmin = domain.getMinX(); // We will tolerate 0.5 pixels before (from center to border).
ymin = domain.getMinY();
xmax = domain.getMaxX() - 1; // Iterator limit (inclusive) because of interpolation support.
ymax = domain.getMaxY() - 1;
xlim = interpolationLimit(xmax, support.width); // Upper limit of coordinates where we can interpolate.
ylim = interpolationLimit(ymax, support.height);
xoff = interpolationSupportOffset(support.width) - 0.5; // Always negative (or 0 for nearest-neighbor).
yoff = interpolationSupportOffset(support.height) - 0.5;
}
/*
* In the special case of nearest-neighbor interpolation with no precision lost, the code inside the loop
* can take a shorter path were data are just copied. The lossless criterion allows us to omit the checks
* for minimal and maximal values. Shortcut may apply to both integer values and floating point values.
*/
final boolean useFillValues = (getDestination() == null);
final boolean shortcut = useFillValues && Interpolation.NEAREST.equals(interpolation) &&
ImageUtilities.isLosslessConversion(sampleModel, tile.getSampleModel());
/*
* Prepare a buffer where to store a line of interpolated values. We use this buffer for transferring
* many pixels in a single `WritableRaster.setPixels(…)` call, which is faster than invoking `setPixel(…)`
* for each pixel. We use integer values if possible because `WritableRaster.setPixels(…)` implementations
* have optimizations for this case. If data are not integers, then we fallback on non-optimized `double[]`.
*/
double[] transfer = null;
int[] intTransfer = null;
final double[] values;
final int[] intValues;
final Object valuesArray;
final long[] minValues, maxValues;
final boolean isInteger = (fillValues instanceof int[]);
if (isInteger) {
valuesArray = intValues = new int[scanline * numBands];
if (shortcut) {
values = null; // No floating point values to transfer.
minValues = null; // Min/max checks are not needed in shortcut case.
maxValues = null;
} else {
values = new double[numBands];
minValues = new long [numBands];
maxValues = new long [numBands];
final SampleModel sm = tile.getSampleModel();
for (int b=0; b<numBands; b++) {
maxValues[b] = Numerics.bitmask(sm.getSampleSize(b)) - 1;
}
if (!DataType.isUnsigned(sm)) {
for (int b=0; b<numBands; b++) {
minValues[b] = ~(maxValues[b] >>>= 1); // Convert unsigned type to signed type range.
}
}
}
} else {
intValues = null;
values = new double[scanline * numBands];
valuesArray = values;
minValues = null; // Not used for floating point types.
maxValues = null;
}
/*
* The (sx,sy) values are iterator position, remembered for detecting if the window buffer
* needs to be updated. The `Integer.MAX_VALUE` initial value is safe because the iterator
* cannot have that position (its construction would have failed with ArithmeticException
* if the image position was so high).
*/
int sx = Integer.MAX_VALUE;
int sy = Integer.MAX_VALUE;
final PixelIterator.Window<DoubleBuffer> buffer = it.createWindow(TransferType.DOUBLE);
for (int ty = tileMinY; ty < tileMaxY; ty++) {
/*
* Transform a block of coordinates in one `transform(…)` method call.
* This is faster than doing a method call for each coordinates tuple.
*/
for (int ci=0, tx=tileMinX; tx<tileMaxX; tx++) {
coordinates[ci++] = tx;
coordinates[ci++] = ty;
}
toSourceSupport.transform(coordinates, 0, coordinates, 0, scanline);
/*
* Pixel coordinate along X axis where to start writing the `values` or `intValues` array.
* This is usually the first column of the tile, and the number of pixels to write is the
* tile width (i.e. we write a full tile row). However, those values may be modified below
* if we avoid writing pixels that are outside the source image.
*/
int posX = tileMinX;
if (shortcut) {
/*
* Special case for nearest-neighbor interpolation without the need to check for min/max values.
* In this case values will be copied as `int` or `double` type without further processing.
*/
int ci = 0; // Index in `coordinates` array.
int vi = 0; // Index in `values` or `intValues` array.
for (int tx=tileMinX; tx<tileMaxX; tx++, ci+=tgtDim, vi+=numBands) {
final long x = (long) Math.floor(coordinates[ci]);
if (x >= it.lowerX && x < it.upperX) {
final long y = (long) Math.floor(coordinates[ci+1]);
if (y >= it.lowerY && y < it.upperY) {
if (sx != (sx = (int) x) | // Really |, not ||.
sy != (sy = (int) y))
{
it.moveTo(sx, sy);
}
if (isInteger) {
intTransfer = it.getPixel(intTransfer);
System.arraycopy(intTransfer, 0, intValues, vi, numBands);
} else {
transfer = it.getPixel(transfer);
System.arraycopy(transfer, 0, values, vi, numBands);
}
continue; // Values have been set, move to next pixel.
}
}
System.arraycopy(fillValues, 0, valuesArray, vi, numBands);
}
} else {
/*
* Interpolate values for all bands in current scanline. The (x,y) values are coordinates
* in the source image and (xf,yf) are their fractional parts. Those fractional parts are
* between 0 inclusive and 1 exclusive except on the image borders: on the left and upper
* sides the fractional parts can go down to -0.5, because 0 is for pixel center and -0.5
* is at image border. On the right and bottom sides the fractional parts are constrained
* to +0.5 in nearest-neighbor interpolation case, for the same reason as other borders.
* However if the interpolation is bilinear, then the fractional parts on the bottom and
* right borders can go up to 1.5 because `PixelIterator` has reduced the (xmax, ymax)
* values by 1 (for taking in account the padding needed for interpolation support).
* This tolerance can be generalized (2.5, 3.5, etc.) depending on interpolation method.
*/
int ci = 0; // Index in `coordinates` array.
int vi = 0; // Index in `values` or `intValues` array.
for (int tx=tileMinX; tx<tileMaxX; tx++, ci+=tgtDim) {
double x = coordinates[ci];
if (x <= xlim) {
// Separate integer and fractional parts with 0 ≤ xf < 1 except on borders.
final double xf = x - (x = Math.max(xmin, Math.min(xmax, Math.floor(x))));
if (xf >= xoff) { // Negative only on left image border.
double y = coordinates[ci+1];
if (y <= ylim) {
// Separate integer and fractional parts with 0 ≤ yf < 1 except on borders.
final double yf = y - (y = Math.max(ymin, Math.min(ymax, Math.floor(y))));
if (yf >= yoff) { // Negative only on upper image border.
/*
* At this point we determined that (x,y) coordinates are inside source image domain.
* Those coordinates may have been slightly shifted for interpolation support if they
* were close to an image border. If the MathTransform produced 3 or more coordinates,
* current implementation does not yet use those coordinates. But if we want to use
* them in a future version (e.g. for interpolation in 3D cube), it would be there.
*/
if (sx != (sx = (int) x) | // Really |, not ||.
sy != (sy = (int) y))
{
it.moveTo(sx, sy);
buffer.update();
}
/*
* Interpolate the values at current position. We don't do any special processing
* for NaN values because we want to keep them if output type is floating point,
* and NaN values should not occur if data type (input and output) is integer.
*/
interpolation.interpolate(buffer.values, numBands, xf, yf, values, isInteger ? 0 : vi);
if (isInteger) {
for (int b=0; b<numBands; b++) {
intValues[vi+b] = (int) Math.max(minValues[b],
Math.min(maxValues[b], Math.round(values[b])));
}
}
vi += numBands;
continue; // Values have been set, move to next pixel.
}
}
}
}
/*
* If we reach this point then any of the "if" conditions above failed
* (i.e. the point to interpolate is outside the source image bounds)
* and no values have been set in the `values` or `intValues` array.
* If we are writing in an existing image, do not write anything
* (i.e. keep the existing value). Otherwise write the fill values.
*/
if (useFillValues) {
System.arraycopy(fillValues, 0, valuesArray, vi, numBands);
vi += numBands;
} else {
if (vi != 0) {
final int numX = vi / numBands;
if (isInteger) {
tile.setPixels(posX, ty, numX, 1, intValues);
} else {
tile.setPixels(posX, ty, numX, 1, values);
}
posX += numX;
vi = 0;
}
posX++;
}
}
}
/*
* At this point we finished to compute the value of a scanline.
* Copy to its final destination then move to next line.
*/
final int numX = scanline - (posX - tileMinX);
if (numX != 0) {
if (isInteger) {
tile.setPixels(posX, ty, numX, 1, intValues);
} else {
tile.setPixels(posX, ty, numX, 1, values);
}
}
}
return tile;
}