You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2020/07/16 16:08:45 UTC
[sis] branch geoapi-4.0 updated: Revisit the API of methods related to ColorModel creations. This is in preparation for addition of an ImageProcessor.toIndexedColors(…) method.
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 2775b27 Revisit the API of methods related to ColorModel creations. This is in preparation for addition of an ImageProcessor.toIndexedColors(…) method.
2775b27 is described below
commit 2775b27f4fc4c739c77560cc434fe886945a1c3d
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Jul 16 10:39:15 2020 +0200
Revisit the API of methods related to ColorModel creations.
This is in preparation for addition of an ImageProcessor.toIndexedColors(…) method.
---
.../apache/sis/gui/coverage/CoverageCanvas.java | 20 +--
.../org/apache/sis/gui/coverage/RenderingData.java | 14 +-
.../sis/coverage/grid/BufferedGridCoverage.java | 6 +-
.../sis/coverage/grid/ConvertedGridCoverage.java | 6 +-
.../org/apache/sis/coverage/grid/GridCoverage.java | 35 ++--
.../apache/sis/coverage/grid/GridCoverage2D.java | 16 +-
.../sis/coverage/grid/GridCoverageBuilder.java | 7 +-
.../apache/sis/coverage/grid/ImageRenderer.java | 8 +-
.../java/org/apache/sis/image/ImageProcessor.java | 108 ++++++------
.../java/org/apache/sis/image/RecoloredImage.java | 183 +++++++++++----------
.../internal/coverage/j2d/ColorModelFactory.java | 75 +++++----
.../coverage/grid/ConvertedGridCoverageTest.java | 2 +-
12 files changed, 245 insertions(+), 235 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index 732f205..8a0f55b 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -447,9 +447,9 @@ public class CoverageCanvas extends MapCanvasAWT {
private RenderedImage resampledImage;
/**
- * The resampled image after color ramp stretching or other operation applied.
+ * The resampled image after color ramp stretching and/or index color model applied.
*/
- private RenderedImage filteredImage;
+ private RenderedImage recoloredImage;
/**
* The filtered image with tiles computed in advance. The set of prefetched
@@ -486,7 +486,7 @@ public class CoverageCanvas extends MapCanvasAWT {
displayBounds = canvas.getDisplayBounds();
if (data.validateCRS(objectiveCRS)) {
resampledImage = canvas.resampledImages.get(Stretching.NONE);
- filteredImage = canvas.resampledImages.get(data.selectedDerivative);
+ recoloredImage = canvas.resampledImages.get(data.selectedDerivative);
}
}
@@ -523,14 +523,14 @@ public class CoverageCanvas extends MapCanvasAWT {
& ~(AffineTransform.TYPE_IDENTITY | AffineTransform.TYPE_TRANSLATION)) == 0;
}
if (!isResampled) {
- filteredImage = null;
+ recoloredImage = null;
resampledImage = data.resample(objectiveCRS, objectiveToDisplay);
resampledToDisplay = data.getTransform(objectiveToDisplay);
}
- if (filteredImage == null) {
- filteredImage = data.filter(resampledImage, displayBounds);
+ if (recoloredImage == null) {
+ recoloredImage = data.recolor(resampledImage);
}
- prefetchedImage = data.prefetch(filteredImage, resampledToDisplay, displayBounds);
+ prefetchedImage = data.prefetch(recoloredImage, resampledToDisplay, displayBounds);
} finally {
LogHandler.loadingStop(id);
}
@@ -571,15 +571,15 @@ public class CoverageCanvas extends MapCanvasAWT {
resampledImages.clear();
resampledImages.put(Stretching.NONE, newValue);
}
- resampledImages.put(data.selectedDerivative, worker.filteredImage);
+ resampledImages.put(data.selectedDerivative, worker.recoloredImage);
/*
* Notify the "Image properties" tab that the image changed.
*/
if (imageProperty != null) {
- imageProperty.setImage(worker.filteredImage, worker.getVisibleImageBounds());
+ imageProperty.setImage(worker.recoloredImage, worker.getVisibleImageBounds());
}
if (statusBar != null) {
- final Object value = worker.filteredImage.getProperty(PlanarImage.POSITIONAL_ACCURACY_KEY);
+ final Object value = worker.recoloredImage.getProperty(PlanarImage.POSITIONAL_ACCURACY_KEY);
Quantity<Length> accuracy = null;
if (value instanceof Quantity<?>[]) {
for (final Quantity<?> q : (Quantity<?>[]) value) {
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
index 100d066..cb41d64 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
@@ -24,7 +24,6 @@ import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
-import java.awt.geom.Rectangle2D;
import org.opengis.util.FactoryException;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.CoordinateOperation;
@@ -258,16 +257,15 @@ final class RenderingData implements Cloneable {
* In current implementation, the only operations are stretching the color ramp.
*
* @param resampledImage the image computed by {@link #resample(CoordinateReferenceSystem, LinearTransform)}.
- * @param displayBounds size and location of the display device, in pixel units.
* @return image with operation applied and color ramp stretched. May be the same instance than given image.
*/
- final RenderedImage filter(RenderedImage resampledImage, final Rectangle2D displayBounds) {
+ final RenderedImage recolor(final RenderedImage resampledImage) {
if (selectedDerivative != Stretching.NONE) {
final Map<String,Object> modifiers = new HashMap<>(4);
/*
* Select the original image as the source of statistics. It saves computation time (no need
* to recompute the statistics when the projection is changed) and provides more stable visual
- * output (color ramp computed from same standard deviation in "automatic" mode).
+ * output when standard deviations are used for configuring the color ramp.
*/
if (statistics == null) {
statistics = processor.getStatistics(data, null);
@@ -285,20 +283,20 @@ final class RenderingData implements Cloneable {
* Computes immediately, possibly using many threads, the tiles that are going to be displayed.
* The returned instance should be used only for current rendering event; it should not be cached.
*
- * @param filteredImage the image computed by {@link #filter(RenderedImage, Rectangle2D)}.
+ * @param recoloredImage the image computed by {@link #recolor(RenderedImage)}.
* @param resampledToDisplay the transform computed by {@link #getTransform(LinearTransform)}.
* @param displayBounds size and location of the display device, in pixel units.
* @return a temporary image with tiles intersecting the display region already computed.
*/
- final RenderedImage prefetch(final RenderedImage filteredImage, final AffineTransform resampledToDisplay,
+ final RenderedImage prefetch(final RenderedImage recoloredImage, final AffineTransform resampledToDisplay,
final Envelope2D displayBounds)
{
try {
- return processor.prefetch(filteredImage, (Rectangle) AffineTransforms2D.transform(
+ return processor.prefetch(recoloredImage, (Rectangle) AffineTransforms2D.transform(
resampledToDisplay.createInverse(), displayBounds, new Rectangle()));
} catch (NoninvertibleTransformException e) {
recoverableException(e);
- return filteredImage;
+ return recoloredImage;
}
}
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 8cb915a..e21cedb 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
@@ -16,7 +16,7 @@
*/
package org.apache.sis.coverage.grid;
-import java.util.Collection;
+import java.util.List;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferDouble;
@@ -106,7 +106,7 @@ public class BufferedGridCoverage extends GridCoverage {
* @throws IllegalGridGeometryException if the grid extent is larger than the data buffer capacity.
* @throws ArithmeticException if the number of cells is larger than 64 bits integer capacity.
*/
- public BufferedGridCoverage(final GridGeometry domain, final Collection<? extends SampleDimension> range, final DataBuffer data) {
+ public BufferedGridCoverage(final GridGeometry domain, final List<? extends SampleDimension> range, final DataBuffer data) {
super(domain, range);
this.data = data;
ArgumentChecks.ensureNonNull("data", data);
@@ -147,7 +147,7 @@ public class BufferedGridCoverage extends GridCoverage {
* @param dataType one of {@code DataBuffer.TYPE_*} constants, the native data type used to store the coverage values.
* @throws ArithmeticException if the grid size is too large.
*/
- public BufferedGridCoverage(final GridGeometry grid, final Collection<? extends SampleDimension> bands, final int dataType) {
+ public BufferedGridCoverage(final GridGeometry grid, final List<? extends SampleDimension> bands, final int dataType) {
super(grid, bands);
final int n = Math.toIntExact(getSampleCount(grid.getExtent(), bands.size()));
switch (dataType) {
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 d1c0595..b846cae 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
@@ -20,7 +20,6 @@ import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Optional;
-import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import org.opengis.geometry.DirectPosition;
@@ -30,8 +29,6 @@ import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.coverage.SampleDimension;
-import org.apache.sis.internal.coverage.j2d.BandedSampleConverter;
-import org.apache.sis.internal.coverage.j2d.ImageUtilities;
import org.apache.sis.internal.coverage.j2d.RasterFactory;
import org.apache.sis.measure.NumberRange;
@@ -269,8 +266,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) {
- final ColorModel colorModel = createColorModel(Math.max(0, ImageUtilities.getVisibleBand(image)), dataType);
- image = BandedSampleConverter.create(image, null, dataType, colorModel, getRanges(), converters);
+ return convert(image, dataType, 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 c5bdc33..a625761 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
@@ -17,7 +17,6 @@
package org.apache.sis.coverage.grid;
import java.util.List;
-import java.util.Collection;
import java.util.Locale;
import java.util.Optional;
import java.awt.image.ColorModel;
@@ -26,12 +25,15 @@ import java.awt.image.RenderedImage;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+import org.apache.sis.internal.coverage.j2d.BandedSampleConverter;
import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
import org.apache.sis.util.collection.DefaultTreeTable;
import org.apache.sis.util.collection.TableColumn;
import org.apache.sis.util.collection.TreeTable;
@@ -91,7 +93,7 @@ public abstract class GridCoverage {
* @throws NullPointerException if an argument is {@code null} or if the list contains a null element.
* @throws IllegalArgumentException if the {@code range} list is empty.
*/
- protected GridCoverage(final GridGeometry domain, final Collection<? extends SampleDimension> ranges) {
+ protected GridCoverage(final GridGeometry domain, final List<? extends SampleDimension> ranges) {
ArgumentChecks.ensureNonNull ("domain", domain);
ArgumentChecks.ensureNonEmpty("ranges", ranges);
gridGeometry = domain;
@@ -157,7 +159,7 @@ public abstract class GridCoverage {
/**
* Returns the range of values in each sample dimension, or {@code null} if none.
*/
- final NumberRange<?>[] getRanges() {
+ private NumberRange<?>[] getRanges() {
NumberRange<?>[] ranges = null;
for (int i=0; i<sampleDimensions.length; i++) {
final Optional<NumberRange<?>> r = sampleDimensions[i].getSampleRange();
@@ -180,17 +182,6 @@ public abstract class GridCoverage {
}
/**
- * Creates a color model for the expected range of sample values.
- *
- * @param visibleBand the band to be made visible (usually 0). All other bands (if any) will be ignored.
- * @param dataType the color model type as one {@link java.awt.image.DataBuffer} constants.
- * @return proposed color model, or {@code null} if none.
- */
- final ColorModel createColorModel(final int visibleBand, final int dataType) {
- return ColorModelFactory.createColorModel(sampleDimensions, visibleBand, dataType, ColorModelFactory.GRAYSCALE);
- }
-
- /**
* Returns the converted or package view, or {@code null} if not yet computed.
* It is caller responsibility to ensure that this method is invoked in a synchronized block.
*/
@@ -245,6 +236,22 @@ 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 this image 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 int dataType, final MathTransform1D[] converters) {
+ final int visibleBands = Math.max(0, ImageUtilities.getVisibleBand(source));
+ final ColorModel cm = ColorModelFactory.createColorModel(dataType, sampleDimensions.length, visibleBands,
+ sampleDimensions[visibleBands].getCategories(), ColorModelFactory.GRAYSCALE);
+
+ return BandedSampleConverter.create(source, null, dataType, cm, getRanges(), converters);
+ }
+
+ /**
* Creates a new function for computing or interpolating sample values at given locations.
* That function accepts {@link DirectPosition} in arbitrary Coordinate Reference System;
* conversion to grid indices are applied as needed.
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 a281172..e27fd61 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
@@ -19,7 +19,6 @@ package org.apache.sis.coverage.grid;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import java.text.NumberFormat;
@@ -30,7 +29,6 @@ import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
-import java.awt.image.ColorModel;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.spatial.DimensionNameType;
import org.opengis.util.NameFactory;
@@ -44,7 +42,6 @@ import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.MathTransform1D;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.internal.coverage.j2d.ImageUtilities;
-import org.apache.sis.internal.coverage.j2d.BandedSampleConverter;
import org.apache.sis.internal.feature.Resources;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.util.collection.TableColumn;
@@ -151,8 +148,7 @@ public class GridCoverage2D extends GridCoverage {
{
super(source.gridGeometry, range);
final int dataType = ConvertedGridCoverage.getDataType(range, isConverted, source);
- final ColorModel colorModel = createColorModel(Math.max(0, ImageUtilities.getVisibleBand(source.data)), dataType);
- data = BandedSampleConverter.create(source.data, null, dataType, colorModel, getRanges(), converters);
+ data = convert(source.data, dataType, converters);
gridToImageX = source.gridToImageX;
gridToImageY = source.gridToImageY;
xDimension = source.xDimension;
@@ -208,7 +204,7 @@ public class GridCoverage2D extends GridCoverage {
* @throws IllegalArgumentException if the image number of bands is not the same than the number of sample dimensions.
* @throws ArithmeticException if the distance between grid location and image location exceeds the {@code long} capacity.
*/
- public GridCoverage2D(GridGeometry domain, final Collection<? extends SampleDimension> range, RenderedImage data) {
+ public GridCoverage2D(GridGeometry domain, final List<? extends SampleDimension> range, RenderedImage data) {
/*
* The complex nesting of method calls below is a workaround for RFE #4093999
* ("Relax constraint on placement of this()/super() call in constructors").
@@ -334,7 +330,7 @@ public class GridCoverage2D extends GridCoverage {
*
* @see GridGeometry#GridGeometry(GridExtent, Envelope)
*/
- public GridCoverage2D(final Envelope domain, final Collection<? extends SampleDimension> range, final RenderedImage data) {
+ public GridCoverage2D(final Envelope domain, final List<? extends SampleDimension> range, final RenderedImage data) {
super(createGridGeometry(data, domain), defaultIfAbsent(range, data, ImageUtilities.getNumBands(data)));
this.data = data; // Non-null verified by createGridGeometry(…, data).
xDimension = 0;
@@ -402,8 +398,8 @@ public class GridCoverage2D extends GridCoverage {
* @param numBands the number of bands in the given image, or 0 if none.
* @return the given list of sample dimensions if it was non-null, or a default list otherwise.
*/
- static Collection<? extends SampleDimension> defaultIfAbsent(Collection<? extends SampleDimension> range,
- final RenderedImage data, final int numBands)
+ static List<? extends SampleDimension> defaultIfAbsent(List<? extends SampleDimension> range,
+ final RenderedImage data, final int numBands)
{
if (range == null) {
final short[] names = (data != null) ? ImageUtilities.bandNames(data) : ArraysExt.EMPTY_SHORT;
@@ -430,7 +426,7 @@ public class GridCoverage2D extends GridCoverage {
* However this class has a little bit of tolerance to missing sample model; it may happen
* when the image is used only as a matrix storage.
*/
- private static void verifyBandCount(final Collection<? extends SampleDimension> range, final RenderedImage data) {
+ private static void verifyBandCount(final List<? extends SampleDimension> range, final RenderedImage data) {
if (range != null) {
final SampleModel sm = data.getSampleModel();
if (sm != null) {
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 81aea94..c6f5cd3 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
@@ -432,7 +432,7 @@ public class GridCoverageBuilder {
*/
public GridCoverage build() throws IllegalStateException {
GridGeometry grid = domain; // May be replaced by an instance with extent.
- Collection<? extends SampleDimension> bands = ranges; // May be replaced by a non-null value.
+ List<? extends SampleDimension> bands = ranges; // May be replaced by a non-null value.
/*
* If not already done, create the image from the raster. We try to create the most standard objects
* when possible: a BufferedImage (from Java2D), then later a GridCoverage2D (from SIS public API).
@@ -462,9 +462,8 @@ public class GridCoverageBuilder {
*/
bands = GridCoverage2D.defaultIfAbsent(bands, null, raster.getNumBands());
final int dataType = raster.getSampleModel().getDataType();
- final ColorModel colors = ColorModelFactory.createColorModel(
- bands.toArray(new SampleDimension[bands.size()]),
- visibleBand, dataType, ColorModelFactory.GRAYSCALE);
+ final ColorModel colors = ColorModelFactory.createColorModel(dataType, bands.size(), visibleBand,
+ bands.get(visibleBand).getCategories(), ColorModelFactory.GRAYSCALE);
/*
* Create an image from the raster. We favor BufferedImage instance when possible,
* and fallback on TiledImage only if the BufferedImage can not be created.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
index 3d32da1..b01351b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -245,6 +245,7 @@ public class ImageRenderer {
*
* @see #addProperty(String, Object)
*/
+ @SuppressWarnings("UseOfObsoleteCollectionType")
private Hashtable<String,Object> properties;
/**
@@ -417,6 +418,7 @@ public class ImageRenderer {
*
* @since 1.1
*/
+ @SuppressWarnings("UseOfObsoleteCollectionType")
public void addProperty(final String key, final Object value) {
ArgumentChecks.ensureNonNull("key", key);
ArgumentChecks.ensureNonNull("value", value);
@@ -617,10 +619,11 @@ public class ImageRenderer {
* @throws RasterFormatException if a call to a {@link WritableRaster} factory method failed.
* @throws ArithmeticException if a property of the image to construct exceeds the capacity of 32 bits integers.
*/
+ @SuppressWarnings("UseOfObsoleteCollectionType")
public RenderedImage image() {
final WritableRaster raster = raster();
- final ColorModel colors = ColorModelFactory.createColorModel(bands, visibleBand,
- buffer.getDataType(), ColorModelFactory.GRAYSCALE);
+ final ColorModel colors = ColorModelFactory.createColorModel(buffer.getDataType(), bands.length, visibleBand,
+ bands[visibleBand].getCategories(), ColorModelFactory.GRAYSCALE);
SliceGeometry supplier = null;
if (imageGeometry == null) {
@@ -663,6 +666,7 @@ public class ImageRenderer {
/**
* Creates a new buffered image wrapping the given raster.
*/
+ @SuppressWarnings("UseOfObsoleteCollectionType")
Untiled(final ColorModel colors, final WritableRaster raster, final Hashtable<?,?> properties,
final GridGeometry geometry, final SliceGeometry supplier)
{
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
index a8da696..e87ed5f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
@@ -448,53 +448,55 @@ public class ImageProcessor implements Cloneable {
*/
public Statistics[] getStatistics(final RenderedImage source, final Shape areaOfInterest) {
ArgumentChecks.ensureNonNull("source", source);
- Object property = source.getProperty(StatisticsCalculator.STATISTICS_KEY);
- if (!(property instanceof Statistics[])) {
- final boolean parallel, failOnException;
- final Filter errorListener;
- synchronized (this) {
- parallel = parallel(source);
- failOnException = failOnException();
- errorListener = errorListener();
+ if (areaOfInterest == null) {
+ final Object property = source.getProperty(StatisticsCalculator.STATISTICS_KEY);
+ if (property instanceof Statistics[]) {
+ return (Statistics[]) property;
}
- /*
- * No need to check if the given source is already an instance of StatisticsCalculator.
- * The way AnnotatedImage cache mechanism is implemented, if statistics result already
- * exist, they will be used.
- */
- final AnnotatedImage calculator = new StatisticsCalculator(source, areaOfInterest, parallel, failOnException);
- property = calculator.getProperty(StatisticsCalculator.STATISTICS_KEY);
- calculator.logAndClearError(ImageProcessor.class, "getStatistics", errorListener);
}
+ final boolean parallel, failOnException;
+ final Filter errorListener;
+ synchronized (this) {
+ parallel = parallel(source);
+ failOnException = failOnException();
+ errorListener = errorListener();
+ }
+ /*
+ * No need to check if the given source is already an instance of StatisticsCalculator.
+ * The way AnnotatedImage cache mechanism is implemented, if statistics results already
+ * exist, they will be used.
+ */
+ final AnnotatedImage calculator = new StatisticsCalculator(source, areaOfInterest, parallel, failOnException);
+ final Object property = calculator.getProperty(StatisticsCalculator.STATISTICS_KEY);
+ calculator.logAndClearError(ImageProcessor.class, "getStatistics", errorListener);
return (Statistics[]) property;
}
/**
* Returns an image with statistics (minimum, maximum, mean, standard deviation) on each bands.
- * The property value will be computed when first requested (it is not computed by this method).
+ * The property value will be computed when first requested (it is not computed immediately by this method).
*
* <p>If {@code areaOfInterest} is {@code null}, then the default is as below:</p>
* <ul>
* <li>If the {@value StatisticsCalculator#STATISTICS_KEY} property value exists in the given image,
* then that image is returned as-is. Note that the existing property value is not necessarily
* statistics for the whole image.
- * They are whatever statistics the property provided considered as representative.</li>
+ * They are whatever statistics the property provider considers as representative.</li>
* <li>Otherwise an image augmented with a {@value StatisticsCalculator#STATISTICS_KEY} property value
* is returned.</li>
* </ul>
*
- * @param source the image for which to provide statistics (may be {@code null}).
+ * @param source the image for which to provide statistics.
* @param areaOfInterest pixel coordinates of the area of interest, or {@code null} for the default.
* @return an image with an {@value StatisticsCalculator#STATISTICS_KEY} property.
- * May be {@code image} if the given argument is null or already has a statistics property.
+ * May be {@code image} if the given argument already has a statistics property.
*
* @see #getStatistics(RenderedImage, Shape)
* @see StatisticsCalculator#STATISTICS_KEY
*/
public RenderedImage statistics(final RenderedImage source, final Shape areaOfInterest) {
- if (source == null || (areaOfInterest == null &&
- ArraysExt.contains(source.getPropertyNames(), StatisticsCalculator.STATISTICS_KEY)))
- {
+ ArgumentChecks.ensureNonNull("source", source);
+ if (areaOfInterest == null && ArraysExt.contains(source.getPropertyNames(), StatisticsCalculator.STATISTICS_KEY)) {
return source;
}
final boolean parallel, failOnException;
@@ -506,36 +508,18 @@ public class ImageProcessor implements Cloneable {
}
/**
- * Returns an image with the same sample values than the given image, but with its color ramp stretched between the specified bounds.
- * For example in a gray scale image, pixels with the minimum value will be black and pixels with the maximum value will be white.
- * This operation is a kind of <cite>tone mapping</cite>, a technique used in image processing to map one set of colors to another.
- * The mapping applied by this method is conceptually a simple linear transform (a scale and an offset)
- * applied on sample values before they are mapped to their colors.
- *
- * <p>Current implementation can stretch only gray scale images (it may be extended to indexed color models
- * in a future version). If this method can not stretch the color ramp, for example because the given image
- * is an RGB image, then the image is returned unchanged.</p>
- *
- * @param source the image to recolor (may be {@code null}).
- * @param minimum the sample value to display with the first color of the color ramp (black in a grayscale image).
- * @param maximum the sample value to display with the last color of the color ramp (white in a grayscale image).
- * @return the image with color ramp stretched between the given bounds, or {@code image} unchanged if the operation
- * can not be applied on the given image.
- */
- public RenderedImage stretchColorRamp(final RenderedImage source, final double minimum, final double maximum) {
- ArgumentChecks.ensureFinite("minimum", minimum);
- ArgumentChecks.ensureFinite("maximum", maximum);
- return RecoloredImage.create(source, minimum, maximum);
- }
-
- /**
* Returns an image with the same sample values than the given image, but with its color ramp stretched between
- * automatically determined bounds. This is the same operation than {@link #stretchColorRamp(RenderedImage,
- * double, double) stretchColorRamp(…)} except that the minimum and maximum values are determined by
- * {@linkplain #getStatistics(RenderedImage, Shape) statistics} on the image:
- * a range of value is determined first from the {@linkplain Statistics#minimum() minimum} and
- * {@linkplain Statistics#maximum() maximum} values found in the image, optionally narrowed to an interval
- * of some {@linkplain Statistics#standardDeviation(boolean) standard deviations} around the mean value.
+ * specified or inferred bounds. For example in a gray scale image, pixels with the minimum value will be black
+ * and pixels with the maximum value will be white. This operation is a kind of <cite>tone mapping</cite>,
+ * a technique used in image processing to map one set of colors to another. The mapping applied by this method
+ * is conceptually a simple linear transform (a scale and an offset) applied on sample values before they are
+ * mapped to their colors.
+ *
+ * <p>The minimum and maximum value can be either specified explicitly,
+ * or determined from {@linkplain #getStatistics(RenderedImage, Shape) statistics} on the image.
+ * In the later case a range of value is determined first from the {@linkplain Statistics#minimum() minimum}
+ * and {@linkplain Statistics#maximum() maximum} values found in the image, optionally narrowed to an interval
+ * of some {@linkplain Statistics#standardDeviation(boolean) standard deviations} around the mean value.</p>
*
* <p>Narrowing with standard deviations is useful for data having a Gaussian distribution, as often seen in nature.
* In such distribution, 99.9% of the data are between the mean ± 3×<var>standard deviation</var>, but some values
@@ -555,6 +539,14 @@ public class ImageProcessor implements Cloneable {
* <th>Purpose</th>
* <th>Values</th>
* </tr><tr>
+ * <td>{@code "minimum"}</td>
+ * <td>Minimum value (omit for computing from statistics).</td>
+ * <td>{@link Number}</td>
+ * </tr><tr>
+ * <td>{@code "maximum"}</td>
+ * <td>Maximum value (omit for computing from statistics).</td>
+ * <td>{@link Number}</td>
+ * </tr><tr>
* <td>{@code "multStdDev"}</td>
* <td>Multiple of the standard deviation.</td>
* <td>{@link Number} (typical values: 1.5, 2 or 3)</td>
@@ -569,18 +561,24 @@ public class ImageProcessor implements Cloneable {
* </tr>
* </table>
*
- * @param source the image to recolor (may be {@code null}).
+ * <h4>Limitation</h4>
+ * Current implementation can stretch only gray scale images (it may be extended to indexed color models
+ * in a future version). If this method can not stretch the color ramp, for example because the given image
+ * is an RGB image, then the image is returned unchanged.
+ *
+ * @param source the image to recolor.
* @param modifiers modifiers for narrowing the range of values, or {@code null} if none.
* @return the image with color ramp stretched between the automatic bounds,
* or {@code image} unchanged if the operation can not be applied on the given image.
*/
public RenderedImage stretchColorRamp(final RenderedImage source, final Map<String,?> modifiers) {
- return RecoloredImage.create(this, source, modifiers);
+ ArgumentChecks.ensureNonNull("source", source);
+ return RecoloredImage.stretchColorRamp(this, source, modifiers);
}
/**
* Changes the color ramp of the given image. The given image must use an {@link IndexColorModel}
- * with the same number of colors than the given {@code ARGB} length.
+ * with the same number of colors than the given {@code ARGB} array length.
*
* @param source the image for which to replace the color model.
* @param ARGB Alpha=Red=Green=Blue codes of new color map.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java
index e305976..238cb85 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java
@@ -34,9 +34,9 @@ import org.apache.sis.util.Classes;
/**
* An image with the same sample values than the wrapped image but a different color model.
- * Current implementation can only apply a gray scale. Future implementations may detect
- * the existing color model and try to preserve colors (for example by building an indexed
- * color model).
+ * The only interesting member method is {@link #getColorModel()}, which returns the model
+ * specified at construction time. All other non-trivial methods are static helper methods
+ * for {@link ImageProcessor}, defined here for reducing {@link ImageProcessor} size.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
@@ -46,6 +46,8 @@ import org.apache.sis.util.Classes;
final class RecoloredImage extends ImageAdapter {
/**
* The color model to associate with this recolored image.
+ *
+ * @see #getColorModel()
*/
private final ColorModel colors;
@@ -58,110 +60,115 @@ final class RecoloredImage extends ImageAdapter {
}
/**
- * Wraps the given image with its colors ramp scaled between the given bounds. If the given image is
- * already using a color ramp for the given range of values, then that image is returned unchanged.
+ * Returns an image with the same sample values than the given image, but with its color ramp stretched
+ * between specified or inferred bounds. The mapping applied by this method is conceptually a linear
+ * transform applied on sample values before they are mapped to their colors.
*
- * @param source the image to recolor.
- * @param visibleBand the band to make visible.
- * @param minimum the sample value to display with the first color of the color ramp (black in a grayscale image).
- * @param maximum the sample value to display with the last color of the color ramp (white in a grayscale image).
- * @return the image with color ramp rescaled between the given bounds. May be the given image returned as-is.
- */
- private static RenderedImage create(RenderedImage source, final int visibleBand, final double minimum, final double maximum) {
- final SampleModel sm = source.getSampleModel();
- final int dataType = sm.getDataType();
- final ColorModel colors = ColorModelFactory.createGrayScale(dataType, sm.getNumBands(), visibleBand, minimum, maximum);
- for (;;) {
- if (colors.equals(source.getColorModel())) {
- return source;
- } else if (source instanceof RecoloredImage) {
- source = ((RecoloredImage) source).source;
- } else {
- break;
- }
- }
- return ImageProcessor.unique(new RecoloredImage(source, colors));
- }
-
- /**
- * Implementation of {@link ImageProcessor#stretchColorRamp(RenderedImage, double, double)}.
- * Defined in this class for reducing {@link ImageProcessor} size.
- *
- * @param source the image to recolor (may be {@code null}).
- * @param minimum the sample value to display with the first color of the color ramp (black in a grayscale image).
- * @param maximum the sample value to display with the last color of the color ramp (white in a grayscale image).
- * @return the image with color ramp stretched between the given bounds, or {@code image} unchanged if the operation
- * can not be applied on the given image.
- */
- static RenderedImage create(final RenderedImage source, final double minimum, final double maximum) {
- if (!(minimum < maximum)) {
- throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalRange_2, minimum, maximum));
- }
- final int visibleBand = ImageUtilities.getVisibleBand(source);
- if (visibleBand >= 0) {
- return create(source, visibleBand, minimum, maximum);
- }
- return source;
- }
-
- /**
- * Implementation of {@link ImageProcessor#stretchColorRamp(RenderedImage, Map)}.
- * Defined in this class for reducing {@link ImageProcessor} size.
- * See above-cited public method for the list of modifier keys recognized by this method.
+ * <p>Current implementation can stretch only gray scale images (it may be extended to indexed color models
+ * in a future version). If this method can not stretch the color ramp, for example because the given image
+ * is an RGB image, then the image is returned unchanged.</p>
*
* @param processor the processor to use for computing statistics if needed.
- * @param source the image to recolor (may be {@code null}).
+ * @param source the image to recolor (can be {@code null}).
* @param modifiers modifiers for narrowing the range of values, or {@code null} if none.
* @return the image with color ramp stretched between the automatic bounds,
* or {@code image} unchanged if the operation can not be applied on the given image.
+ *
+ * @see ImageProcessor#stretchColorRamp(RenderedImage, Map)
*/
- static RenderedImage create(final ImageProcessor processor, final RenderedImage source, final Map<String,?> modifiers) {
- RenderedImage statsSource = source;
- Statistics[] statsAllBands = null;
- Statistics statistics = null;
- double deviations = Double.POSITIVE_INFINITY;
- if (modifiers != null) {
- Object value = modifiers.get("multStdDev");
- if (value instanceof Number) {
- deviations = ((Number) value).doubleValue();
- ArgumentChecks.ensureStrictlyPositive("multStdDev", deviations);
- }
- value = modifiers.get("statistics");
- if (value instanceof RenderedImage) {
- statsSource = (RenderedImage) value;
- } else if (value instanceof Statistics) {
- statistics = (Statistics) value;
- } else if (value instanceof Statistics[]) {
- statsAllBands = (Statistics[]) value;
- }
- }
+ static RenderedImage stretchColorRamp(final ImageProcessor processor, final RenderedImage source, final Map<String,?> modifiers) {
final int visibleBand = ImageUtilities.getVisibleBand(source);
if (visibleBand >= 0) {
- if (statistics == null) {
- if (statsAllBands == null) {
- final Object areaOfInterest = modifiers.get("areaOfInterest");
- statsAllBands = processor.getStatistics(statsSource,
- (areaOfInterest instanceof Shape) ? (Shape) areaOfInterest : null);
+ RenderedImage statsSource = source;
+ Statistics[] statsAllBands = null;
+ Statistics statistics = null;
+ double minimum = Double.NaN;
+ double maximum = Double.NaN;
+ double deviations = Double.POSITIVE_INFINITY;
+ /*
+ * Extract and validate parameter values.
+ * No calculation started at this stage.
+ */
+ if (modifiers != null) {
+ final Object minValue = modifiers.get("minimum");
+ if (minValue instanceof Number) {
+ minimum = ((Number) minValue).doubleValue();
+ }
+ final Object maxValue = modifiers.get("maximum");
+ if (maxValue instanceof Number) {
+ maximum = ((Number) maxValue).doubleValue();
}
- if (statsAllBands != null && visibleBand < statsAllBands.length) {
- statistics = statsAllBands[visibleBand];
+ if (minimum >= maximum) {
+ throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalRange_2, minValue, maxValue));
+ }
+ Object value = modifiers.get("multStdDev");
+ if (value instanceof Number) {
+ deviations = ((Number) value).doubleValue();
+ ArgumentChecks.ensureStrictlyPositive("multStdDev", deviations);
+ }
+ value = modifiers.get("statistics");
+ if (value instanceof RenderedImage) {
+ statsSource = (RenderedImage) value;
+ } else if (value instanceof Statistics) {
+ statistics = (Statistics) value;
+ } else if (value instanceof Statistics[]) {
+ statsAllBands = (Statistics[]) value;
}
}
- if (statistics != null) {
- deviations *= statistics.standardDeviation(true);
- final double mean = statistics.mean();
- final double minimum = Math.max(statistics.minimum(), mean - deviations);
- final double maximum = Math.min(statistics.maximum(), mean + deviations);
- if (minimum < maximum) {
- return create(source, visibleBand, minimum, maximum);
+ /*
+ * If minimum and maximum values were not explicitly specified,
+ * compute them from statistics.
+ */
+ if (Double.isNaN(minimum) || Double.isNaN(maximum)) {
+ if (statistics == null) {
+ if (statsAllBands == null) {
+ final Object areaOfInterest = modifiers.get("areaOfInterest");
+ statsAllBands = processor.getStatistics(statsSource,
+ (areaOfInterest instanceof Shape) ? (Shape) areaOfInterest : null);
+ }
+ if (statsAllBands != null && visibleBand < statsAllBands.length) {
+ statistics = statsAllBands[visibleBand];
+ }
+ }
+ if (statistics != null) {
+ deviations *= statistics.standardDeviation(true);
+ final double mean = statistics.mean();
+ if (Double.isNaN(minimum)) minimum = Math.max(statistics.minimum(), mean - deviations);
+ if (Double.isNaN(maximum)) maximum = Math.min(statistics.maximum(), mean + deviations);
}
}
+ /*
+ * Wraps the given image with its colors ramp scaled between the given bounds. If the given image is
+ * already using a color ramp for the given range of values, then that image is returned unchanged.
+ */
+ if (minimum < maximum) {
+ final SampleModel sm = source.getSampleModel();
+ final ColorModel colors = ColorModelFactory.createGrayScale(sm.getDataType(), sm.getNumBands(), visibleBand, minimum, maximum);
+ RenderedImage parent = source;
+ for (;;) {
+ if (colors.equals(parent.getColorModel())) {
+ return parent;
+ } else if (parent instanceof RecoloredImage) {
+ parent = ((RecoloredImage) parent).source;
+ } else {
+ break;
+ }
+ }
+ return ImageProcessor.unique(new RecoloredImage(parent, colors));
+ }
}
return source;
}
/**
- * Implementation of {@link ImageProcessor#recolor(RenderedImage, int[])}.
+ * Changes the color ramp of the given image. The given image must use an {@link IndexColorModel}
+ * with the same number of colors than the given {@code ARGB} array length.
+ *
+ * @param source the image for which to replace the color model.
+ * @param ARGB Alpha=Red=Green=Blue codes of new color map.
+ * @return image using the given color map.
+ *
+ * @see ImageProcessor#recolor(RenderedImage, int[])
*/
static RenderedImage recolor(final RenderedImage source, final int[] ARGB) {
String expected, actual; // To be used in case of error.
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 19d4fd8..1d6a3d6 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
@@ -17,9 +17,10 @@
package org.apache.sis.internal.coverage.j2d;
import java.util.Map;
+import java.util.List;
import java.util.Arrays;
import java.util.Comparator;
-import java.util.LinkedHashMap;
+import java.util.AbstractMap;
import java.util.function.Function;
import java.awt.Transparency;
import java.awt.Color;
@@ -30,7 +31,6 @@ import java.awt.image.PackedColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import org.apache.sis.coverage.Category;
-import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.ArgumentChecks;
@@ -56,7 +56,7 @@ public final class ColorModelFactory {
/**
* Applies a gray scale to quantitative category and transparent colors to qualitative categories.
- * This is a possible argument for {@link #createColorModel(SampleDimension[], int, int, Function)}.
+ * This is a possible argument for {@link #createColorModel(int, int, int, List, Function)}.
*/
public static final Function<Category,Color[]> GRAYSCALE =
(category) -> category.isQuantitative() ? new Color[] {Color.BLACK, Color.WHITE} : null;
@@ -101,6 +101,7 @@ public final class ColorModelFactory {
/**
* The minimum (inclusive) and maximum (exclusive) sample values.
+ * This is used only if {@link #type} is not {@link DataBuffer#TYPE_BYTE} or {@link DataBuffer#TYPE_USHORT}.
*/
private final double minimum, maximum;
@@ -150,22 +151,22 @@ public final class ColorModelFactory {
* Constructs a new {@code ColorModelFactory}. This object will be used as a key in a {@link Map},
* so this is not really a {@code ColorModelFactory} but a kind of "{@code ColorModelKey}" instead.
* However, since this constructor is private, user does not need to know that.
+ *
+ * @see #createColorModel(Map.Entry[], int, int, int)
*/
- private ColorModelFactory(final Map<? extends NumberRange<?>, ? extends Color[]> categories,
+ private ColorModelFactory(final Map.Entry<NumberRange<?>, Color[]>[] colors,
final int visibleBand, final int numBands, final int type)
{
this.visibleBand = visibleBand;
this.numBands = numBands;
this.type = type;
- @SuppressWarnings({"unchecked", "rawtypes"})
- final Map.Entry<NumberRange<?>, Color[]>[] entries = categories.entrySet().toArray(new Map.Entry[categories.size()]);
- Arrays.sort(entries, RANGE_COMPARATOR);
+ Arrays.sort(colors, RANGE_COMPARATOR);
int count = 0;
- int[] starts = new int[entries.length + 1];
- int[][] codes = new int[entries.length][];
+ int[] starts = new int[colors.length + 1];
+ int[][] codes = new int[colors.length][];
double minimum = Double.POSITIVE_INFINITY;
double maximum = Double.NEGATIVE_INFINITY;
- for (final Map.Entry<NumberRange<?>, Color[]> entry : entries) {
+ for (final Map.Entry<NumberRange<?>, Color[]> entry : colors) {
final NumberRange<?> range = entry.getKey();
final double min = range.getMinDouble(true);
final double max = range.getMaxDouble(false);
@@ -225,6 +226,9 @@ public final class ColorModelFactory {
/*
* If the requested type is any type not supported by IndexColorModel,
* fallback on a generic (but very slow!) color model.
+ *
+ * TODO: current implementation ignores ARGB codes.
+ * But a future implementation may use them.
*/
if (type != DataBuffer.TYPE_BYTE && type != DataBuffer.TYPE_USHORT) {
return createGrayScale(type, numBands, visibleBand, minimum, maximum);
@@ -303,27 +307,28 @@ public final class ColorModelFactory {
}
/**
- * Returns a color model interpolated for the ranges in the given sample dimensions.
- * This method builds up the color model from each category in the visible sample dimension.
+ * Returns a color model interpolated for the ranges in the given categories.
* Returned instances of {@link ColorModel} are shared among all callers in the running virtual machine.
*
- * @param bands the sample dimensions for which to create a color model.
- * @param visibleBand the band to be made visible (usually 0). All other bands (if any) will be ignored.
* @param type the color model type. One of {@link DataBuffer#TYPE_BYTE}, {@link DataBuffer#TYPE_USHORT},
- * {@link DataBuffer#TYPE_INT}, {@link DataBuffer#TYPE_FLOAT} or {@link DataBuffer#TYPE_DOUBLE}.
+ * {@link DataBuffer#TYPE_SHORT}, {@link DataBuffer#TYPE_INT}, {@link DataBuffer#TYPE_FLOAT}
+ * or {@link DataBuffer#TYPE_DOUBLE}.
+ * @param numBands number of bands.
+ * @param visibleBand the band to be made visible (usually 0). All other bands (if any) will be ignored.
+ * @param categories description of value ranges in the visible band.
* @param colors the colors to use for each category. The function may return {@code null}, which means transparent.
* @return a color model suitable for {@link java.awt.image.RenderedImage} objects with values in the given ranges.
*/
- public static ColorModel createColorModel(final SampleDimension[] bands,
- final int visibleBand, final int type, Function<Category,Color[]> colors)
+ public static ColorModel createColorModel(final int type, final int numBands, final int visibleBand,
+ final List<Category> categories, final Function<Category,Color[]> colors)
{
- ArgumentChecks.ensureNonNull("bands", bands);
- ArgumentChecks.ensureNonNull("colors", colors);
- final Map<NumberRange<?>, Color[]> ranges = new LinkedHashMap<>();
- for (final Category category : bands[visibleBand].getCategories()) {
- ranges.put(category.getSampleRange(), colors.apply(category));
+ @SuppressWarnings({"unchecked", "rawtypes"}) // Generic array creation.
+ final Map.Entry<NumberRange<?>, Color[]>[] ranges = new Map.Entry[categories.size()];
+ for (int i=0; i<ranges.length; i++) {
+ final Category category = categories.get(i);
+ ranges[i] = new AbstractMap.SimpleImmutableEntry<>(category.getSampleRange(), colors.apply(category));
}
- return createColorModel(ranges, visibleBand, bands.length, type);
+ return createColorModel(ranges, visibleBand, numBands, type);
}
/**
@@ -331,28 +336,28 @@ public final class ColorModelFactory {
* This method builds up the color model from each set of colors associated to ranges in the given map.
* Returned instances of {@link ColorModel} are shared among all callers in the running virtual machine.
*
- * @param categories the colors associated to ranges of sample values.
+ * <p>The given ranges are rounded to nearest integers and clamped to the range of 32 bits integer values.
+ * The associated arrays of colors do not need to have a length equals to {@code upper} − {@code lower};
+ * color interpolations will be applied as needed.</p>
+ *
+ * @param colors the colors associated to ranges of sample values.
* @param visibleBand the band to be made visible (usually 0). All other bands, if any will be ignored.
* @param numBands the number of bands for the color model (usually 1). The returned color model will render only
* the {@code visibleBand} and ignore the others, but the existence of all {@code numBands} will
* be at least tolerated. Supplemental bands, even invisible, are useful for processing.
* @param type the color model type. One of {@link DataBuffer#TYPE_BYTE}, {@link DataBuffer#TYPE_USHORT},
- * {@link DataBuffer#TYPE_INT}, {@link DataBuffer#TYPE_FLOAT} or {@link DataBuffer#TYPE_DOUBLE}.
+ * {@link DataBuffer#TYPE_SHORT}, {@link DataBuffer#TYPE_INT}, {@link DataBuffer#TYPE_FLOAT}
+ * or {@link DataBuffer#TYPE_DOUBLE}.
* @return a color model suitable for {@link java.awt.image.RenderedImage} objects with values in the given ranges.
*/
- public static ColorModel createColorModel(final Map<? extends NumberRange<?>, ? extends Color[]> categories,
- final int visibleBand, final int numBands, final int type)
+ public static ColorModel createColorModel(final Map.Entry<NumberRange<?>, Color[]>[] colors,
+ final int visibleBand, final int numBands, final int type)
{
- ArgumentChecks.ensureNonNull("categories", categories);
+ ArgumentChecks.ensureNonNull("colors", colors);
ArgumentChecks.ensureBetween("visibleBand", 0, numBands - 1, visibleBand);
- final ColorModelFactory key = new ColorModelFactory(categories, visibleBand, numBands, type);
+ final ColorModelFactory key = new ColorModelFactory(colors, visibleBand, numBands, type);
synchronized (PIECEWISES) {
- ColorModel model = PIECEWISES.get(key);
- if (model == null) {
- model = key.createColorModel();
- PIECEWISES.put(key, model);
- }
- return model;
+ return PIECEWISES.computeIfAbsent(key, ColorModelFactory::createColorModel);
}
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java
index 455e310..9109296 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java
@@ -63,7 +63,7 @@ public final strictfp class ConvertedGridCoverageTest extends TestCase {
new AffineTransform2D(1, 0, 0, 1, 1, 0), HardCodedCRS.WGS84);
final BufferedGridCoverage coverage = new BufferedGridCoverage(
- grid, Collections.singleton(sd), DataBuffer.TYPE_SHORT);
+ grid, Collections.singletonList(sd), DataBuffer.TYPE_SHORT);
coverage.data.setElem(0, -1);
coverage.data.setElem(1, 3);