sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/02: Enable the use of IndexColorModel in CoverageCanvas.
Date Wed, 05 Aug 2020 20:32:41 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 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


Mime
View raw message