sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Add a PlanarImage.MASK_KEY property providing a mask over missing values.
Date Sat, 27 Jun 2020 12:54:49 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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new acea035  Add a PlanarImage.MASK_KEY property providing a mask over missing values.
acea035 is described below

commit acea0356c352c92edf29e5971040a8f0b66b3e23
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Jun 27 14:47:39 2020 +0200

    Add a PlanarImage.MASK_KEY property providing a mask over missing values.
---
 .../java/org/apache/sis/image/ComputedImage.java   |   8 ++
 .../main/java/org/apache/sis/image/MaskImage.java  | 109 +++++++++++++++++++
 .../java/org/apache/sis/image/PlanarImage.java     |  17 +++
 .../sis/image/PositionalConsistencyImage.java      | 108 ++++++-------------
 .../java/org/apache/sis/image/ResampledImage.java  |  49 ++++++---
 .../org/apache/sis/image/SourceAlignedImage.java   | 115 +++++++++++++++++++++
 .../internal/coverage/j2d/ColorModelFactory.java   |   2 +-
 .../coverage/j2d/MultiBandsIndexColorModel.java    |  15 ++-
 8 files changed, 328 insertions(+), 95 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
index f95b244..d64193b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
@@ -245,6 +245,14 @@ public abstract class ComputedImage extends PlanarImage implements Disposable
{
     }
 
     /**
+     * Returns the source at index 0 without any check. This method is invoked by subclasses
who know that a
+     * single source exist. This method should not be in public API because of the absence
of verification.
+     */
+    final RenderedImage getSource() {
+        return sources[0];
+    }
+
+    /**
      * Returns the immediate sources of image data for this image (may be {@code null}).
      * This method returns all sources specified at construction time.
      *
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
new file mode 100644
index 0000000..17e0366
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/MaskImage.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image;
+
+import java.util.Set;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.internal.jdk9.JDK9;
+
+
+/**
+ * Mask of missing values.
+ * This is the implementation of {@link ResampledImage#MASK_KEY} property value.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class MaskImage extends SourceAlignedImage {
+    /**
+     * Properties inherited from the source image. Must be consistent with the <cite>switch
case</cite>
+     * statement delegating to the source image in {@link #getProperty(String)}.
+     *
+     * @see #getPropertyNames()
+     */
+    private static final Set<String> INHERITED_PROPERTIES = JDK9.setOf(
+            GRID_GEOMETRY_KEY, POSITIONAL_ACCURACY_KEY, ResampledImage.POSITIONAL_CONSISTENCY_KEY);
+
+    /**
+     * Creates a new instance for the given image.
+     */
+    MaskImage(final ResampledImage image) {
+        super(image, ColorModelFactory.createIndexColorModel(new int[] {0, -1},
+                image.getSampleModel().getNumBands(), ImageUtilities.getVisibleBand(image),
0));
+    }
+
+    /**
+     * Gets a property from this image.
+     */
+    @Override
+    public Object getProperty(final String key) {
+        return INHERITED_PROPERTIES.contains(key) ? getSource().getProperty(key) : super.getProperty(key);
+    }
+
+    /**
+     * Returns the names of all recognized properties.
+     *
+     * @return names of all recognized properties, or {@code null} if none.
+     */
+    @Override
+    public String[] getPropertyNames() {
+        return getPropertyNames(INHERITED_PROPERTIES, null);
+    }
+
+    /**
+     * Invoked when a tile need to be computed or updated.
+     *
+     * @param  tileX  the column index of the tile to compute.
+     * @param  tileY  the row index of the tile to compute.
+     * @param  tile   if the tile already exists but needs to be updated, the tile to update.
Otherwise {@code null}.
+     * @return computed tile for the given indices.
+     * @throws TransformException if an error occurred while computing pixel coordinates.
+     */
+    @Override
+    protected Raster computeTile(final int tileX, final int tileY, WritableRaster tile) throws
TransformException {
+        final Raster source = getSource().getTile(tileX, tileY);
+        if (tile == null) {
+            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());
+        final int[] pixel  = new int[tile.getNumBands()];
+        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.
+         */
+        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<pixel.length; i++) {
+                    pixel[i] = Float.isNaN(values[i]) ? 1 : 0;
+                }
+                tile.setPixel(x, y, pixel);
+            }
+        }
+        return tile;
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
index 5ea8970..2646114 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
@@ -172,6 +172,20 @@ public abstract class PlanarImage implements RenderedImage {
     public static final String STATISTICS_KEY = "org.apache.sis.Statistics";
 
     /**
+     * Key of property providing a mask for missing values. Values should be instances of
{@link RenderedImage}
+     * with binary sample values and a color model of {@link java.awt.Transparency#BITMASK}
type.
+     * The binary values 0 and 1 are alpha values: 0 for fully transparent pixels and 1 for
fully opaque pixels.
+     * For every pixel (<var>x</var>,<var>y</var>) in this image,
the pixel at the same coordinates in the mask
+     * is either fully transparent (sample value 0) if the sample value in this image is
valid, or fully opaque
+     * (sample value 1) if the sample value in this image is invalid ({@link Float#NaN}).
+     *
+     * <p>Note that it is usually not necessary to use masks explicitly in Apache SIS
because missing values
+     * are represented by {@link Float#NaN}. This property is provided for algorithms that
can not work with
+     * NaN values.</p>
+     */
+    public static final String MASK_KEY = "org.apache.sis.Mask";
+
+    /**
      * Creates a new rendered image.
      */
     protected PlanarImage() {
@@ -216,6 +230,9 @@ public abstract class PlanarImage implements RenderedImage {
      *     <td>{@value #STATISTICS_KEY}</td>
      *     <td>Minimum, maximum and mean values for each band.</td>
      *   </tr><tr>
+     *     <td>{@value #MASK_KEY}</td>
+     *     <td>Image with transparent pixels at locations of valid values and opaque
pixels elsewhere.</td>
+     *   </tr><tr>
      *     <td>{@value ResampledImage#POSITIONAL_CONSISTENCY_KEY}</td>
      *     <td>Estimation of positional error for each pixel as a consistency check.</td>
      *   </tr><tr>
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PositionalConsistencyImage.java
b/core/sis-feature/src/main/java/org/apache/sis/image/PositionalConsistencyImage.java
index 24ffd5d..77f2596 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PositionalConsistencyImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PositionalConsistencyImage.java
@@ -16,16 +16,14 @@
  */
 package org.apache.sis.image;
 
-import java.awt.image.ColorModel;
+import java.util.Set;
 import java.awt.image.DataBuffer;
-import java.awt.image.PixelInterleavedSampleModel;
 import java.awt.image.Raster;
-import java.awt.image.SampleModel;
 import java.awt.image.WritableRaster;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
-import org.apache.sis.util.Workaround;
+import org.apache.sis.internal.jdk9.JDK9;
 
 
 /**
@@ -37,17 +35,23 @@ import org.apache.sis.util.Workaround;
  * @since   1.1
  * @module
  */
-final class PositionalConsistencyImage extends ComputedImage {
+final class PositionalConsistencyImage extends SourceAlignedImage {
     /**
-     * The color model for this image.
-     * Arbitrarily configured for an error range from 0 to 1.
+     * Properties inherited from the source image. Must be consistent with the <cite>switch
case</cite>
+     * statement delegating to the source image in {@link #getProperty(String)}.
+     *
+     * @see #getPropertyNames()
      */
-    private final ColorModel colorModel;
+    private static final Set<String> INHERITED_PROPERTIES = JDK9.setOf(
+            GRID_GEOMETRY_KEY, POSITIONAL_ACCURACY_KEY, MASK_KEY);
 
     /**
-     * Domain of pixel coordinates in this image.
+     * Properties added by this image, no matter if present in source image or not. Must
be consistent with
+     * the <cite>switch case</cite> statement doing its own calculation in {@link
#getProperty(String)}.
+     *
+     * @see #getPropertyNames()
      */
-    private final int minX, minY, width, height;
+    private static final String[] ADDED_PROPERTIES = {SAMPLE_RESOLUTIONS_KEY};
 
     /**
      * A copy of {@link ResampledImage#toSourceSupport} with the support translation removed.
@@ -67,48 +71,28 @@ final class PositionalConsistencyImage extends ComputedImage {
      * Creates a new instance for the given image.
      */
     PositionalConsistencyImage(final ResampledImage image, final MathTransform toSource)
throws TransformException {
-        super(createSampleModel(image.getSampleModel()), image);
+        super(image, ColorModelFactory.createGrayScale(DataBuffer.TYPE_FLOAT, 1, 0, 0, 1));
         this.toSource = toSource;
         this.toTarget = image.toSource.inverse();
-        this.minX     = image.getMinX();
-        this.minY     = image.getMinY();
-        this.width    = image.getWidth();
-        this.height   = image.getHeight();
-        colorModel    = ColorModelFactory.createGrayScale(DataBuffer.TYPE_FLOAT, 1, 0, 0,
1);
-    }
-
-    /**
-     * Creates the sample model. This is a workaround for RFE #4093999
-     * ("Relax constraint on placement of this()/super() call in constructors").
-     */
-    @Workaround(library="JDK", version="1.8")
-    private static SampleModel createSampleModel(final SampleModel origin) {
-        final int width = origin.getWidth();
-        return new PixelInterleavedSampleModel(DataBuffer.TYPE_FLOAT, width, origin.getHeight(),
1, width, new int[1]);
-    }
-
-    /**
-     * Returns an arbitrary color model for this image.
-     */
-    @Override
-    public ColorModel getColorModel() {
-        return colorModel;
     }
 
     /**
-     * Gets a property from this image. Current default implementation supports the following
keys
-     * (more properties may be added to this list in future Apache SIS versions):
-     *
-     * <ul>
-     *   <li>{@value #SAMPLE_RESOLUTIONS_KEY}</li>
-     * </ul>
+     * Gets a property from this image.
      */
     @Override
     public Object getProperty(final String key) {
-        if (SAMPLE_RESOLUTIONS_KEY.equals(key)) {
-            return new double[] {ResamplingGrid.TOLERANCE / 8};
-        } else {
-            return super.getProperty(key);
+        switch (key) {
+            case SAMPLE_RESOLUTIONS_KEY: {
+                return new double[] {ResamplingGrid.TOLERANCE / 8};
+            }
+            case POSITIONAL_ACCURACY_KEY:
+            case GRID_GEOMETRY_KEY:
+            case MASK_KEY: {
+                return getSource().getProperty(key);
+            }
+            default: {
+                return super.getProperty(key);
+            }
         }
     }
 
@@ -119,41 +103,7 @@ final class PositionalConsistencyImage extends ComputedImage {
      */
     @Override
     public String[] getPropertyNames() {
-        return new String[] {
-            SAMPLE_RESOLUTIONS_KEY
-        };
-    }
-
-    /**
-     * Returns the minimum <var>x</var> coordinate (inclusive) of this image.
-     */
-    @Override
-    public final int getMinX() {
-        return minX;
-    }
-
-    /**
-     * Returns the minimum <var>y</var> coordinate (inclusive) of this image.
-     */
-    @Override
-    public final int getMinY() {
-        return minY;
-    }
-
-    /**
-     * Returns the number of columns in this image.
-     */
-    @Override
-    public final int getWidth() {
-        return width;
-    }
-
-    /**
-     * Returns the number of rows in this image.
-     */
-    @Override
-    public final int getHeight() {
-        return height;
+        return getPropertyNames(INHERITED_PROPERTIES, ADDED_PROPERTIES);
     }
 
     /**
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 28e91a6..bb31faa 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
@@ -154,6 +154,14 @@ public class ResampledImage extends ComputedImage {
     private Reference<ComputedImage> positionalConsistency;
 
     /**
+     * {@link #MASK_KEY} value, computed when first requested.
+     *
+     * @see #getMask()
+     * @see #getProperty(String)
+     */
+    private Reference<ComputedImage> mask;
+
+    /**
      * Creates a new image which will resample the given image. The resampling operation
is defined
      * by a non-linear transform from <em>this</em> image to the specified <em>source</em>
image.
      * That transform should map {@linkplain org.opengis.referencing.datum.PixelInCell#CELL_CENTER
pixel centers}.
@@ -357,6 +365,21 @@ public class ResampledImage extends ComputedImage {
     }
 
     /**
+     * Computes the {@value #MASK_KEY} value. This method is invoked by {@link #getProperty(String)}
when the
+     * {@link #MASK_KEY} property value is requested. The result is saved by weak reference
since recomputing
+     * this image is rarely requested, and if needed can be recomputed easily.
+     */
+    private synchronized RenderedImage getMask() {
+        ComputedImage image = (mask != null) ? mask.get() : null;
+        if (image == null) {
+            mask = null;                    // Cleared first in case an error occurs below.
+            image = new MaskImage(this);
+            mask = image.reference();
+        }
+        return image;
+    }
+
+    /**
      * 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
@@ -393,13 +416,6 @@ public class ResampledImage extends ComputedImage {
     }
 
     /**
-     * Returns the unique source of this resampled image.
-     */
-    private RenderedImage getSource() {
-        return getSource(0);
-    }
-
-    /**
      * Returns the same color model than the source image.
      *
      * @return the color model, or {@code null} if unspecified.
@@ -414,8 +430,10 @@ public class ResampledImage extends ComputedImage {
      * (more properties may be added to this list in future Apache SIS versions):
      *
      * <ul>
+     *   <li>{@value #POSITIONAL_ACCURACY_KEY}</li>
      *   <li>{@value #POSITIONAL_CONSISTENCY_KEY}</li>
      *   <li>{@value #SAMPLE_RESOLUTIONS_KEY} (forwarded to the source image)</li>
+     *   <li>{@value #MASK_KEY} if the image uses floating point numbers.</li>
      * </ul>
      *
      * <div class="note"><b>Note:</b>
@@ -438,10 +456,12 @@ public class ResampledImage extends ComputedImage {
             } catch (TransformException | IllegalArgumentException e) {
                 throw (ImagingOpException) new ImagingOpException(e.getMessage()).initCause(e);
             }
-            default: {
-                return super.getProperty(key);
+            case MASK_KEY: {
+                if (ImageUtilities.isIntegerType(sampleModel.getDataType())) break;
+                return getMask();
             }
         }
+        return super.getProperty(key);
     }
 
     /**
@@ -457,14 +477,19 @@ public class ResampledImage extends ComputedImage {
         final String[] names = {
             SAMPLE_RESOLUTIONS_KEY,
             POSITIONAL_ACCURACY_KEY,
-            POSITIONAL_CONSISTENCY_KEY
+            POSITIONAL_CONSISTENCY_KEY,
+            MASK_KEY
         };
         int n = 0;
         for (final String name : names) {
-            if (name != POSITIONAL_CONSISTENCY_KEY) {
+            if (name != POSITIONAL_CONSISTENCY_KEY) {           // Identity comparisons are
okay for this method.
                 if (name == POSITIONAL_ACCURACY_KEY) {
                     if (getPositionalAccuracyCount() == 0) {
-                        continue;                   // Exclude PositionalAccuracy change.
+                        continue;                               // Exclude PositionalAccuracy
change.
+                    }
+                } else if (name == MASK_KEY) {
+                    if (ImageUtilities.isIntegerType(sampleModel.getDataType())) {
+                        continue;
                     }
                 } else if (!ArraysExt.contains(inherited, name)) {
                     continue;                       // Exclude inherited property not defined
by source.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java
new file mode 100644
index 0000000..f8085a5
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image;
+
+import java.util.Set;
+import java.awt.image.ColorModel;
+import java.awt.image.SampleModel;
+import java.awt.image.RenderedImage;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Workaround;
+
+
+/**
+ * An image computed from a single source and sharing the same coordinate system.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+abstract class SourceAlignedImage extends ComputedImage {
+    /**
+     * The color model for this image.
+     */
+    private final ColorModel colorModel;
+
+    /**
+     * Creates a new image with the given source and a sample model derived from the color
model.
+     * The new image will have the same tile size than the given image.
+     *
+     * @param  source       source of this image. Shall not be null.
+     * @param  colorModel   the color model of the new image.
+     */
+    SourceAlignedImage(final RenderedImage source, final ColorModel colorModel) {
+        super(createSampleModel(colorModel, source.getSampleModel()), source);
+        this.colorModel = colorModel;
+    }
+
+    /**
+     * Creates the sample model. This is a workaround for RFE #4093999
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @Workaround(library="JDK", version="1.8")
+    private static SampleModel createSampleModel(final ColorModel colorModel, final SampleModel
original) {
+        final SampleModel sm = colorModel.createCompatibleSampleModel(original.getWidth(),
original.getHeight());
+        return original.equals(sm) ? original : sm;
+    }
+
+    /**
+     * Returns an arbitrary color model for this image.
+     */
+    @Override
+    public final ColorModel getColorModel() {
+        return colorModel;
+    }
+
+    /**
+     * Returns the names of properties as a merge between source properties (after filtering)
and properties added
+     * by the subclass. This is a helper method for {@link #getPropertyNames()} implementations
in subclasses.
+     *
+     * @param  inherit  properties to inherit from the source.
+     * @param  append   properties to append, or {@code null} if none.
+     * @return properties recognized by this image, or {@code null} if none.
+     */
+    final String[] getPropertyNames(final Set<String> inherit, final String[] append)
{
+        /*
+         * This method modifies directly the array returned by `getPropertyNames()` on the
assumption that the array
+         * is already a copy. This assumption is okay when the source is known to be an Apache
SIS implementation.
+         */
+        String[] names = getSource().getPropertyNames();
+        if (names == null) {
+            return (append != null) ? append.clone() : null;
+        }
+        int n = 0;
+        for (final String name : names) {
+            if (inherit.contains(name)) {
+                names[n++] = name;
+            }
+        }
+        if (append == null) {
+            return ArraysExt.resize(names, n);
+        }
+        names = ArraysExt.resize(names, n + append.length);
+        System.arraycopy(names, n, append, 0, append.length);
+        return names;
+    }
+
+    /**
+     * Delegates to source image.
+     */
+    @Override public final int getMinX()            {return getSource().getMinX();}
+    @Override public final int getMinY()            {return getSource().getMinY();}
+    @Override public final int getWidth()           {return getSource().getWidth();}
+    @Override public final int getHeight()          {return getSource().getHeight();}
+    @Override public final int getMinTileX()        {return getSource().getMinTileX();}
+    @Override public final int getMinTileY()        {return getSource().getMinTileY();}
+    @Override public final int getNumXTiles()       {return getSource().getNumXTiles();}
+    @Override public final int getNumYTiles()       {return getSource().getNumYTiles();}
+    @Override public final int getTileGridXOffset() {return getSource().getTileGridXOffset();}
+    @Override public final int getTileGridYOffset() {return getSource().getTileGridYOffset();}
+}
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 73cd32e..19d4fd8 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
@@ -357,7 +357,7 @@ public final class ColorModelFactory {
     }
 
     /**
-     * Returns a tolerant index color model for the specified ARGB code.
+     * Returns a tolerant index color model for the specified ARGB codes.
      * This color model accepts image with the specified number of bands.
      *
      * <p>This methods caches previously created instances using weak references,
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
index 988be37..5aca711 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java
@@ -24,6 +24,7 @@ import java.awt.image.WritableRaster;
 import java.awt.image.IndexColorModel;
 import java.awt.image.BandedSampleModel;
 import java.awt.image.ComponentSampleModel;
+import java.awt.image.SinglePixelPackedSampleModel;
 
 
 /**
@@ -198,7 +199,7 @@ final class MultiBandsIndexColorModel extends IndexColorModel {
      */
     @Override
     public WritableRaster createCompatibleWritableRaster(final int width, final int height)
{
-        return Raster.createBandedRaster(transferType, width, height, numBands, null);
+        return Raster.createWritableRaster(createCompatibleSampleModel(width, height), null);
     }
 
     /**
@@ -207,7 +208,15 @@ final class MultiBandsIndexColorModel extends IndexColorModel {
      */
     @Override
     public SampleModel createCompatibleSampleModel(final int width, final int height) {
-        return RasterFactory.unique(new BandedSampleModel(transferType, width, height, numBands));
+        final SampleModel sm;
+        if (pixel_bits <= DataBuffer.getDataTypeSize(transferType) / numBands) {
+            final int[] bitMasks = new int[numBands];
+            Arrays.fill(bitMasks, pixel_bits);
+            sm = new SinglePixelPackedSampleModel(transferType, width, height, width, bitMasks);
+        } else {
+            sm = new BandedSampleModel(transferType, width, height, numBands);
+        }
+        return RasterFactory.unique(sm);
     }
 
     /**
@@ -228,7 +237,7 @@ final class MultiBandsIndexColorModel extends IndexColorModel {
      */
     @Override
     public boolean isCompatibleSampleModel(final SampleModel sm) {
-        return (sm instanceof ComponentSampleModel)                  &&
+        return (sm instanceof ComponentSampleModel || sm instanceof SinglePixelPackedSampleModel)
&&
                 sm.getTransferType()                 == transferType &&
                 sm.getNumBands()                     == numBands     &&
                 (1 << sm.getSampleSize(visibleBand)) >= getMapSize();


Mime
View raw message