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.
|