This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 485df91 Add nearest-neighbor interpolation. 485df91 is described below commit 485df91de868ce06bd36eff9df23b8c0af7155f2 Author: Martin Desruisseaux AuthorDate: Fri Mar 27 15:53:43 2020 +0100 Add nearest-neighbor interpolation. --- .../java/org/apache/sis/image/Interpolation.java | 29 ++++- .../java/org/apache/sis/image/ResampledImage.java | 134 +++++++++++++-------- .../org/apache/sis/image/ResampledImageTest.java | 24 ++++ 3 files changed, 132 insertions(+), 55 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java b/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java index 2f14ee0..6cf39e8 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java +++ b/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java @@ -101,6 +101,31 @@ public interface Interpolation { boolean interpolate(DoubleBuffer source, int numBands, double xfrac, double yfrac, double[] writeTo, int writeToOffset); /** + * A nearest-neighbor interpolation using 1×1 pixel. + */ + Interpolation NEAREST = new Interpolation() { + /** Interpolation name for debugging purpose. */ + @Override public String toString() { + return "NEAREST"; + } + + /** Size of the area over which to provide values. */ + @Override public Dimension getSupportSize() { + return new Dimension(1,1); + } + + /** Applies nearest-neighbor interpolation on 1×1 window. */ + @Override public boolean interpolate(final DoubleBuffer source, final int numBands, + final double xfrac, final double yfrac, final double[] writeTo, int writeToOffset) + { + source.mark(); + source.get(writeTo, writeToOffset, numBands); + source.reset(); + return true; + } + }; + + /** * A bilinear interpolation using 2×2 pixels. * If the interpolation result is NaN, this method fallbacks on nearest-neighbor. */ @@ -110,12 +135,12 @@ public interface Interpolation { return "BILINEAR"; } - /** Size of the area over which to provide values.*/ + /** Size of the area over which to provide values. */ @Override public Dimension getSupportSize() { return new Dimension(2,2); } - /** Applies bilinear interpolation. */ + /** Applies bilinear interpolation on a 2×2 window. */ @Override public boolean interpolate(final DoubleBuffer source, final int numBands, final double xfrac, final double yfrac, final double[] writeTo, int writeToOffset) { diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java index ca61728..745de69 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java +++ b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java @@ -341,6 +341,8 @@ public class ResampledImage extends ComputedImage { * 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; @@ -385,67 +387,93 @@ public class ResampledImage extends ComputedImage { } toSource.transform(coordinates, 0, coordinates, 0, scanline); /* - * 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 than 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. + * Special case for nearest-neighbor. */ - int ci = 0; // Index in `coordinates` array. - int vi = 0; // Index in `values` or `intValues` array. - for (int tx=tileMinX; tx= 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. - */ - if (interpolation.interpolate(buffer.values, numBands, xf, yf, values, isInteger ? 0 : vi)) { - if (isInteger) { - for (int b=0; b= it.lowerX && x < it.upperX) { + final long y = Math.round(coordinates[ci+1]); + if (y >= it.lowerY && y < it.upperY) { + it.moveTo((int) x, (int) y); + 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 than 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= 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. + */ + if (interpolation.interpolate(buffer.values, numBands, xf, yf, values, isInteger ? 0 : vi)) { + if (isInteger) { + for (int b=0; b