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 e37076cae403b63853194fae9c9fd8f44a3e1236
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Aug 5 14:52:54 2020 +0200
Enable the use of IndexColorModel in CoverageCanvas.
---
.../org/apache/sis/gui/coverage/RenderingData.java | 11 ++--
.../apache/sis/internal/gui/ImageConverter.java | 2 +-
.../java/org/apache/sis/image/ImageProcessor.java | 22 ++++---
.../main/java/org/apache/sis/image/MaskImage.java | 54 ++++++++++------
.../java/org/apache/sis/image/ResampledImage.java | 11 +++-
.../java/org/apache/sis/image/Visualization.java | 71 +++++++++++++++++-----
6 files changed, 122 insertions(+), 49 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
index ee1e100..e5ca996 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
@@ -50,6 +50,7 @@ import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
import org.apache.sis.referencing.CRS;
+import org.apache.sis.util.Debug;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.logging.Logging;
@@ -90,7 +91,8 @@ final class RenderingData implements Cloneable {
* to {@code false} for testing or debugging. If {@code false}, images may be only grayscale
and may be much
* slower to render, but should still be visible.
*/
- private static final boolean CREATE_INDEX_COLOR_MODEL = false;
+ @Debug
+ private static final boolean CREATE_INDEX_COLOR_MODEL = true;
/**
* The data fetched from {@link GridCoverage#render(GridExtent)} for current {@code sliceExtent}.
@@ -324,14 +326,13 @@ final class RenderingData implements Cloneable {
* sample values in source image to ranges of sample values in destination
image, then query
* ColorModel.getRGB(Object) for increasing integer values in that range.
*/
- RenderedImage resampledImage = processor.resample(recoloredImage, bounds, displayToCenter);
if (CREATE_INDEX_COLOR_MODEL) {
- final ColorModelType ct = ColorModelType.find(resampledImage.getColorModel());
+ final ColorModelType ct = ColorModelType.find(recoloredImage.getColorModel());
if (ct.isSlow || (processor.getCategoryColors() != null && ct.useColorRamp))
{
- resampledImage = processor.visualize(resampledImage, dataRanges);
+ return processor.visualize(recoloredImage, bounds, displayToCenter, dataRanges);
}
}
- return resampledImage;
+ return processor.resample(recoloredImage, bounds, displayToCenter);
}
/**
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageConverter.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageConverter.java
index af49353..24ce727 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageConverter.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageConverter.java
@@ -68,7 +68,7 @@ final class ImageConverter extends Task<Statistics[]> {
*/
private static final Map<NumberRange<?>,Color[]> MASK_TRANSPARENCY = JDK9.mapOf(
NumberRange.create(0, true, 0, true), new Color[] {ColorModelFactory.TRANSPARENT},
- NumberRange.create(1, true, 1, true), new Color[] {new Color(0x20FFFF00, true)});
+ NumberRange.create(1, true, 1, true), new Color[] {new Color(0x30FFFF00, true)});
/**
* The Java2D image to convert.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
index 34d2278..1d734ef 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
@@ -796,9 +796,9 @@ public class ImageProcessor implements Cloneable {
* The {@link Color} arrays may have any length; colors will be interpolated as needed
for fitting
* the ranges of values in the destination image.</p>
*
- * <p>The resulting image is suitable for visualization purposes, but should not
be used for computation
- * purposes. There is no guarantees about the number of bands in returned image and the
formulas used for
- * converting floating point values to integer values.</p>
+ * <p>The resulting image is suitable for visualization purposes, but should not
be used for computation purposes.
+ * There is no guarantees about the number of bands in returned image or about which
formula is used for converting
+ * floating point values to integer values.</p>
*
* @param source the image to recolor for visualization purposes.
* @param colors colors to use for each range of values in the source image.
@@ -831,9 +831,9 @@ public class ImageProcessor implements Cloneable {
* The {@link Color} arrays may have any length; colors will be interpolated as needed
for fitting
* the ranges of values in the destination image.</p>
*
- * <p>The resulting image is suitable for visualization purposes, but should not
be used for computation
- * purposes. There is no guarantees about the number of bands in returned image and the
formulas used for
- * converting floating point values to integer values.</p>
+ * <p>The resulting image is suitable for visualization purposes, but should not
be used for computation purposes.
+ * There is no guarantees about the number of bands in returned image or about which
formula is used for converting
+ * floating point values to integer values.</p>
*
* @param source the image to recolor for visualization purposes.
* @param ranges description of {@code source} bands, or {@code null} if none. This
is typically
@@ -859,9 +859,13 @@ public class ImageProcessor implements Cloneable {
* <li><code>{@linkplain #visualize(RenderedImage, List) visualize}(resampled,
ranges)</code></li>
* </ol>
*
- * The resulting image is suitable for visualization purposes, but should not be used
for computation
- * purposes. There is no guarantees about the number of bands in returned image and the
formulas used
- * for converting floating point values to integer values.
+ * Combining above steps may be advantageous when the {@code resample(…)} result is
not needed for anything
+ * else than visualization. If the same resampling may be needed for computational purposes,
then it may be
+ * more advantageous to keep above method calls separated instead than using this {@code
visualize(…)} method.
+ *
+ * <p>The resulting image is suitable for visualization purposes, but should not
be used for computation purposes.
+ * There is no guarantees about the number of bands in returned image or about which
formula is used for converting
+ * floating point values to integer values.</p>
*
* @param source the image to be resampled and recolored.
* @param bounds domain of pixel coordinates of resampled image to create.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/MaskImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/MaskImage.java
index 28e8e3b..ef99182 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/MaskImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/MaskImage.java
@@ -18,27 +18,47 @@ package org.apache.sis.image;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
+import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.internal.system.Modules;
+import org.apache.sis.util.logging.Logging;
/**
* Mask of missing values.
- * This is the implementation of {@link ResampledImage#MASK_KEY} property value.
+ * This is the implementation of {@value ResampledImage#MASK_KEY} property value.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
- * @since 1.1
+ *
+ * @see ResampledImage#getProperty(String)
+ * @see ResampledImage#MASK_KEY
+ *
+ * @since 1.1
* @module
*/
final class MaskImage extends SourceAlignedImage {
/**
+ * Convert integer values to floating point values, or {@code null} if none.
+ * This is needed since we use {@link Float#isNaN(float)} for identifying values to mask.
+ */
+ private MathTransform converter;
+
+ /**
* Creates a new instance for the given image.
*/
MaskImage(final ResampledImage image) {
super(image, ColorModelFactory.createIndexColorModel(
1, ImageUtilities.getVisibleBand(image), new int[] {0, -1}, 0));
+ if (image.interpolation instanceof Visualization.InterpConvert) try {
+ converter = ((Visualization.InterpConvert) image.interpolation).converter.inverse();
+ } catch (NoninvertibleTransformException e) {
+ // ResampledImage.getProperty("org.apache.sis.Mask") is the public caller of
this constructor.
+ Logging.unexpectedException(Logging.getLogger(Modules.RASTER), ResampledImage.class,
"getProperty", e);
+ }
}
/**
@@ -77,23 +97,21 @@ final class MaskImage extends SourceAlignedImage {
* often that there is a tile to recycle anyway.
*/
tile = createTile(tileX, tileY);
- final int tileMinX = tile.getMinX();
- final int tileMinY = tile.getMinY();
- final int tileMaxX = Math.addExact(tileMinX, tile.getWidth());
- final int tileMaxY = Math.addExact(tileMinY, tile.getHeight());
- float[] values = null;
- /*
- * Following algorithm is inefficient; it would be much faster to read or write directly
in the arrays.
- * But it may not be worth to optimize it for now.
- */
+ final int numBands = tile.getNumBands();
+ final int tileMinX = tile.getMinX();
+ final int tileMinY = tile.getMinY();
+ final int tileMaxY = Math.addExact(tileMinY, tile.getHeight());
+ final int tileWidth = tile.getWidth();
+ final float[] row = new float[Math.multiplyExact(tileWidth, numBands)];
for (int y=tileMinY; y<tileMaxY; y++) {
- for (int x=tileMinX; x<tileMaxX; x++) {
- values = source.getPixel(x, y, values);
- for (int i=0; i<values.length; i++) {
- if (Float.isNaN(values[i])) {
- tile.setSample(x, y, 0, 1);
- break;
- }
+ source.getPixels(tileMinX, y, tileWidth, 1, row);
+ if (converter != null) {
+ converter.transform(row, 0, row, 0, tileWidth);
+ }
+ for (int i=0; i<row.length; i++) {
+ if (Float.isNaN(row[i])) {
+ final int x = i / numBands + tileMinX;
+ tile.setSample(x, y, 0, 1);
}
// Otherwise leave the value to 0.
}
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 d7bf041..5cb31b1 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
@@ -375,6 +375,13 @@ public class ResampledImage extends ComputedImage {
}
/**
+ * Returns {@code true} if this image can not have mask.
+ */
+ boolean hasNoMask() {
+ return ImageUtilities.isIntegerType(sampleModel.getDataType());
+ }
+
+ /**
* Verifies whether image layout information are consistent. This method performs all
verifications
* {@linkplain ComputedImage#verify() documented in parent class}, then verifies that
source coordinates
* required by this image (computed by converting {@linkplain #getBounds() this image
bounds} using the
@@ -453,7 +460,7 @@ public class ResampledImage extends ComputedImage {
throw (ImagingOpException) new ImagingOpException(e.getMessage()).initCause(e);
}
case MASK_KEY: {
- if (ImageUtilities.isIntegerType(sampleModel.getDataType())) break;
+ if (hasNoMask()) break;
return getMask();
}
}
@@ -484,7 +491,7 @@ public class ResampledImage extends ComputedImage {
continue; // Exclude PositionalAccuracy
change.
}
} else if (name == MASK_KEY) {
- if (ImageUtilities.isIntegerType(sampleModel.getDataType())) {
+ if (hasNoMask()) {
continue;
}
} else if (!ArraysExt.contains(inherited, name)) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
index 6377380..caadf7d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
@@ -97,7 +97,7 @@ final class Visualization extends ResampledImage {
layout.createBandedSampleModel(Colorizer.TYPE_COMPACT, converters.length, source,
bounds),
(bounds != null) ? bounds : ImageUtilities.getBounds(source),
toSource,
- isIdentity ? Interpolation.NEAREST : new InterpConvert(interpolation, converters).simplify(),
+ isIdentity ? Interpolation.NEAREST : combine(interpolation, converters),
fillValues,
accuracy);
@@ -106,44 +106,51 @@ final class Visualization extends ResampledImage {
}
/**
+ * Combines the given interpolation method with the given sample conversion.
+ */
+ static Interpolation combine(final Interpolation interpolation, final MathTransform1D[]
converters) {
+ final MathTransform converter = CompoundTransform.create(converters);
+ if (converter.isIdentity()) {
+ return interpolation;
+ } else if (converter instanceof MathTransform1D) {
+ return new InterpConvertOneBand(interpolation, (MathTransform1D) converter);
+ } else {
+ return new InterpConvert(interpolation, converter);
+ }
+ }
+
+ /**
* Interpolation followed by conversion from floating point values to the values to store
as integers in the
* destination image. This class is used for combining {@link ResampledImage} and {@link
BandedSampleConverter}
* in a single operation.
*/
- private static final class InterpConvert implements Interpolation {
+ static class InterpConvert implements Interpolation {
/**
* The object to use for performing interpolations.
*
* @see ResampledImage#interpolation
*/
- private final Interpolation interpolation;
+ final Interpolation interpolation;
/**
* Conversion from floating point values resulting from interpolations to values
to store as integers
* in the destination image. This transform shall operate on all bands in one {@code
transform(…)} call.
*/
- private final MathTransform converter;
+ final MathTransform converter;
/**
* Creates a new object combining the given interpolation with the given conversion
of sample values.
*/
- InterpConvert(final Interpolation interpolation, final MathTransform1D[] converters)
{
+ InterpConvert(final Interpolation interpolation, final MathTransform converter) {
this.interpolation = interpolation;
- converter = CompoundTransform.create(converters);
- }
-
- /**
- * Returns a more direct {@code Interpolation} object if possible, or {@code this}
otherwise.
- */
- Interpolation simplify() {
- return converter.isIdentity() ? interpolation : this;
+ this.converter = converter;
}
/**
* Delegates to {@link Interpolation#getSupportSize()}.
*/
@Override
- public Dimension getSupportSize() {
+ public final Dimension getSupportSize() {
return interpolation.getSupportSize();
}
@@ -168,6 +175,42 @@ final class Visualization extends ResampledImage {
}
/**
+ * Same as {@link InterpConvert} optimized for the single-band case.
+ * This class uses the more efficient {@link MathTransform1D#transform(double)} method.
+ */
+ private static final class InterpConvertOneBand extends InterpConvert {
+ /** Conversion from floating point values to values to store as integers in the destination
image. */
+ private final MathTransform1D singleConverter;
+
+ /** Creates a new object combining the given interpolation with the given conversion
of sample values. */
+ InterpConvertOneBand(final Interpolation interpolation, final MathTransform1D converter)
{
+ super(interpolation, converter);
+ singleConverter = converter;
+ }
+
+ /** Delegates to {@link #interpolation}, then convert sample values in all bands.
*/
+ @Override public void interpolate(final DoubleBuffer source, final int numBands,
+ final double xfrac, final double yfrac,
+ final double[] writeTo, final int writeToOffset)
+ {
+ interpolation.interpolate(source, numBands, xfrac, yfrac, writeTo, writeToOffset);
+ try {
+ writeTo[writeToOffset] = singleConverter.transform(writeTo[writeToOffset]);
+ } catch (TransformException e) {
+ throw new BackingStoreException(e); // Will be unwrapped by computeTile(…).
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if this image can not have mask.
+ */
+ @Override
+ final boolean hasNoMask() {
+ return !(interpolation instanceof InterpConvert) && super.hasNoMask();
+ }
+
+ /**
* Returns an image where all sample values are indices of colors in an {@link IndexColorModel}.
* If the given image stores sample values as unsigned bytes or short integers, then
those values
* are used as-is (they are not copied or converted). Otherwise this operation will convert
sample
|