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
|