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: Remove ImageUtilities.getDataType(…) methods, replaced by getBandType(SampleModel). The previous method was returning the storage type, which can be misleading when the 4 bytes values of an ARGB image are packed in a single 32-bits integer value (java.awt.image.SinglePixelPackedSampleModel and MultiPixelPackedSampleModel). In practice, the type usually wanted by SIS is the type that the image would have if it was using a java.awt.image.BandedSampleModel.
Date Fri, 07 Aug 2020 17:19:32 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 ae004c8  Remove ImageUtilities.getDataType(…) methods, replaced by getBandType(SampleModel).
The previous method was returning the storage type, which can be misleading when the 4 bytes
values of an ARGB image are packed in a single 32-bits integer value (java.awt.image.SinglePixelPackedSampleModel
and MultiPixelPackedSampleModel). In practice, the type usually wanted by SIS is the type
that the image would have if it was using a java.awt.image.BandedSampleModel.
ae004c8 is described below

commit ae004c86391ca96582f1e025b140ce37c6293451
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Aug 7 19:15:43 2020 +0200

    Remove ImageUtilities.getDataType(…) methods, replaced by getBandType(SampleModel).
    The previous method was returning the storage type, which can be misleading when
    the 4 bytes values of an ARGB image are packed in a single 32-bits integer value
    (java.awt.image.SinglePixelPackedSampleModel and MultiPixelPackedSampleModel).
    In practice, the type usually wanted by SIS is the type that the image would have
    if it was using a java.awt.image.BandedSampleModel.
---
 .../java/org/apache/sis/gui/coverage/GridView.java |   5 +-
 .../sis/coverage/grid/BufferedGridCoverage.java    |   4 +-
 .../sis/coverage/grid/ConvertedGridCoverage.java   |  24 ++--
 .../org/apache/sis/coverage/grid/GridCoverage.java |  15 ++-
 .../apache/sis/coverage/grid/GridCoverage2D.java   |   8 +-
 .../sis/coverage/grid/GridCoverageBuilder.java     |   3 +-
 .../sis/coverage/grid/ResampledGridCoverage.java   |   4 +-
 .../apache/sis/image/BandedSampleConverter.java    |  10 +-
 .../main/java/org/apache/sis/image/DataType.java   |  23 ++--
 .../java/org/apache/sis/image/PixelIterator.java   |   4 +-
 .../java/org/apache/sis/image/ResampledImage.java  |   8 +-
 .../main/java/org/apache/sis/image/Transferer.java |  16 +--
 .../java/org/apache/sis/image/Visualization.java   |   2 +-
 .../sis/internal/coverage/j2d/Colorizer.java       |  19 ++-
 .../sis/internal/coverage/j2d/ImageUtilities.java  | 142 ++++++++++++++-------
 .../org/apache/sis/image/ResampledImageTest.java   |  35 +++++
 .../internal/coverage/j2d/ImageUtilitiesTest.java  |  16 +++
 17 files changed, 222 insertions(+), 116 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
index c4c70bf..14dc571 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
@@ -22,7 +22,6 @@ import java.awt.Rectangle;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import java.awt.image.SampleModel;
-import java.awt.image.DataBuffer;
 import javafx.beans.DefaultProperty;
 import javafx.beans.property.DoubleProperty;
 import javafx.beans.property.IntegerProperty;
@@ -46,6 +45,7 @@ import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.Styles;
 import org.apache.sis.gui.map.StatusBar;
 import org.apache.sis.gui.referencing.RecentReferenceSystems;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 
 
 /**
@@ -466,8 +466,7 @@ public class GridView extends Control {
                 if (getBand() >= numBands) {
                     ((BandProperty) bandProperty).setNoCheck(numBands - 1);
                 }
-                final int dataType = sm.getDataType();
-                cellFormat.dataTypeisInteger = (dataType >= DataBuffer.TYPE_BYTE &&
dataType <= DataBuffer.TYPE_INT);
+                cellFormat.dataTypeisInteger = ImageUtilities.isIntegerType(sm);
             }
             cellFormat.configure(image, getBand());
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
index f6b97a2..e2cf30d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
@@ -84,6 +84,8 @@ public class BufferedGridCoverage extends GridCoverage {
      * ({@linkplain java.awt.image.PixelInterleavedSampleModel pixel interleaved} image)
or in different banks
      * ({@linkplain java.awt.image.BandedSampleModel banded} image). This class detects automatically
      * which of those two sample models is used when {@link #render(GridExtent)} is invoked.
+     *
+     * <p>Sample values in this buffer shall not be {@linkplain java.awt.image.SinglePixelPackedSampleModel
packed}.</p>
      */
     protected final DataBuffer data;
 
@@ -181,7 +183,7 @@ public class BufferedGridCoverage extends GridCoverage {
      * Returns the constant identifying the primitive type used for storing sample values.
      */
     @Override
-    final DataType getDataType() {
+    final DataType getBandType() {
         return DataType.forDataBufferType(data.getDataType());
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
index bb8de9e..7e69d13 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
@@ -70,12 +70,12 @@ final class ConvertedGridCoverage extends GridCoverage {
     private final boolean isConverted;
 
     /**
-     * One of enumeration value that describe the sample values type of images
-     * produced by {@link #render(GridExtent)}. Shall not be {@code null}.
+     * One of enumeration value that describe the sample values type in each band
+     * of images produced by {@link #render(GridExtent)}. Shall not be {@code null}.
      *
-     * @see #getDataType()
+     * @see #getBandType()
      */
-    private final DataType dataType;
+    private final DataType bandType;
 
     /**
      * Creates a new coverage with the same grid geometry than the given coverage but converted
sample dimensions.
@@ -92,7 +92,7 @@ final class ConvertedGridCoverage extends GridCoverage {
         this.source      = source;
         this.converters  = converters;
         this.isConverted = isConverted;
-        this.dataType    = getDataType(range, isConverted, source);
+        this.bandType    = getBandType(range, isConverted, source);
     }
 
     /**
@@ -152,13 +152,17 @@ final class ConvertedGridCoverage extends GridCoverage {
 
     /**
      * Returns the data type for range of values of given sample dimensions.
+     * This data type applies to each band, not to a packed sample model
+     * (e.g. we assume no packing of 4 byte values in a single 32-bits integer).
      *
      * @param  targets    the sample dimensions for which to get the data type.
      * @param  converted  whether the image will hold converted or packed values.
      * @param  source     if the type can not be determined, coverage from which to inherit
the type as a fallback.
      * @return the data type (never null).
+     *
+     * @see GridCoverage#getBandType()
      */
-    static DataType getDataType(final List<SampleDimension> targets, final boolean
converted, final GridCoverage source) {
+    static DataType getBandType(final List<SampleDimension> targets, final boolean
converted, final GridCoverage source) {
         NumberRange<?> union = null;
         boolean allowsNaN = false;
         for (final SampleDimension dimension : targets) {
@@ -174,7 +178,7 @@ final class ConvertedGridCoverage extends GridCoverage {
             if (!allowsNaN) allowsNaN = dimension.allowsNaN();
         }
         if (union == null) {
-            return source.getDataType();
+            return source.getBandType();
         }
         DataType type = DataType.forRange(union, !converted);
         if (allowsNaN) {
@@ -187,8 +191,8 @@ final class ConvertedGridCoverage extends GridCoverage {
      * Returns the constant identifying the primitive type used for storing sample values.
      */
     @Override
-    final DataType getDataType() {
-        return dataType;
+    final DataType getBandType() {
+        return bandType;
     }
 
     /**
@@ -267,7 +271,7 @@ final class ConvertedGridCoverage extends GridCoverage {
          * That image should never be null. But if an implementation wants to do so, respect
that.
          */
         if (image != null) {
-            image = convert(image, dataType, converters);
+            image = convert(image, bandType, converters);
         }
         return image;
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 2336a16..5b5e9d6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -183,9 +183,12 @@ public abstract class GridCoverage {
     }
 
     /**
-     * Returns the data type identifying the primitive type used for storing sample values.
+     * Returns the data type identifying the primitive type used for storing sample values
in each band.
+     * We assume no packed sample model (e.g. no packing of 4 byte ARGB values in a single
32-bits integer).
+     * If the sample model is packed, the value returned by this method should be as if the
image has been
+     * converted to a banded sample model.
      */
-    DataType getDataType() {
+    DataType getBandType() {
         return DataType.DOUBLE;     // Must conservative value, should be overridden by subclasses.
     }
 
@@ -247,20 +250,20 @@ public abstract class GridCoverage {
      * Creates a new image of the given data type which will compute values using the given
converters.
      *
      * @param  source      the image for which to convert sample values.
-     * @param  dataType    the type of the image resulting from conversion of given image.
+     * @param  bandType    the type of data in the bands resulting from conversion of given
image.
      * @param  converters  the transfer functions to apply on each band of the source image.
      * @return the image which compute converted values from the given source.
      */
-    final RenderedImage convert(final RenderedImage source, final DataType dataType, final
MathTransform1D[] converters) {
+    final RenderedImage convert(final RenderedImage source, final DataType bandType, final
MathTransform1D[] converters) {
         final int visibleBand = Math.max(0, ImageUtilities.getVisibleBand(source));
         final Colorizer colorizer = new Colorizer(Colorizer.GRAYSCALE);
         final ColorModel colors;
         if (colorizer.initialize(sampleDimensions[visibleBand]) || colorizer.initialize(source.getColorModel()))
{
-            colors = colorizer.createColorModel(dataType.ordinal(), sampleDimensions.length,
visibleBand);
+            colors = colorizer.createColorModel(bandType.ordinal(), sampleDimensions.length,
visibleBand);
         } else {
             colors = Colorizer.NULL_COLOR_MODEL;
         }
-        return Lazy.PROCESSOR.convert(source, getRanges(), converters, dataType, colors);
+        return Lazy.PROCESSOR.convert(source, getRanges(), converters, bandType, colors);
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index 1ef38c4..7f14bd1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -148,8 +148,8 @@ public class GridCoverage2D extends GridCoverage {
                            final MathTransform1D[] converters, final boolean isConverted)
     {
         super(source.gridGeometry, range);
-        final DataType dataType = ConvertedGridCoverage.getDataType(range, isConverted, source);
-        data           = convert(source.data, dataType, converters);
+        final DataType bandType = ConvertedGridCoverage.getBandType(range, isConverted, source);
+        data           = convert(source.data, bandType, converters);
         gridToImageX   = source.gridToImageX;
         gridToImageY   = source.gridToImageY;
         xDimension     = source.xDimension;
@@ -444,8 +444,8 @@ public class GridCoverage2D extends GridCoverage {
      * Returns the constant identifying the primitive type used for storing sample values.
      */
     @Override
-    final DataType getDataType() {
-        return DataType.of(data);
+    final DataType getBandType() {
+        return DataType.forBands(data);
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
index bef443c..216837f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
@@ -35,6 +35,7 @@ import org.opengis.geometry.Envelope;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.internal.coverage.j2d.Colorizer;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.coverage.j2d.TiledImage;
 import org.apache.sis.internal.coverage.j2d.WritableTiledImage;
 import org.apache.sis.internal.feature.Resources;
@@ -467,7 +468,7 @@ public class GridCoverageBuilder {
                 final Colorizer colorizer = new Colorizer(Colorizer.GRAYSCALE);
                 final ColorModel colors;
                 if (colorizer.initialize(bands.get(visibleBand)) || colorizer.initialize(sm,
visibleBand)) {
-                    colors = colorizer.createColorModel(sm.getDataType(), bands.size(), visibleBand);
+                    colors = colorizer.createColorModel(ImageUtilities.getBandType(sm), bands.size(),
visibleBand);
                 } else {
                     colors = Colorizer.NULL_COLOR_MODEL;
                 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index a06eb46..a99bbab 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -536,8 +536,8 @@ final class ResampledGridCoverage extends GridCoverage {
      * Returns the constant identifying the primitive type used for storing sample values.
      */
     @Override
-    final DataType getDataType() {
-        return source.getDataType();
+    final DataType getBandType() {
+        return source.getBandType();
     }
 
     /**
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 d95cc36..13beb7b 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
@@ -24,6 +24,7 @@ import java.awt.image.WritableRenderedImage;
 import java.awt.image.BandedSampleModel;
 import java.awt.image.ColorModel;
 import java.awt.image.DataBuffer;
+import java.awt.image.SampleModel;
 import java.awt.image.TileObserver;
 import java.lang.reflect.Array;
 import org.opengis.referencing.operation.MathTransform1D;
@@ -132,10 +133,11 @@ class BandedSampleConverter extends ComputedImage {
                 }
             }
             if (!Double.isFinite(middle)) {
-                switch (ImageUtilities.getDataType(source)) {
-                    default:                     middle = 0;      break;
-                    case DataBuffer.TYPE_BYTE:   middle = 0x80;   break;
-                    case DataBuffer.TYPE_USHORT: middle = 0x8000; break;
+                final SampleModel sm = source.getSampleModel();
+                if (ImageUtilities.isUnsignedType(sm)) {
+                    middle = Math.scalb(0.5, sm.getSampleSize(i));
+                } else {
+                    middle = 0;
                 }
             }
             /*
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/DataType.java b/core/sis-feature/src/main/java/org/apache/sis/image/DataType.java
index 1ef11ae..a298022 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/DataType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/DataType.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.image;
 
-import java.awt.image.Raster;
 import java.awt.image.DataBuffer;
 import java.awt.image.RenderedImage;
 import java.awt.image.RasterFormatException;
@@ -88,25 +87,19 @@ public enum DataType {
     }
 
     /**
-     * Returns the data type of the given image.
+     * Returns the data type of the bands in the given image. This is often the
+     * {@linkplain java.awt.image.SampleModel#getDataType() storage data type}, but not necessarily.
+     * For example if an ARGB image uses a storage mode where the sample values in the 4
bands are
+     * {@linkplain java.awt.image.SinglePixelPackedSampleModel packed in a single 32-bits
integer},
+     * then this method returns {@link #BYTE} (the type of a single band), not {@link #INT}
+     * (the type of a whole pixel).
      *
      * @param  image  the image for which to get the type.
      * @return type of the given image (never {@code null}).
      * @throws RasterFormatException if the image does not use a recognized data type.
      */
-    public static DataType of(final RenderedImage image) {
-        return forDataBufferType(ImageUtilities.getDataType(image));
-    }
-
-    /**
-     * Returns the data type of the given raster.
-     *
-     * @param  raster  the raster for which to get the type.
-     * @return type of the given raster (never {@code null}).
-     * @throws RasterFormatException if the raster does not use a recognized data type.
-     */
-    public static DataType of(final Raster raster) {
-        return forDataBufferType(ImageUtilities.getDataType(raster));
+    public static DataType forBands(final RenderedImage image) {
+        return forDataBufferType(ImageUtilities.getBandType(image.getSampleModel()));
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
index 7940213..cf2e8c1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
@@ -481,8 +481,8 @@ public abstract class PixelIterator {
                     final int size = model.getSampleSize(bandToDefine);
                     long minimum = 0;
                     long maximum = Numerics.bitmask(size) - 1;
-                    if (dataType >= DataBuffer.TYPE_SHORT) {
-                        maximum >>>= 1;                         // Convert unsigned
range to signed range.
+                    if (!ImageUtilities.isUnsignedType(model)) {
+                        maximum >>>= 1;                                 // Convert
unsigned range to signed range.
                         minimum = ~maximum;
                     }
                     final NumberRange<?> range = (dataType == DataBuffer.TYPE_BYTE
|| dataType == DataBuffer.TYPE_SHORT)
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 5cb31b1..d00ceb1 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
@@ -264,7 +264,7 @@ public class ResampledImage extends ComputedImage {
          * the target data type is an integer type or not. Null elements default to zero.
          */
         final int numBands = ImageUtilities.getNumBands(source);
-        if (ImageUtilities.isIntegerType(sampleModel.getDataType())) {
+        if (ImageUtilities.isIntegerType(sampleModel)) {
             final int[] fill = new int[numBands];
             if (fillValues != null) {
                 for (int i=Math.min(fillValues.length, numBands); --i >= 0;) {
@@ -378,7 +378,7 @@ public class ResampledImage extends ComputedImage {
      * Returns {@code true} if this image can not have mask.
      */
     boolean hasNoMask() {
-        return ImageUtilities.isIntegerType(sampleModel.getDataType());
+        return fillValues instanceof int[];
     }
 
     /**
@@ -601,7 +601,7 @@ public class ResampledImage extends ComputedImage {
          * 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));
+                    ImageUtilities.isLosslessConversion(sampleModel, tile.getSampleModel());
         /*
          * 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(…)`
@@ -629,7 +629,7 @@ public class ResampledImage extends ComputedImage {
                 for (int b=0; b<numBands; b++) {
                     maxValues[b] = Numerics.bitmask(sm.getSampleSize(b)) - 1;
                 }
-                if (!ImageUtilities.isUnsignedType(sm.getDataType())) {
+                if (!ImageUtilities.isUnsignedType(sm)) {
                     for (int b=0; b<numBands; b++) {
                         minValues[b] = ~(maxValues[b] >>>= 1);      // Convert unsigned
type to signed type range.
                     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Transferer.java b/core/sis-feature/src/main/java/org/apache/sis/image/Transferer.java
index 6f196c2..4c506a6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Transferer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Transferer.java
@@ -525,7 +525,7 @@ abstract class Transferer {
      * @return object to use for applying the operation.
      */
     static Transferer create(final Raster source, final WritableRaster target, final Rectangle
aoi) {
-        switch (ImageUtilities.getDataType(target)) {
+        switch (ImageUtilities.getBandType(target.getSampleModel())) {
             case DataBuffer.TYPE_DOUBLE: {
                 if (isDirect(target, aoi)) {
                     return new DoubleToDirect(source, target, aoi);
@@ -533,7 +533,7 @@ abstract class Transferer {
                 break;
             }
             case DataBuffer.TYPE_FLOAT: {
-                switch (ImageUtilities.getDataType(source)) {
+                switch (ImageUtilities.getBandType(source.getSampleModel())) {
                     case DataBuffer.TYPE_BYTE:
                     case DataBuffer.TYPE_SHORT:
                     case DataBuffer.TYPE_USHORT:        // TODO: consider using IntegerToDirect
here.
@@ -551,10 +551,10 @@ abstract class Transferer {
                 }
                 break;
             }
-            case DataBuffer.TYPE_INT:    return isFloat(source) ? new FloatToInteger(source,
target, aoi) : new DoubleToInteger(source, target, aoi);
-            case DataBuffer.TYPE_USHORT: return isFloat(source) ? new FloatToUShort (source,
target, aoi) : new DoubleToUShort (source, target, aoi);
-            case DataBuffer.TYPE_SHORT:  return isFloat(source) ? new FloatToShort  (source,
target, aoi) : new DoubleToShort  (source, target, aoi);
-            case DataBuffer.TYPE_BYTE:   return isFloat(source) ? new FloatToByte   (source,
target, aoi) : new DoubleToByte   (source, target, aoi);
+            case DataBuffer.TYPE_INT:    return singlePrecision(source) ? new FloatToInteger(source,
target, aoi) : new DoubleToInteger(source, target, aoi);
+            case DataBuffer.TYPE_USHORT: return singlePrecision(source) ? new FloatToUShort
(source, target, aoi) : new DoubleToUShort (source, target, aoi);
+            case DataBuffer.TYPE_SHORT:  return singlePrecision(source) ? new FloatToShort
 (source, target, aoi) : new DoubleToShort  (source, target, aoi);
+            case DataBuffer.TYPE_BYTE:   return singlePrecision(source) ? new FloatToByte
  (source, target, aoi) : new DoubleToByte   (source, target, aoi);
         }
         /*
          * Most conservative fallback, also used for any unknown type.
@@ -568,8 +568,8 @@ abstract class Transferer {
      * method also returns {@code false} for {@link DataBuffer#TYPE_INT} because conversion
of 32 bits integer
      * to the {@code float} type may lost precision digits.
      */
-    private static boolean isFloat(final Raster source) {
-        switch (ImageUtilities.getDataType(source)) {
+    private static boolean singlePrecision(final Raster source) {
+        switch (ImageUtilities.getBandType(source.getSampleModel())) {
             case DataBuffer.TYPE_BYTE:
             case DataBuffer.TYPE_USHORT:
             case DataBuffer.TYPE_SHORT:
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 caadf7d..3852859 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
@@ -297,7 +297,7 @@ final class Visualization extends ResampledImage {
          * If the source image uses unsigned integer types and there is no resampling operation,
we can
          * update the color model without changing sample values. This is much cheaper and
as accurate.
          */
-        final int dataType = ImageUtilities.getDataType(source);
+        final int dataType = source.getSampleModel().getDataType();
         if (dataType == DataBuffer.TYPE_BYTE || dataType == DataBuffer.TYPE_USHORT) {
             if (toSource != null && !toSource.isIdentity()) {
                 source = processor.resample(source, bounds, toSource);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
index 6caa6d0..da2eacb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java
@@ -214,18 +214,15 @@ public final class Colorizer {
      */
     public boolean initialize(final SampleModel source, final int band) {
         checkInitializationStatus(false);
-        if (source != null) {
-            final int dataType = source.getDataType();
-            if (ImageUtilities.isIntegerType(dataType)) {
-                long minimum = 0;
-                long maximum = Numerics.bitmask(source.getSampleSize(band)) - 1;
-                if (dataType != DataBuffer.TYPE_BYTE && dataType != DataBuffer.TYPE_USHORT)
{
-                    maximum >>>= 1;
-                    minimum = ~maximum;
-                }
-                initialize(minimum, maximum);
-                return true;
+        if (ImageUtilities.isIntegerType(source)) {
+            long minimum = 0;
+            long maximum = Numerics.bitmask(source.getSampleSize(band)) - 1;
+            if (!ImageUtilities.isUnsignedType(source)) {
+                maximum >>>= 1;
+                minimum = ~maximum;
             }
+            initialize(minimum, maximum);
+            return true;
         }
         return false;
     }
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 41af223..d8cda2c 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
@@ -29,6 +29,7 @@ import java.awt.image.Raster;
 import java.awt.image.RasterFormatException;
 import java.awt.image.SampleModel;
 import java.awt.image.SinglePixelPackedSampleModel;
+import java.awt.image.MultiPixelPackedSampleModel;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Numerics;
@@ -159,39 +160,44 @@ public final class ImageUtilities extends Static {
     }
 
     /**
-     * Returns the data type of the given image.
+     * Returns the data type of bands in rasters that use the given sample model.
+     * If each band is stored in its own {@link DataBuffer} element, then this method returns
the same value
+     * as {@link SampleModel#getDataType()}. But if multiple sample values are packed in
a single data element
+     * ({@link SinglePixelPackedSampleModel} or {@link MultiPixelPackedSampleModel}), then
this method returns
+     * a smaller data type. As a general rule, this method returns the smallest data type
capable to store all
+     * sample values with a {@link java.awt.image.BandedSampleModel}.
      *
-     * @param  image  the image for which to get the data type, or {@code null}.
+     * @param  sm  the sample model for which to get the band type, or {@code null}.
      * @return the data type, or {@link DataBuffer#TYPE_UNDEFINED} if unknown.
      *
-     * @see #getDataType(Raster)
      * @see #isIntegerType(int)
+     * @see #isUnsignedType(SampleModel)
      */
-    public static int getDataType(final RenderedImage image) {
-        if (image != null) {
-            final SampleModel sm = image.getSampleModel();
-            if (sm != null) return sm.getDataType();            // Should never be null,
but we are paranoiac.
+    public static int getBandType(final SampleModel sm) {
+        if (sm == null) {
+            return DataBuffer.TYPE_UNDEFINED;
         }
-        return DataBuffer.TYPE_UNDEFINED;
-    }
-
-    /**
-     * Returns the data type of the given raster.
-     *
-     * @param  raster  the raster for which to get the data type, or {@code null}.
-     * @return the data type, or {@link DataBuffer#TYPE_UNDEFINED} if unknown.
-     *
-     * @see #getDataType(RenderedImage)
-     * @see #isIntegerType(int)
-     */
-    public static int getDataType(final Raster raster) {
-        if (raster != null) {
-            final DataBuffer buffer = raster.getDataBuffer();
-            if (buffer != null) {                               // Should never be null,
but we are paranoiac.
-                return buffer.getDataType();
+        final int type = sm.getDataType();
+        if (!isIntegerType(type)) {
+            return type;
+        }
+        final int maxBits = Math.min(DataBuffer.getDataTypeSize(type), Short.SIZE + 1);
+        int numBits = 0;
+        for (int i=sm.getNumBands(); --i >= 0;) {
+            final int n = sm.getSampleSize(i);
+            if (n > numBits) {
+                if (n >= maxBits) {
+                    return type;
+                }
+                numBits = n;
             }
         }
-        return DataBuffer.TYPE_UNDEFINED;
+        final boolean isUnsignedType = (type <= DataBuffer.TYPE_USHORT)
+                        || (sm instanceof SinglePixelPackedSampleModel)
+                        || (sm instanceof MultiPixelPackedSampleModel);
+
+        return isUnsignedType ? (numBits <= Byte.SIZE ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT)
+                              : DataBuffer.TYPE_SHORT;
     }
 
     /**
@@ -378,41 +384,89 @@ public final class ImageUtilities extends Static {
      *
      * @param  dataType  one of {@link DataBuffer} constants.
      * @return whether the given constant is for an integer type.
-     *
-     * @see #getDataType(RenderedImage)
-     * @see #getDataType(Raster)
      */
     public static boolean isIntegerType(final int dataType) {
         return dataType >= DataBuffer.TYPE_BYTE && dataType <= DataBuffer.TYPE_INT;
     }
 
     /**
-     * Returns {@code true} if the given data buffer type is an unsigned integer type.
+     * Returns {@code true} if the given sample model use an integer type.
+     * Returns {@code false} if the type is a floating point type or in case
+     * of doubt (e.g. for {@link DataBuffer#TYPE_UNDEFINED}).
+     *
+     * @param  sm  the sample model, or {@code null}.
+     * @return whether the given sample model is for integer values.
+     */
+    public static boolean isIntegerType(final SampleModel sm) {
+        return (sm != null) && isIntegerType(sm.getDataType());
+    }
+
+    /**
+     * Returns {@code true} if the type of sample values is an unsigned integer type.
      * Returns {@code false} if the type is a floating point type or in case of doubt
      * (e.g. for {@link DataBuffer#TYPE_UNDEFINED}).
      *
-     * @param  dataType  one of {@link DataBuffer} constants.
-     * @return whether the given constant is for an unsigned integer type.
-     *
-     * @see #getDataType(RenderedImage)
-     * @see #getDataType(Raster)
+     * @param  sm  the sample model, or {@code null}.
+     * @return whether the given sample model provides unsigned sample values.
      */
-    public static boolean isUnsignedType(final int dataType) {
-        return dataType >= DataBuffer.TYPE_BYTE && dataType <= DataBuffer.TYPE_USHORT;
+    public static boolean isUnsignedType(final SampleModel sm) {
+        if (sm != null) {
+            final int dataType = sm.getDataType();
+            if (dataType >= DataBuffer.TYPE_BYTE) {
+                if (dataType <= DataBuffer.TYPE_USHORT) return true;
+                if (dataType <= DataBuffer.TYPE_INT) {
+                    /*
+                     * Typical case: 4 bands (ARGB) stored in a single data element of type
`int`.
+                     * The javadoc of those classes explain how to unpack the sample values,
+                     * and the result is always unsigned.
+                     */
+                    return (sm instanceof SinglePixelPackedSampleModel) ||
+                           (sm instanceof MultiPixelPackedSampleModel);
+                }
+            }
+        }
+        return false;
     }
 
     /**
-     * Returns whether {@code sourceType} can be converted to {@code targetType} without
data lost.
+     * Returns whether samples values stored using {@code source} model can be converted
to {@code target} model
+     * without data lost. This method verifies the number of bands and the size of data in
each band.
      *
-     * @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.
+     * @param  source  model of sample values to convert.
+     * @param  target  model of converted sample values.
+     * @return whether the conversion from source model to target model 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.
+    public static boolean isLosslessConversion(final SampleModel source, final SampleModel
target) {
+        if (source != target) {
+            final int numBands = source.getNumBands();
+            if (target.getNumBands() < numBands) {
+                return false;                           // Conversion would lost some bands.
+            }
+            final boolean sourceIsInteger = isIntegerType(source.getDataType());
+            final boolean targetIsInteger = isIntegerType(target.getDataType());
+            if (targetIsInteger && !sourceIsInteger) {
+                return false;                           // Conversion from floating point
type to integer type.
+            }
+            boolean hasSameSize = false;
+            for (int i=0; i<numBands; i++) {
+                final double d = target.getSampleSize(i) - source.getSampleSize(i);
+                hasSameSize |= (d == 0);
+                if (d < 0) {
+                    return false;
+                }
+            }
+            if (hasSameSize) {
+                /*
+                 * Need more checks if at least one band uses the same amount of bits:
+                 *   - Conversion from `int` to `float` can loose significant digits.
+                 *   - Conversion from signed short to unsigned short (or conversely) can
change values.
+                 */
+                if (sourceIsInteger != targetIsInteger || isUnsignedType(source) != isUnsignedType(target))
{
+                    return false;
+                }
+            }
         }
-        return sourceType >= DataBuffer.TYPE_BYTE && targetType <= DataBuffer.TYPE_DOUBLE
&& targetType >= sourceType;
+        return true;
     }
 
     /**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
index 7722097..8550b68 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
@@ -19,9 +19,11 @@ package org.apache.sis.image;
 import java.util.Arrays;
 import java.util.Random;
 import java.nio.FloatBuffer;
+import java.awt.Color;
 import java.awt.Point;
 import java.awt.Dimension;
 import java.awt.Rectangle;
+import java.awt.Graphics2D;
 import java.awt.image.DataBuffer;
 import java.awt.image.DataBufferFloat;
 import java.awt.image.BufferedImage;
@@ -111,6 +113,14 @@ public final strictfp class ResampledImageTest extends TestCase {
         final AffineTransform tr = AffineTransform.getTranslateInstance(source.getMinX(),
source.getMinY());
         tr.scale(0.5, 0.5);
         tr.translate(-bounds.x, -bounds.y);
+        resample(bounds, tr);
+    }
+
+    /**
+     * Creates the resampled image. The source image shall be specified in {@link #source}
field.
+     * The interpolation result will be stored in {@link #target}.
+     */
+    private void resample(final Rectangle bounds, final AffineTransform tr) {
         final ImageProcessor processor = new ImageProcessor();
         processor.setInterpolation(interpolation);
         target = (ResampledImage) processor.resample(source, bounds, new AffineTransform2D(tr));
@@ -320,4 +330,29 @@ public final strictfp class ResampledImageTest extends TestCase {
             1.111111111f, 1f, 0.888888888f, 0.777777777f, 0.666666666f, 0.777777777f, 0.888888888f,
1f, 1.111111111f
         }, resampleSimpleImage(3), (float) STRICT);
     }
+
+    /**
+     * Tests resampling of a 4 banded image. This simple test uses a solid color on the whole
image,
+     * but with a different value in each band. Interpolations should result in the same
values in all bands.
+     */
+    @Test
+    public void testMultiBands() {
+        final BufferedImage image = new BufferedImage(6, 3, BufferedImage.TYPE_INT_ARGB);
+        final Graphics2D g = image.createGraphics();
+        g.setColor(Color.ORANGE);
+        g.fillRect(0, 0, image.getWidth(), image.getHeight());
+        g.dispose();
+
+        source = image;
+        interpolation = Interpolation.BILINEAR;
+        resample(new Rectangle(3, 2), new AffineTransform(0.5, 0, 0, 2/3d, 0, 0));
+
+        final PixelIterator it = PixelIterator.create(target);
+        final double[] expected = {255, 200, 0, 255};           // Alpha, Blue, Green, Red.
+        double[] actual = null;
+        while (it.next()) {
+            actual = it.getPixel(actual);
+            assertArrayEquals(actual, expected, STRICT);
+        }
+    }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
index a7e8d28..91cecfc 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
@@ -24,6 +24,7 @@ import java.awt.image.RenderedImage;
 import java.awt.image.BufferedImage;
 import java.awt.image.SampleModel;
 import java.awt.image.BandedSampleModel;
+import java.awt.image.SinglePixelPackedSampleModel;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
@@ -73,6 +74,21 @@ public final strictfp class ImageUtilitiesTest extends TestCase {
     }
 
     /**
+     * Tests {@link ImageUtilities#getBandType(SampleModel)}.
+     */
+    @Test
+    public void testGetBandType() {
+        SampleModel sm = new BandedSampleModel(DataBuffer.TYPE_INT, 1, 1, 3);
+        assertEquals(DataBuffer.TYPE_INT, ImageUtilities.getBandType(sm));
+
+        sm = new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, 1, 1, new int[] {0x7F0000,
0x00FF00, 0x00007F});
+        assertEquals(DataBuffer.TYPE_BYTE, ImageUtilities.getBandType(sm));
+
+        sm = new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, 1, 1, new int[] {0x7F0000,
0x00FF80, 0x00007F});
+        assertEquals(DataBuffer.TYPE_USHORT, ImageUtilities.getBandType(sm));
+    }
+
+    /**
      * Tests {@link ImageUtilities#getDataTypeName(SampleModel)}.
      */
     @Test


Mime
View raw message