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 7090cb3ada Complete the migration to `Colorizer` in the
`Visualization` class. Deprecate the `Map<NumberRange,Color[]>` argument in
`ImageProcessor`. This is replaced by `Colorizer.forRanges(Map)`.
7090cb3ada is described below
commit 7090cb3ada08646cf9cc9e277c54b68235900c01
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Mar 28 14:09:54 2023 +0200
Complete the migration to `Colorizer` in the `Visualization` class.
Deprecate the `Map<NumberRange,Color[]>` argument in `ImageProcessor`.
This is replaced by `Colorizer.forRanges(Map)`.
---
.../apache/sis/internal/gui/ImageConverter.java | 13 +-
.../main/java/org/apache/sis/image/Colorizer.java | 62 +++++---
.../java/org/apache/sis/image/ImageProcessor.java | 87 ++++++++--
.../java/org/apache/sis/image/Interpolation.java | 6 +-
.../java/org/apache/sis/image/Visualization.java | 177 +++++++++++++--------
5 files changed, 237 insertions(+), 108 deletions(-)
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageConverter.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageConverter.java
index 19a3e5e6cc..eebf6c0227 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageConverter.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageConverter.java
@@ -32,6 +32,7 @@ import javafx.scene.image.ImageView;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
+import org.apache.sis.image.Colorizer;
import org.apache.sis.image.ImageProcessor;
import org.apache.sis.image.PlanarImage;
import org.apache.sis.internal.map.coverage.RenderingWorkaround;
@@ -67,9 +68,9 @@ final class ImageConverter extends Task<Statistics[]> {
* Colors to apply on the mask image when that image is overlay on top of
another image.
* Current value is a transparent yellow color.
*/
- private static final Map<NumberRange<?>,Color[]> MASK_TRANSPARENCY =
Map.of(
+ private static final Colorizer MASK_TRANSPARENCY =
Colorizer.forRanges(Map.of(
NumberRange.create(0, true, 0, true), new Color[]
{ColorModelFactory.TRANSPARENT},
- NumberRange.create(1, true, 1, true), new Color[] {new
Color(0x30FFFF00, true)});
+ NumberRange.create(1, true, 1, true), new Color[] {new
Color(0x30FFFF00, true)}));
/**
* The Java2D image to convert.
@@ -207,9 +208,13 @@ final class ImageConverter extends Task<Statistics[]> {
private RenderedImage getMask(final ImageProcessor processor) {
final Object mask = source.getProperty(PlanarImage.MASK_KEY);
if (mask instanceof RenderedImage) try {
- return processor.visualize((RenderedImage) mask,
MASK_TRANSPARENCY);
+ processor.setColorizer(MASK_TRANSPARENCY);
+ return processor.visualize((RenderedImage) mask, (java.util.List)
null);
} catch (IllegalArgumentException e) {
- // Ignore, we will not apply any mask. Declare
PropertyView.setImage(…) as the public method.
+ /*
+ * Ignore, we will not apply any mask over the thumbnail image.
+ * `PropertyView.setImage(…)` is declared as the public method.
+ */
Logging.recoverableException(LOGGER, PropertyView.class,
"setImage", e);
}
return null;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Colorizer.java
b/core/sis-feature/src/main/java/org/apache/sis/image/Colorizer.java
index 7f454b6cf6..330e7170e4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Colorizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Colorizer.java
@@ -26,6 +26,7 @@ import java.awt.Color;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
import java.awt.image.IndexColorModel;
+import java.awt.image.RenderedImage;
import org.apache.sis.coverage.Category;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.internal.coverage.j2d.ColorModelBuilder;
@@ -81,7 +82,7 @@ public interface Colorizer extends Function<Colorizer.Target,
Optional<ColorMode
private final int visibleBand;
/**
- * Creates a new record with the sample model of the image to colorize.
+ * Creates a new target with the sample model of the image to colorize.
*
* @param model sample model of the computed image to colorize
(mandatory).
* @param ranges description of the bands of the computed image
to colorize, or {@code null} if none.
@@ -135,6 +136,17 @@ public interface Colorizer extends
Function<Colorizer.Target, Optional<ColorMode
public OptionalInt getVisibleBand() {
return (visibleBand >= 0) ? OptionalInt.of(visibleBand) :
OptionalInt.empty();
}
+
+ /**
+ * Returns {@code true} if {@code orElse(…)} should not try
alternative colorizers.
+ * This is used only for {@link Visualization} operation, which is a
special case
+ * because it merges 3 operations in a single one.
+ *
+ * @return whether {@link #orElse(Colorizer)} should not try
alternative.
+ */
+ boolean isConsumed() {
+ return false;
+ }
}
/**
@@ -175,18 +187,24 @@ public interface Colorizer extends
Function<Colorizer.Target, Optional<ColorMode
*
* @param colors the colors to use for the specified range of sample
values.
* @return a colorizer which will interpolate the given colors in the
given range of values.
+ *
+ * @see ImageProcessor#visualize(RenderedImage, List)
*/
public static Colorizer forRanges(final Map<NumberRange<?>,Color[]>
colors) {
ArgumentChecks.ensureNonEmpty("colors", colors.entrySet());
final var factory = ColorModelFactory.piecewise(colors);
return (target) -> {
- final OptionalInt visibleBand = target.getVisibleBand();
- if (visibleBand.isEmpty()) {
- return Optional.empty();
+ if (target instanceof Visualization.Target) {
+ ((Visualization.Target) target).rangeColors = colors;
+ } else {
+ final OptionalInt visibleBand = target.getVisibleBand();
+ if (!visibleBand.isEmpty()) {
+ final SampleModel model = target.getSampleModel();
+ final int numBands = model.getNumBands();
+ return
Optional.ofNullable(factory.createColorModel(model.getDataType(), numBands,
visibleBand.getAsInt()));
+ }
}
- final SampleModel model = target.getSampleModel();
- final int numBands = model.getNumBands();
- return
Optional.ofNullable(factory.createColorModel(model.getDataType(), numBands,
visibleBand.getAsInt()));
+ return Optional.empty();
};
}
@@ -202,18 +220,24 @@ public interface Colorizer extends
Function<Colorizer.Target, Optional<ColorMode
*
* @param colors colors to use for arbitrary categories of sample values.
* @return a colorizer which will apply colors determined by the {@link
Category} of sample values.
+ *
+ * @see ImageProcessor#visualize(RenderedImage, List)
*/
public static Colorizer forCategories(final Function<Category,Color[]>
colors) {
ArgumentChecks.ensureNonNull("colors", colors);
return (target) -> {
- final int visibleBand = target.getVisibleBand().orElse(-1);
- if (visibleBand >= 0) {
- final List<SampleDimension> ranges =
target.getRanges().orElse(null);
- if (visibleBand < ranges.size()) {
- final SampleModel model = target.getSampleModel();
- final var c = new ColorModelBuilder(colors);
- c.initialize(model, ranges.get(visibleBand));
- return
Optional.ofNullable(c.createColorModel(model.getDataType(),
model.getNumBands(), visibleBand));
+ if (target instanceof Visualization.Target) {
+ ((Visualization.Target) target).categoryColors = colors;
+ } else {
+ final int visibleBand = target.getVisibleBand().orElse(-1);
+ if (visibleBand >= 0) {
+ final List<SampleDimension> ranges =
target.getRanges().orElse(null);
+ if (visibleBand < ranges.size()) {
+ final SampleModel model = target.getSampleModel();
+ final var c = new ColorModelBuilder(colors);
+ c.initialize(model, ranges.get(visibleBand));
+ return
Optional.ofNullable(c.createColorModel(model.getDataType(),
model.getNumBands(), visibleBand));
+ }
}
}
return Optional.empty();
@@ -255,10 +279,10 @@ public interface Colorizer extends
Function<Colorizer.Target, Optional<ColorMode
*/
default Colorizer orElse(final Colorizer alternative) {
ArgumentChecks.ensureNonNull("alternative", alternative);
- return (model) -> {
- var result = apply(model);
- if (result.isEmpty()) {
- result = alternative.apply(model);
+ return (target) -> {
+ var result = apply(target);
+ if (result.isEmpty() && !target.isConsumed()) {
+ result = alternative.apply(target);
}
return result;
};
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 78a52cb585..f1f30fd2d4 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
@@ -164,6 +164,7 @@ public class ImageProcessor implements Cloneable {
/**
* Properties (size, tile size, sample model, <i>etc.</i>) of destination
images.
+ * Shall never be null. Default value is {@link ImageLayout#DEFAULT}.
*
* @see #getImageLayout()
* @see #setImageLayout(ImageLayout)
@@ -1213,12 +1214,24 @@ public class ImageProcessor implements Cloneable {
*
* @param source the image to recolor for visualization purposes.
* @param colors colors to use for each range of values in the source
image.
- * @return recolored image for visualization purposes only.
+ * @deprecated Replaced by {@link #visualize(RenderedImage, List)} with
{@code null} list argument
+ * and colors map inferred from the {@link Colorizer}.
*/
- public RenderedImage visualize(final RenderedImage source, final
Map<NumberRange<?>,Color[]> colors) {
+ @Deprecated(since="1.4", forRemoval=true)
+ public synchronized RenderedImage visualize(final RenderedImage source,
final Map<NumberRange<?>,Color[]> colors) {
+ /*
+ * TODO: after removal of this method, search for usages of
+ * `visualize(RenderedImage, List)` and remove unecessary `(List)
null` cast.
+ */
ArgumentChecks.ensureNonNull("source", source);
ArgumentChecks.ensureNonNull("colors", colors);
- return visualize(new Visualization.Builder(source, colors.entrySet()));
+ final Colorizer old = colorizer;
+ try {
+ colorizer = Colorizer.forRanges(colors);
+ return visualize(new Visualization.Builder(null, source, null,
null));
+ } finally {
+ colorizer = old;
+ }
}
/**
@@ -1227,31 +1240,75 @@ public class ImageProcessor implements Cloneable {
* are used as-is (they are not copied or converted). Otherwise this
operation will convert sample
* values to unsigned bytes in order to enable the use of {@link
IndexColorModel}.
*
- * <p>This method is similar to {@link #visualize(RenderedImage, Map)}
- * except that the {@link Map} argument is splitted in two parts: the
ranges (map keys) are
- * {@linkplain Category#getSampleRange() encapsulated in
<code>Category</code>} objects, themselves
- * {@linkplain SampleDimension#getCategories() encapsulated in
<code>SampleDimension</code>} objects.
- * The colors (map values) are determined by a function receiving {@link
Category} inputs.
+ * <p>The resulting image is suitable for visualization purposes, but
should not be used for computation purposes.
+ * There is no guarantee about the number of bands in returned image or
about which formula is used for converting
+ * floating point values to integer values.</p>
+ *
+ * <h4>Specifying colors for ranges of pixel values</h4>
+ * When no {@link SampleDimension} information is available, the
recommended way to specify colors is like below.
+ * In this example, <var>min</var> and <var>max</var> are minimum and
maximum values
+ * (inclusive in this example, but they could be exclusive as well) in the
<em>source</em> image.
+ * Those extrema can be floating point values. This example specifies only
one range of values,
+ * but arbitrary numbers of non-overlapping ranges are allowed.
+ *
+ * {@snippet lang="java" :
+ * NumberRange<?> range = NumberRange.create(min, true, max, true);
+ * Color[] colors = {Color.BLUE, Color.MAGENTA, Color.RED};
+ * processor.setColorizer(Colorizer.forRanges(Map.of(range, colors)));
+ * RenderedImage visualization = processor.visualize(source, null);
+ * }
+ *
+ * The map given to the colorizer specifies the colors to use for
different ranges of values in the source image.
+ * The ranges of values in the returned image may not be the same; this
method is free to rescale them.
+ * The {@link Color} arrays may have any length; colors will be
interpolated as needed for fitting
+ * the ranges of values in the destination image.
+ *
+ * <h4>Specifying colors for sample dimension categories</h4>
+ * If {@link SampleDimension} information is available, a more flexible
way to specify colors
+ * is to associate colors to category names instead of predetermined
ranges of pixel values.
+ * The ranges will be inferred indirectly, {@linkplain
Category#getSampleRange() from the categories}
+ * themselves {@linkplain SampleDimension#getCategories() encapsulated in
sample dimensions}.
+ * The colors are determined by a function receiving {@link Category}
inputs.
+ *
+ * {@snippet lang="java" :
+ * Map<String,Color[]> colors = Map.of(
+ * "Temperature", new Color[] {Color.BLUE, Color.MAGENTA,
Color.RED},
+ * "Wind speed", new Color[] {Color.GREEN, Color.CYAN,
Color.BLUE});
+ *
+ * processor.setColorizer(Colorizer.forCategories((category) ->
+ * colors.get(category.getName().toString(Locale.ENGLISH))));
+ *
+ * RenderedImage visualization = processor.visualize(source, ranges);
+ * }
+ *
* This separation makes easier to apply colors based on criterion other
than numerical values.
* For example, colors could be determined from {@linkplain
Category#getName() category name} such as "Temperature",
* or {@linkplain org.apache.sis.measure.MeasurementRange#unit() units of
measurement}.
* The {@link Color} arrays may have any length; colors will be
interpolated as needed for fitting
- * the ranges of values in the destination image.</p>
+ * the ranges of values in the destination image.
*
- * <p>The resulting image is suitable for visualization purposes, but
should not be used for computation purposes.
- * There is no guarantee about the number of bands in returned image or
about which formula is used for converting
- * floating point values to integer values.</p>
+ * <p>The two approaches can be combined. For example the following
colorizer will choose colors based
+ * on sample dimensions if available, or fallback on predefined ranges of
pixel values otherwise:</p>
+ *
+ * {@snippet lang="java" :
+ * Function<Category,Color[]> flexible = ...;
+ * Map<NumberRange<?>,Color[]> predefined = ...;
+ *
processor.setColorizer(Colorizer.forCategories(flexible).orElse(Colorizer.forRanges(predefined)));
+ * }
*
* <h4>Properties used</h4>
* This operation uses the following properties in addition to method
parameters:
* <ul>
- * <li>{@linkplain #getCategoryColors() Category colors}.</li>
+ * <li>{@linkplain #getColorizer() Colorizer}.</li>
* </ul>
*
* @param source the image to recolor for visualization purposes.
* @param ranges description of {@code source} bands, or {@code null} if
none. This is typically
* obtained by {@link
org.apache.sis.coverage.grid.GridCoverage#getSampleDimensions()}.
* @return recolored image for visualization purposes only.
+ *
+ * @see Colorizer#forRanges(Map)
+ * @see Colorizer#forCategories(Function)
*/
public RenderedImage visualize(final RenderedImage source, final
List<SampleDimension> ranges) {
ArgumentChecks.ensureNonNull("source", source);
@@ -1285,7 +1342,7 @@ public class ImageProcessor implements Cloneable {
* if {@code bounds} size is not divisible by a tile size.</li>
* <li>{@linkplain #getPositionalAccuracyHints() Positional accuracy
hints}
* for enabling faster resampling at the cost of lower
precision.</li>
- * <li>{@linkplain #getCategoryColors() Category colors}.</li>
+ * <li>{@linkplain #getColorizer() Colorizer}.</li>
* </ul>
*
* @param source the image to be resampled and recolored.
@@ -1313,7 +1370,7 @@ public class ImageProcessor implements Cloneable {
synchronized (this) {
builder.layout = layout;
builder.interpolation = interpolation;
- builder.categoryColors = colors;
+ builder.colorizer = colorizer;
builder.fillValues = fillValues;
builder.positionalAccuracyHints = positionalAccuracyHints;
}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
b/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
index 87bcd4442d..196c1c2e65 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
@@ -110,13 +110,13 @@ public abstract class Interpolation {
* If the given image uses an index color model, interpolating the indexed
values does not produce the
* expected colors. Safest approach is to disable completely
interpolations in that case.
*
- * <div class="note"><b>Note:</b>
- * we could interpolate if we knew that all index values, without
exception (i.e. no index for missing values),
+ * <h4>Design note</h4>
+ * We could interpolate if we knew that all index values, without
exception (i.e. no index for missing values),
* are related to measurements by a linear function. In practice it rarely
happens, because there is usually
* at least one index value reserved for missing values. Scientific data
in SIS are usually stored as floating
* point type (with missing values mapped to NaN), which cannot be
associated to {@link IndexColorModel}.
* For now we do not try to perform a more sophisticated detection of
which interpolations are allowed,
- * but a future SIS version may revisit this policy if needed.</div>
+ * but a future SIS version may revisit this policy if needed.
*
* @return {@link #NEAREST} if interpolations should be restricted to
nearest-neighbor, or {@code this} otherwise.
*/
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 914b847f0d..4b8f38804f 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
@@ -20,7 +20,6 @@ import java.util.Map;
import java.util.List;
import java.util.Arrays;
import java.util.Objects;
-import java.util.Collection;
import java.util.function.Function;
import java.util.function.DoubleUnaryOperator;
import java.awt.Color;
@@ -34,7 +33,6 @@ import java.awt.image.WritableRaster;
import java.awt.image.RenderedImage;
import java.nio.DoubleBuffer;
import javax.measure.Quantity;
-import org.apache.sis.coverage.Category;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.operation.TransformException;
@@ -43,10 +41,12 @@ import
org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.internal.coverage.SampleDimensions;
import org.apache.sis.internal.coverage.CompoundTransform;
import org.apache.sis.internal.coverage.j2d.ColorModelBuilder;
+import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
import org.apache.sis.internal.coverage.j2d.ImageLayout;
import org.apache.sis.internal.coverage.j2d.ImageUtilities;
import org.apache.sis.internal.feature.Resources;
import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.Category;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.math.Statistics;
import org.apache.sis.util.collection.BackingStoreException;
@@ -60,10 +60,56 @@ import org.apache.sis.util.collection.BackingStoreException;
* {@link WritableRaster#setPixel(int, int, int[])} has more efficient
implementations for integers.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.1
*/
final class Visualization extends ResampledImage {
+ /**
+ * A colorization target handled in a special way by {@link Colorizer}
factory methods.
+ * The fields in this class are set when the {@link
Colorizer#apply(Target)} work needs
+ * to be done by the caller instead. This special case is needed because
the ranges and
+ * categories specified by the user are relative to the source image while
colorization
+ * operation needs ranges and categories relative to the target image.
+ */
+ static final class Target extends Colorizer.Target {
+ /**
+ * Colors to apply on the sample value ranges, as supplied by user.
+ */
+ Map<NumberRange<?>,Color[]> rangeColors;
+
+ /**
+ * Colors to apply on the sample dimensions, as supplied by user.
+ */
+ Function<Category,Color[]> categoryColors;
+
+ /**
+ * Whether the {@link Builder} has information about {@link
SampleDimension} categories.
+ */
+ private final boolean hasCategories;
+
+ /**
+ * Creates a new target with the sample model of the image to colorize.
+ *
+ * @param model sample model of the computed image to
colorize (mandatory).
+ * @param visibleBand the band to colorize if the colorization
algorithm uses only one band, or -1 if none.
+ * @param hasCategories whether the builder has information about
{@link SampleDimension} categories.
+ */
+ Target(final SampleModel model, final int visibleBand, final boolean
hasCategories) {
+ super(model, null, visibleBand);
+ this.hasCategories = hasCategories;
+ }
+
+ /**
+ * Returns {@code true} if {@code orElse(…)} should not try
alternative colorizers.
+ *
+ * @return whether {@link #orElse(Colorizer)} should not try
alternative.
+ */
+ @Override
+ boolean isConsumed() {
+ return (rangeColors != null) || (hasCategories && categoryColors
!= null);
+ }
+ }
+
/**
* Builds an image where all sample values are indices of colors in an
{@link IndexColorModel}.
* If the given image stores sample values as unsigned bytes or short
integers, then those values
@@ -72,8 +118,8 @@ final class Visualization extends ResampledImage {
*
* <p>This builder accepts two kinds of input:</p>
* <ul>
- * <li>Non-null {@code sourceBands} and {@link
ImageProcessor#getCategoryColors()}.</li>
- * <li>Non-null {@code rangesAndColors}.</li>
+ * <li>Non-null {@link #sourceBands} and {@link
Target#categoryColors}.</li>
+ * <li>Non-null {@link Target#rangeColors}.</li>
* </ul>
*
* The resulting image is suitable for visualization purposes but should
not be used for computation purposes.
@@ -91,7 +137,7 @@ final class Visualization extends ResampledImage {
private static final int NUM_BANDS = 1;
/** Band to make visible. */
- private static final int VISIBLE_BAND = 0;
+ private static final int VISIBLE_BAND =
ColorModelFactory.DEFAULT_VISIBLE_BAND;
//// ┌─────────────────────────────────────┐
//// │ Arguments given by user │
@@ -109,9 +155,6 @@ final class Visualization extends ResampledImage {
/** Description of {@link #source} bands, or {@code null} if none. */
private List<SampleDimension> sourceBands;
- /** Colors to apply for range of sample values in source image, or
{@code null} if none. */
- private Collection<Map.Entry<NumberRange<?>,Color[]>> rangesAndColors;
-
//// ┌─────────────────────────────────────┐
//// │ Given by ImageProcesor.configure(…) │
//// └─────────────────────────────────────┘
@@ -122,8 +165,8 @@ final class Visualization extends ResampledImage {
/** Object to use for performing interpolations. */
Interpolation interpolation;
- /** The colors to use for given categories of sample values, or {@code
null} is unspecified. */
- Function<Category,Color[]> categoryColors;
+ /** Provider of colors to apply for range of sample values in source
image, or {@code null} if none. */
+ Colorizer colorizer;
/** Values to use for pixels in this image that cannot be mapped to
pixels in source image. */
Number[] fillValues;
@@ -161,20 +204,6 @@ final class Visualization extends ResampledImage {
this.sourceBands = sourceBands;
}
- /**
- * Creates a builder for a visualization image with colors specified
for range of values.
- * Current version assumes that target image bounds are the same than
source image bounds
- * and that there is no change of pixel coordinates, but this is not a
real restriction.
- * The {@code bounds} and {@code toSource} arguments could be added
back in the future if useful.
- *
- * @param source the image for which to replace the color
model.
- * @param rangesAndColors range of sample values in source image
associated to colors to apply.
- */
- Builder(final RenderedImage source, final
Collection<Map.Entry<NumberRange<?>,Color[]>> rangesAndColors) {
- this.source = source;
- this.rangesAndColors = rangesAndColors;
- }
-
/**
* Returns an image where all sample values are indices of colors in
an {@link IndexColorModel}.
* If the source image stores sample values as unsigned bytes or short
integers, then those values
@@ -195,59 +224,91 @@ final class Visualization extends ResampledImage {
* cannot be converted to sample values in the recolored image.
*/
RenderedImage create(final ImageProcessor processor) throws
NoninvertibleTransformException {
- final int visibleBand = ImageUtilities.getVisibleBand(source);
+ final RenderedImage coloredSource = source;
+ final int visibleBand =
ImageUtilities.getVisibleBand(coloredSource);
if (visibleBand < 0) {
// This restriction may be relaxed in a future version if we
implement conversion to RGB images.
throw new
IllegalArgumentException(Resources.format(Resources.Keys.OperationRequiresSingleBand));
}
+ /*
+ * Skip any previous `RecoloredImage` since we will replace the
`ColorModel` by a new one.
+ * Keep only the band to make visible in order to reduce the
amount of calculation during
+ * resampling and for saving memory.
+ */
+ while (source instanceof RecoloredImage) {
+ source = ((RecoloredImage) source).source;
+ }
+ source = BandSelectImage.create(source, new int[] {visibleBand});
+ /*
+ * If there is no conversion of pixel coordinates, there is no
need for interpolations.
+ * In such case the `Visualization.computeTile(…)` implementation
takes a shortcut which
+ * requires the tile layout of destination image to be the same as
source image.
+ * Otherwise combine interpolation and value conversions in a
single operation.
+ */
+ if (toSource == null) {
+ toSource = MathTransforms.identity(BIDIMENSIONAL);
+ }
+ final boolean shortcut = toSource.isIdentity() && (bounds == null
|| ImageUtilities.getBounds(source).contains(bounds));
+ if (shortcut) {
+ layout = ImageLayout.fixedSize(source);
+ }
+ /*
+ * Sample values will be unconditionally converted to integers in
the [0 … 255] range.
+ * The sample model is a mandatory argument before we invoke
user-supplied colorizer,
+ * which must be done before to build the color model.
+ */
+ sampleModel =
layout.createBandedSampleModel(ColorModelBuilder.TYPE_COMPACT, NUM_BANDS,
source, bounds);
+ final Target target = new Target(sampleModel, visibleBand,
sourceBands != null);
+ if (colorizer != null) {
+ colorModel = colorizer.apply(target).orElse(null);
+ }
/*
* Get a `ColorModelBuilder` which will compute the `ColorModel`
of destination image.
- * There is different ways to create colorizer, depending on which
arguments were supplied by user.
+ * There is different ways to setup the builder, depending on
which `Colorizer` is used.
* In precedence order:
*
- * - rangesAndColor :
Collection<Map.Entry<NumberRange<?>,Color[]>>
- * - sourceBands : List<SampleDimension>
+ * - rangeColors : Map<NumberRange<?>,Color[]>
+ * - sourceBands : List<SampleDimension>
* - statistics
*/
boolean initialized;
- final ColorModelBuilder colorizer;
- if (rangesAndColors != null) {
- colorizer = new ColorModelBuilder(rangesAndColors);
+ final ColorModelBuilder builder;
+ if (target.rangeColors != null) {
+ builder = new ColorModelBuilder(target.rangeColors.entrySet());
initialized = true;
} else {
/*
* Ranges of sample values were not specified explicitly.
Instead, we will try to infer them
- * in various ways: sample dimensions, scaled color model,
statistics in last resort.
+ * in various ways: sample dimensions, scaled color model, or
image statistics in last resort.
*/
- colorizer = new ColorModelBuilder(categoryColors);
- initialized = (sourceBands != null) &&
colorizer.initialize(source.getSampleModel(), sourceBands.get(visibleBand));
+ builder = new ColorModelBuilder(target.categoryColors);
+ initialized = (sourceBands != null) &&
builder.initialize(coloredSource.getSampleModel(),
sourceBands.get(visibleBand));
if (initialized) {
/*
* If we have been able to configure ColorModelBuilder
using SampleDimension, apply an adjustment
* based on the ScaledColorModel if it exists. Use case:
image is created with an IndexColorModel
- * determined by the SampleModel, then user enhanced
contrast by a call to `stretchColorRamp(…)`
- * above. We want to preserve that contrast enhancement.
+ * determined by the SampleModel, then user enhanced
contrast by a call to `stretchColorRamp(…)`.
+ * We want to preserve that contrast enhancement.
*/
- colorizer.rescaleMainRange(source.getColorModel());
+ builder.rescaleMainRange(coloredSource.getColorModel());
} else {
/*
* If we have not been able to use the SampleDimension,
try to use the ColorModel or SampleModel.
* There is no call to `rescaleMainRange(…)` because the
following code already uses the range
* specified by the ColorModel, if available.
*/
- initialized = colorizer.initialize(source.getColorModel());
+ initialized =
builder.initialize(coloredSource.getColorModel());
if (!initialized) {
- if (source instanceof RecoloredImage) {
- final RecoloredImage colored = (RecoloredImage)
source;
- colorizer.initialize(colored.minimum,
colored.maximum);
+ if (coloredSource instanceof RecoloredImage) {
+ final RecoloredImage colored = (RecoloredImage)
coloredSource;
+ builder.initialize(colored.minimum,
colored.maximum);
initialized = true;
} else {
- initialized =
colorizer.initialize(source.getSampleModel(), visibleBand);
+ initialized =
builder.initialize(coloredSource.getSampleModel(), visibleBand);
}
}
}
}
- source = BandSelectImage.create(source, new int[] {visibleBand});
// Make single-banded.
if (!initialized) {
/*
* If none of above `ColorModelBuilder` configurations worked,
use statistics in last resort.
@@ -255,38 +316,20 @@ final class Visualization extends ResampledImage {
*/
final DoubleUnaryOperator[] sampleFilters =
SampleDimensions.toSampleFilters(processor, sourceBands);
final Statistics statistics =
processor.valueOfStatistics(source, null, sampleFilters)[VISIBLE_BAND];
- colorizer.initialize(statistics.minimum(),
statistics.maximum());
+ builder.initialize(statistics.minimum(), statistics.maximum());
}
- /*
- * If we reach this point, sample values need to be converted to
integers in [0 … 255] range.
- * Skip any previous `RecoloredImage` since we are replacing the
`ColorModel` by a new one.
- */
- while (source instanceof RecoloredImage) {
- source = ((RecoloredImage) source).source;
+ if (colorModel == null) {
+ colorModel = builder.compactColorModel(NUM_BANDS,
VISIBLE_BAND);
}
- colorModel = colorizer.compactColorModel(NUM_BANDS, VISIBLE_BAND);
converters = new MathTransform1D[] {
- colorizer.getSampleToIndexValues() // Must be after
`compactColorModel(…)`.
+ builder.getSampleToIndexValues() // Must be after
`compactColorModel(…)`.
};
- /*
- * If there is no conversion of pixel coordinates, there is no
need for interpolations.
- * In such case the `Visualization.computeTile(…)` implementation
takes a shortcut which
- * requires the tile layout of destination image to be the same as
source image.
- */
- if (toSource == null) {
- toSource = MathTransforms.identity(BIDIMENSIONAL);
- }
- if (toSource.isIdentity() && (bounds == null ||
ImageUtilities.getBounds(source).contains(bounds))) {
- layout = ImageLayout.fixedSize(source);
+ if (shortcut) {
interpolation = Interpolation.NEAREST;
} else {
interpolation = combine(interpolation.toCompatible(source),
converters);
converters = null;
}
- /*
- * Final image creation after the tile layout has been chosen.
- */
- sampleModel =
layout.createBandedSampleModel(ColorModelBuilder.TYPE_COMPACT, NUM_BANDS,
source, bounds);
if (bounds == null) {
bounds = ImageUtilities.getBounds(source);
}