protected Raster computeTile()

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;
    }