sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 04/04: Make `resample` more robust to cases where target data type is not the same than source data type. In particular in the way to compute min/max values and to determine if a shortcut can be used.
Date Mon, 03 Aug 2020 17:26:55 GMT
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

commit 4de677ac5b57cf6610cf851a4328efa9b5f2c565
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Aug 3 16:59:20 2020 +0200

    Make `resample` more robust to cases where target data type is not the same than source
data type.
    In particular in the way to compute min/max values and to determine if a shortcut can
be used.
---
 .../apache/sis/image/BandedSampleConverter.java    |  9 +++-
 .../java/org/apache/sis/image/Interpolation.java   |  9 ++--
 .../org/apache/sis/image/LanczosInterpolation.java |  3 +-
 .../java/org/apache/sis/image/ResampledImage.java  | 61 ++++++++++++++--------
 .../sis/internal/coverage/j2d/ImageUtilities.java  | 14 +++++
 5 files changed, 65 insertions(+), 31 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
index d83b327..9fcea1a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
@@ -185,10 +185,17 @@ class BandedSampleConverter extends ComputedImage {
      *
      * @see ImageProcessor#convert(RenderedImage, NumberRange[], MathTransform1D[], DataType,
ColorModel)
      */
-    static BandedSampleConverter create(final RenderedImage source, final ImageLayout layout,
+    static BandedSampleConverter create(RenderedImage source, final ImageLayout layout,
             final NumberRange<?>[] sourceRanges, final MathTransform1D[] converters,
             final int targetType, final ColorModel colorModel)
     {
+        /*
+         * Since this operation applies its own ColorModel anyway, skip operation that was
doing nothing else
+         * than changing the color model.
+         */
+        if (source instanceof RecoloredImage) {
+            source = ((RecoloredImage) source).source;
+        }
         final int numBands = converters.length;
         final BandedSampleModel sampleModel = layout.createBandedSampleModel(targetType,
numBands, source);
         /*
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 6cf39e8..3eee8df 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
@@ -96,9 +96,8 @@ public interface Interpolation {
      * @param  yfrac          the Y subsample position, usually (but not always) in the range
[0 … 1).
      * @param  writeTo        the array where this method shall write interpolated values.
      * @param  writeToOffset  index of the first value to put in the {@code writeTo} array.
-     * @return {@code true} on success, or {@code false} if the caller should paint some
fill value instead.
      */
-    boolean interpolate(DoubleBuffer source, int numBands, double xfrac, double yfrac, double[]
writeTo, int writeToOffset);
+    void interpolate(DoubleBuffer source, int numBands, double xfrac, double yfrac, double[]
writeTo, int writeToOffset);
 
     /**
      * A nearest-neighbor interpolation using 1×1 pixel.
@@ -115,13 +114,12 @@ public interface Interpolation {
         }
 
         /** Applies nearest-neighbor interpolation on 1×1 window. */
-        @Override public boolean interpolate(final DoubleBuffer source, final int numBands,
+        @Override public void 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;
         }
     };
 
@@ -141,7 +139,7 @@ public interface Interpolation {
         }
 
         /** Applies bilinear interpolation on a 2×2 window. */
-        @Override public boolean interpolate(final DoubleBuffer source, final int numBands,
+        @Override public void interpolate(final DoubleBuffer source, final int numBands,
                 final double xfrac, final double yfrac, final double[] writeTo, int writeToOffset)
         {
             final double mx = (1 - xfrac);
@@ -159,7 +157,6 @@ public interface Interpolation {
                 }
                 writeTo[writeToOffset++] = y;
             }
-            return true;
         }
     };
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/LanczosInterpolation.java
b/core/sis-feature/src/main/java/org/apache/sis/image/LanczosInterpolation.java
index 91e63e7..613c2e6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/LanczosInterpolation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/LanczosInterpolation.java
@@ -80,7 +80,7 @@ final class LanczosInterpolation implements Interpolation {
      * Applies Lanczos interpolation.
      */
     @Override
-    public boolean interpolate(final DoubleBuffer source, final int numBands,
+    public void interpolate(final DoubleBuffer source, final int numBands,
             final double xfrac, final double yfrac, final double[] writeTo, final int writeToOffset)
     {
         Arrays.fill(writeTo, writeToOffset, writeToOffset + numBands, 0);
@@ -101,7 +101,6 @@ final class LanczosInterpolation implements Interpolation {
             }
         }
         source.reset();
-        return true;
     }
 
     /**
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 acb9b1e..d7bf041 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
@@ -589,6 +589,13 @@ public class ResampledImage extends ComputedImage {
             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 shortcut = (interpolation == Interpolation.NEAREST) &&
+                    ImageUtilities.isLosslessConversion(sampleModel.getDataType(), ImageUtilities.getDataType(tile));
+        /*
          * 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
@@ -602,24 +609,30 @@ public class ResampledImage extends ComputedImage {
         final long[] minValues, maxValues;
         final boolean isInteger = (fillValues instanceof int[]);
         if (isInteger) {
-            values      = new double[numBands];
-            minValues   = new long  [numBands];
-            maxValues   = new long  [numBands];
-            intValues   = new int[scanline * numBands];
-            valuesArray = intValues;
-            for (int b=0; b<numBands; b++) {
-                maxValues[b] = Numerics.bitmask(sampleModel.getSampleSize(b)) - 1;
-            }
-            if (!ImageUtilities.isUnsignedType(sampleModel.getDataType())) {
+            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++) {
-                    minValues[b] = ~(maxValues[b] >>>= 1);      // Convert unsigned
type to signed type range.
+                    maxValues[b] = Numerics.bitmask(sm.getSampleSize(b)) - 1;
+                }
+                if (!ImageUtilities.isUnsignedType(sm.getDataType())) {
+                    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.
+            minValues   = null;                                     // Not used for floating
point types.
             maxValues   = null;
         }
         /*
@@ -642,9 +655,10 @@ public class ResampledImage extends ComputedImage {
             }
             toSourceSupport.transform(coordinates, 0, coordinates, 0, scanline);
             /*
-             * Special case for nearest-neighbor.
+             * 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.
              */
-            if (interpolation == Interpolation.NEAREST) {
+            if (shortcut) {
                 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) {
@@ -652,7 +666,11 @@ public class ResampledImage extends ComputedImage {
                     if (x >= 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 (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);
@@ -709,22 +727,21 @@ public class ResampledImage extends ComputedImage {
                                      * 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<numBands; b++) {
-                                                intValues[vi+b] = (int) Math.max(minValues[b],
-                                                                        Math.min(maxValues[b],
Math.round(values[b])));
-                                            }
+                                    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])));
                                         }
-                                        continue;       // Values have been set, move to
next pixel.
                                     }
+                                    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 are outside the source image bounds)
+                     * (i.e. the point to interpolate is outside the source image bounds)
                      * and no values have been set in the `values` or `intValues` array.
                      */
                     System.arraycopy(fillValues, 0, valuesArray, vi, numBands);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
index 32b0d57..41af223 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
@@ -402,6 +402,20 @@ public final class ImageUtilities extends Static {
     }
 
     /**
+     * Returns whether {@code sourceType} can be converted to {@code targetType} without
data lost.
+     *
+     * @param  sourceType  type of values to convert.
+     * @param  targetType  type of converted values.
+     * @return whether the the conversion from source type to target type is lossless.
+     */
+    public static boolean isLosslessConversion(final int sourceType, final int targetType)
{
+        if (sourceType == DataBuffer.TYPE_USHORT && targetType == DataBuffer.TYPE_SHORT)
{
+            return false;       // TYPE_SHORT > TYPE_USHORT but still of lossy conversion.
+        }
+        return sourceType >= DataBuffer.TYPE_BYTE && targetType <= DataBuffer.TYPE_DOUBLE
&& targetType >= sourceType;
+    }
+
+    /**
      * Converts a <var>x</var> pixel coordinates to a tile index.
      *
      * @param  image  the image containing tiles.


Mime
View raw message