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 fe65738ec3e43accdcb27df1a5f29a877c51bfd8
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sun Apr 24 17:03:49 2022 +0200

    Use RGB color model when the BIL/BIP/BSQ raster has 3 or 4 bands of integer 
type.
---
 .../apache/sis/gui/coverage/BandRangeTable.java    |  5 +-
 .../internal/coverage/j2d/ColorModelFactory.java   | 57 +++++++++++++++++++++-
 .../sis/internal/storage/esri/RasterStore.java     | 47 +++++++++++++-----
 3 files changed, 94 insertions(+), 15 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/BandRangeTable.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/BandRangeTable.java
index 5e4111f4f1..13f7ae4fa8 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/BandRangeTable.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/BandRangeTable.java
@@ -25,6 +25,7 @@ import javafx.scene.control.TableColumn.CellDataFeatures;
 import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.beans.value.ObservableValue;
 import javafx.geometry.Pos;
+import org.opengis.util.GenericName;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.util.resources.Vocabulary;
@@ -38,7 +39,7 @@ import org.apache.sis.internal.gui.Styles;
  * the interfaces implemented by this class are implementation convenience 
that may change in any future version.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -138,7 +139,7 @@ final class BandRangeTable implements 
Callback<TableColumn<SampleDimension,Numbe
         final Optional<?> text;
         final SampleDimension sd = cell.getValue();
         switch (cell.getTableColumn().getId()) {
-            case NAME:  text = Optional.ofNullable(sd.getName()); break;
+            case NAME:  text = 
Optional.ofNullable(sd.getName()).map(GenericName::toInternationalString); 
break;
             case UNITS: text = sd.getUnits(); break;
             default: throw new AssertionError();       // Should not happen.
         }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
index c1f3093d4e..99192128ee 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java
@@ -32,6 +32,7 @@ import java.awt.image.ComponentColorModel;
 import java.awt.image.SampleModel;
 import java.awt.image.DataBuffer;
 import org.apache.sis.measure.NumberRange;
+import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.collection.WeakHashSet;
@@ -481,6 +482,56 @@ public final class ColorModelFactory {
         return Math.abs(minimum) < 1 && Math.abs(maximum - (upper - 0.5)) < 
1.5;
     }
 
+    /**
+     * Creates a color model for opaque images storing pixels using the given 
sample model.
+     * The color model can have an arbitrary number of bands, but in current 
implementation only one band is used.
+     *
+     * <p><b>Warning:</b> the use of this color model may be very slow and the 
color stretching may not be a good fit.
+     * This method should be used only when no standard color model can be 
used. This is a last resort method.</p>
+     *
+     * @param  model        the sample model for which to create a gray scale 
color model.
+     * @param  visibleBand  the band to use for computing colors.
+     * @param  range        the minimal and maximal sample value expected, or 
{@code null} if none.
+     * @return the color model for the given range of values.
+     */
+    public static ColorModel createGrayScale(final SampleModel model, final 
int visibleBand, final NumberRange<?> range) {
+        double minimum, maximum;
+        if (range != null) {
+            minimum = range.getMinDouble();
+            maximum = range.getMaxDouble();
+        } else {
+            minimum = 0;
+            maximum = 1;
+            if (ImageUtilities.isIntegerType(model)) {
+                long max = Numerics.bitmask(model.getSampleSize(visibleBand)) 
- 1;
+                if (!ImageUtilities.isUnsignedType(model)) {
+                    max >>>= 1;
+                    minimum = ~max;         // Tild operator, not minus.
+                }
+                maximum = max;
+            }
+        }
+        return createGrayScale(model.getDataType(), model.getNumBands(), 
visibleBand, minimum, maximum);
+    }
+
+    /**
+     * Creates a RGB color model for the given sample model.
+     * The sample model shall use integer type and have 3 or 4 bands.
+     *
+     * @param  model  the sample model for which to create a color model.
+     * @return the color model.
+     */
+    public static ColorModel createRGB(final SampleModel model) {
+        final int numBands = model.getNumBands();
+        assert numBands >= 3 && numBands <= 4 : numBands;
+        assert ImageUtilities.isIntegerType(model) : model;
+        int bitsPerSample = 0;
+        for (int i=0; i<numBands; i++) {
+            bitsPerSample = Math.max(bitsPerSample, model.getSampleSize(i));
+        }
+        return createRGB(bitsPerSample, model.getNumDataElements() == 1, 
numBands > 3);
+    }
+
     /**
      * Creates a RGB color model. The {@code packed} argument should be
      * {@code true}  for color model used with {@link 
java.awt.image.SinglePixelPackedSampleModel}, and
@@ -540,8 +591,10 @@ public final class ColorModelFactory {
      * deduced.
      */
     public static Optional<ColorModel> createSubset(final ColorModel cm, final 
int[] bands) {
-        if (cm == null) return Optional.empty();
-        assert bands != null && bands.length > 0 : bands;
+        assert (bands != null) && bands.length > 0 : bands;
+        if (cm == null) {
+            return Optional.empty();
+        }
         final ColorModel subset;
         if (cm instanceof MultiBandsIndexColorModel) {
             subset = ((MultiBandsIndexColorModel) 
cm).createSubsetColorModel(bands);
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
index 9cd4b92648..4db80b9a4b 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
@@ -45,6 +45,7 @@ import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.storage.RangeArgument;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.math.Statistics;
 
 
@@ -188,11 +189,12 @@ abstract class RasterStore extends PRJDataStore 
implements GridCoverageResource
         boolean computeForEachBand = false;
         final boolean isInteger  = ImageUtilities.isIntegerType(dataType);
         final boolean isUnsigned = isInteger && 
ImageUtilities.isUnsignedType(sm);
+        final boolean isRGB      = isInteger && (bands.length == 3 || 
bands.length == 4);
         if (stats != null && stats.count() != 0) {
             minimum = stats.minimum();
             maximum = stats.maximum();
         } else {
-            computeForEachBand = isInteger;
+            computeForEachBand = isInteger && !isRGB;
         }
         final SampleDimension.Builder builder = new SampleDimension.Builder();
         for (int band=0; band < bands.length; band++) {
@@ -215,26 +217,45 @@ abstract class RasterStore extends PRJDataStore 
implements GridCoverageResource
              * The sample dimension is considered "converted" on the 
assumption that caller will replace
              * all "no data" value by NaN before to return the raster to the 
user.
              */
-            if (name != null) {
-                builder.setName(name);
-                name = null;                // Use the name only for the first 
band.
-            }
-            builder.addQuantitative(null, minimum, maximum, null);
-            if (nodataValue < minimum || nodataValue > maximum) {
-                builder.mapQualitative(null, nodataValue, Float.NaN);
+            if (isRGB) {
+                
builder.setName(Vocabulary.formatInternational(RGB_BAND_NAMES[band]));
+            } else {
+                if (name != null) {
+                    builder.setName(name);
+                    name = null;                // Use the name only for the 
first band.
+                }
+                builder.addQuantitative(null, minimum, maximum, null);
+                if (nodataValue < minimum || nodataValue > maximum) {
+                    builder.mapQualitative(null, nodataValue, Float.NaN);
+                }
             }
             bands[band] = builder.build().forConvertedValues(!isInteger);
             builder.clear();
             /*
-             * Create the color model using the statistics of the band that we 
choose to make visible.
+             * Create the color model using the statistics of the band that we 
choose to make visible,
+             * or using a RGB color model if the number of bands and the data 
type are compatible.
              */
             if (band == VISIBLE_BAND) {
-                colorModel = ColorModelFactory.createGrayScale(dataType, 
sm.getNumBands(), band, minimum, maximum);
+                if (isRGB) {
+                    colorModel = ColorModelFactory.createRGB(sm);
+                } else {
+                    colorModel = ColorModelFactory.createGrayScale(dataType, 
bands.length, band, minimum, maximum);
+                }
             }
         }
         sampleDimensions = UnmodifiableArrayList.wrap(bands);
     }
 
+    /**
+     * Default names of bands when the color model is RGB or RGBA.
+     */
+    private static final short[] RGB_BAND_NAMES = {
+        Vocabulary.Keys.Red,
+        Vocabulary.Keys.Green,
+        Vocabulary.Keys.Blue,
+        Vocabulary.Keys.Transparency
+    };
+
     /**
      * Creates the grid coverage resulting from a {@link #read(GridGeometry, 
int...)} operation.
      *
@@ -259,7 +280,11 @@ abstract class RasterStore extends PRJDataStore implements 
GridCoverageResource
         ColorModel cm = colorModel;
         if (!range.isIdentity()) {
             bands = Arrays.asList(range.select(sampleDimensions));
-            cm = range.select(colorModel).get();
+            cm = range.select(colorModel).orElse(null);
+            if (cm == null) {
+                final SampleDimension band = bands.get(VISIBLE_BAND);
+                cm = ColorModelFactory.createGrayScale(data.getSampleModel(), 
VISIBLE_BAND, band.getSampleRange().orElse(null));
+            }
         }
         return new GridCoverage2D(domain, bands, new BufferedImage(cm, data, 
false, properties));
     }

Reply via email to