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
commit 34aaff134e0fc33dfc14f84ed7b61627784cd5ce Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Mar 25 16:04:38 2023 +0100 `CombinedImageLayout.createColorModel()` should preserve the visible band of source image if possible. This commit also remove some `java.util.Optional` from internal API because all usages of it where invoking `orElse(null)`. --- .../java/org/apache/sis/image/BandSelectImage.java | 2 +- .../org/apache/sis/image/CombinedImageLayout.java | 53 +++++++++------- .../sis/internal/coverage/RangeArgument.java | 8 +-- .../internal/coverage/j2d/ColorModelFactory.java | 73 +++++++++++++++++----- .../coverage/j2d/MultiBandsIndexColorModel.java | 23 +++---- .../internal/coverage/j2d/ScaledColorModel.java | 15 +++-- .../internal/coverage/j2d/ScaledColorSpace.java | 31 +++++---- .../sis/internal/storage/TiledGridResource.java | 3 +- .../sis/internal/storage/esri/RasterStore.java | 6 +- 9 files changed, 132 insertions(+), 82 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java index 5fb07dbb69..712c910420 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java +++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java @@ -85,7 +85,7 @@ final class BandSelectImage extends SourceAlignedImage { return source; } ArgumentChecks.ensureNonEmptyBounded("bands", false, 0, numBands - 1, bands); - final ColorModel cm = ColorModelFactory.createSubset(source.getColorModel(), bands).orElse(null); + final ColorModel cm = ColorModelFactory.createSubset(source.getColorModel(), bands); /* * If the image is an instance of `BufferedImage`, create the subset immediately * (reminder: this operation will not copy pixel data). It allows us to return a diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/CombinedImageLayout.java b/core/sis-feature/src/main/java/org/apache/sis/image/CombinedImageLayout.java index fe9e5a8939..8cacace734 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/image/CombinedImageLayout.java +++ b/core/sis-feature/src/main/java/org/apache/sis/image/CombinedImageLayout.java @@ -310,40 +310,45 @@ final class CombinedImageLayout extends ImageLayout { } /** - * Returns the index of the first band which is flagged as a visible band. - * The concept of visible bands is used when an image contains more than one band, - * but only one of those bands us used by an {@code IndexColorModel}. + * Builds a default color model with RGB(A) colors or the colors of the first visible band. + * If the combined image has 3 or 4 bands and the data type is 8 bits integer (bytes), + * then this method returns a RGB or RGBA color model depending if there is 3 or 4 bands. + * Otherwise if {@link ImageUtilities#getVisibleBand(RenderedImage)} finds that a source image + * declares a visible band, then the returned color model will reuse the colors of that band. + * Otherwise a grayscale color model is built with a value range inferred from the data-type. */ - private int getVisibleBand() { - int band = 0; - for (int i=0; i < sources.length; i++) { + final ColorModel createColorModel() { + ColorModel colors = ColorModelFactory.createRGB(sampleModel); + if (colors != null) { + return colors; + } + int visibleBand = ColorModelFactory.DEFAULT_VISIBLE_BAND; + int base = 0; +search: for (int i=0; i < sources.length; i++) { final RenderedImage source = sources[i]; final int[] bands = bandsPerSource[i]; - final int visibleBand = ImageUtilities.getVisibleBand(source); - if (visibleBand >= 0) { + final int vb = ImageUtilities.getVisibleBand(source); + if (vb >= 0) { if (bands == null) { - return band + visibleBand; + visibleBand = base + vb; + colors = source.getColorModel(); + break; } for (int j=0; j<bands.length; j++) { - if (bands[j] == visibleBand) { - return band + j; + if (bands[j] == vb) { + visibleBand = base + j; + colors = source.getColorModel(); + break search; } } } - band += (bands != null) ? bands.length : ImageUtilities.getNumBands(source); + base += (bands != null) ? bands.length : ImageUtilities.getNumBands(source); } - return 0; - } - - /** - * Builds a default color model. - * If the combined image has 3 or 4 bands and the data type is an integer storing values on 8 bits, - * then this method creates a RGB color model. If there is 4 bands, an RGBA color model is defined. - * Otherwise a grayscale color model is built with a value range inferred from the data-type. - */ - final ColorModel createColorModel() { - return ColorModelFactory.createRGB(sampleModel) - .orElseGet(() -> ColorModelFactory.createGrayScale(sampleModel, getVisibleBand(), null)); + colors = ColorModelFactory.derive(colors, sampleModel.getNumBands(), visibleBand); + if (colors != null) { + return colors; + } + return ColorModelFactory.createGrayScale(sampleModel, visibleBand, null); } /** diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/RangeArgument.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/RangeArgument.java index ee72fbac4b..e2ddedbda0 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/RangeArgument.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/RangeArgument.java @@ -18,7 +18,6 @@ package org.apache.sis.internal.coverage; import java.util.List; import java.util.Arrays; -import java.util.Optional; import java.awt.image.ColorModel; import java.awt.image.SampleModel; import java.awt.image.RasterFormatException; @@ -367,13 +366,14 @@ public final class RangeArgument { /** * Returns a color model for the bands specified by the user. + * This method may return {@code null} if the color model can not be created. * * @param colors the original color model with all bands. Can be {@code null}. - * @return the color model for a subset of bands, or empty if the given color model was null. + * @return the color model for a subset of bands, or null if the given color model was null. */ - public Optional<ColorModel> select(final ColorModel colors) { + public ColorModel select(final ColorModel colors) { if (colors == null || isIdentity()) { - return Optional.ofNullable(colors); + return colors; } return ColorModelFactory.createSubset(colors, getSelectedBands()); } 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 3a942df66d..0fc2398eed 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 @@ -19,7 +19,6 @@ package org.apache.sis.internal.coverage.j2d; import java.util.Map; import java.util.Arrays; import java.util.Comparator; -import java.util.Optional; import java.awt.Transparency; import java.awt.Color; import java.awt.color.ColorSpace; @@ -52,6 +51,12 @@ import org.apache.sis.util.Debug; * @since 1.0 */ public final class ColorModelFactory { + /** + * Band to make visible if an image contains many bands + * but a color map is defined for only one band. + */ + public static final int DEFAULT_VISIBLE_BAND = 0; + /** * The fully transparent color. */ @@ -525,11 +530,12 @@ public final class ColorModelFactory { /** * Creates a RGB color model for the given sample model. * The sample model shall use integer type and have 3 or 4 bands. + * This method may return {@code null} if the color model can not be created. * * @param model the sample model for which to create a color model. - * @return the color model, or empty if a precondition does not hold. + * @return the color model, or null if a precondition does not hold. */ - public static Optional<ColorModel> createRGB(final SampleModel model) { + public static ColorModel createRGB(final SampleModel model) { final int numBands = model.getNumBands(); if (numBands >= 3 && numBands <= 4 && ImageUtilities.isIntegerType(model)) { int bitsPerSample = 0; @@ -537,10 +543,10 @@ public final class ColorModelFactory { bitsPerSample = Math.max(bitsPerSample, model.getSampleSize(i)); } if (bitsPerSample <= Byte.SIZE) { - return Optional.of(createRGB(bitsPerSample, model.getNumDataElements() == 1, numBands > 3)); + return createRGB(bitsPerSample, model.getNumDataElements() == 1, numBands > 3); } } - return Optional.empty(); + return null; } /** @@ -585,7 +591,7 @@ public final class ColorModelFactory { * <li>Input color model is null.</li> * <li>Given color model is not assignable from the following types: * <ul> - * <li>{@link ComponentColorModel}</li> + * <li>{@link ComponentColorModel} with only one band</li> * <li>{@link MultiBandsIndexColorModel}</li> * <li>{@link ScaledColorModel}</li> * </ul> @@ -593,35 +599,68 @@ public final class ColorModelFactory { * <li>Input color model is recognized, but we cannot infer a proper color interpretation for given number of bands.</li> * </ul> * - * <em>Note about {@link PackedColorModel} and {@link DirectColorModel}</em>: they're not managed for now, because - * they're really designed for a "rigid" case where data buffer values are interpreted directly by the color model. + * This method may return {@code null} if the color model can not be created. + * + * <p><em>Note about {@link PackedColorModel} and {@link DirectColorModel}</em>: + * those color models not managed for now, because they are really designed for + * a "rigid" case where data buffer values are interpreted directly by the color model.</p> * * @param cm the color model, or {@code null}. * @param bands the bands to select. Must neither be null nor empty. - * @return the subset color model, or empty if input was null or if a subset cannot be deduced. + * @return the subset color model, or null if input was null or if a subset cannot be deduced. */ - public static Optional<ColorModel> createSubset(final ColorModel cm, final int[] bands) { + public static ColorModel createSubset(final ColorModel cm, final int[] bands) { assert (bands != null) && bands.length > 0 : bands; + int visibleBand = DEFAULT_VISIBLE_BAND; + if (cm instanceof MultiBandsIndexColorModel) { + visibleBand = ((MultiBandsIndexColorModel) cm).visibleBand; + } else if (cm != null) { + final ColorSpace cs = cm.getColorSpace(); + if (cs instanceof ScaledColorSpace) { + visibleBand = ((ScaledColorSpace) cs).visibleBand; + } + } + for (int i=0; i<bands.length; i++) { + if (bands[i] == visibleBand) { + visibleBand = i; + break; + } + } + return derive(cm, bands.length, visibleBand); + } + + /** + * Returns a color model with with the same colors but a different number of bands. + * Current implementation supports {@link ComponentColorModel} with only one band, + * {@link MultiBandsIndexColorModel} and {@link ScaledColorModel}. + * This method may return {@code null} if the color model can not be created. + * + * @param cm the color model, or {@code null}. + * @param numBands new number of bands. + * @param visibleBand new visible band. + * @return the derived color model, or null if this method does not support the given color model. + */ + public static ColorModel derive(final ColorModel cm, final int numBands, final int visibleBand) { if (cm == null) { - return Optional.empty(); + return null; } final ColorModel subset; if (cm instanceof MultiBandsIndexColorModel) { - subset = ((MultiBandsIndexColorModel) cm).createSubsetColorModel(bands); + subset = ((MultiBandsIndexColorModel) cm).derive(numBands, visibleBand); } else if (cm instanceof ScaledColorModel) { - subset = ((ScaledColorModel) cm).createSubsetColorModel(bands); - } else if (bands.length == 1 && cm instanceof ComponentColorModel) { + subset = ((ScaledColorModel) cm).derive(numBands, visibleBand); + } else if (numBands == 1 && cm instanceof ComponentColorModel) { final int dataType = cm.getTransferType(); if (dataType < DataBuffer.TYPE_BYTE || dataType > DataBuffer.TYPE_USHORT) { - return Optional.empty(); + return null; } final ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); subset = new ComponentColorModel(cs, false, true, Transparency.OPAQUE, dataType); } else { // TODO: handle other color models. - return Optional.empty(); + return null; } - return Optional.of(CACHE.unique(subset)); + return CACHE.unique(subset); } /** diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java index 9ec0bd6be9..75f71d90fa 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/MultiBandsIndexColorModel.java @@ -88,26 +88,27 @@ final class MultiBandsIndexColorModel extends IndexColorModel { } /** - * Creates a new color model with only a subset of the bands in this color model. + * Returns a color model with a different number of bands and a different visible band. * * <p><b>Note:</b> the new color model will use a copy of the color map of this color model. * There is no way to share the {@code int[]} array of ARGB values between two {@link IndexColorModel}s.</p> + * + * @param numBands new number of bands. + * @param visibleBand new visible band. + * @return a color model with the same colors but the specified number of bands. */ - final IndexColorModel createSubsetColorModel(final int[] bands) { + final IndexColorModel derive(final int numBands, final int visibleBand) { + if (numBands == this.numBands && visibleBand == this.visibleBand) { + return this; + } final int[] cmap = getARGB(); final boolean hasAlpha = hasAlpha(); final int transparent = getTransparentPixel(); - if (bands.length == 1) { + if (numBands == 1) { return new IndexColorModel(pixel_bits, cmap.length, cmap, 0, hasAlpha, transparent, transferType); } - int vb = 0; - for (int i=0; i<bands.length; i++) { - if (bands[i] == visibleBand) { - vb = i; - break; - } - } - return new MultiBandsIndexColorModel(pixel_bits, cmap.length, cmap, 0, hasAlpha, transparent, transferType, bands.length, vb); + return new MultiBandsIndexColorModel(pixel_bits, cmap.length, cmap, 0, + hasAlpha, transparent, transferType, numBands, visibleBand); } /** diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorModel.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorModel.java index 4028239e67..1da6d2dc07 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorModel.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorModel.java @@ -31,7 +31,7 @@ import org.apache.sis.internal.feature.Resources; * In addition, this class renders the {@link Float#NaN} values as transparent. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * @since 1.1 */ final class ScaledColorModel extends ComponentColorModel { @@ -66,10 +66,17 @@ final class ScaledColorModel extends ComponentColorModel { } /** - * Creates a new color model with only a subset of the bands in this color model. + * Returns a color model with a different number of bands and a different visible band. + * + * @param numBands new number of bands. + * @param visibleBand new visible band. + * @return a color model with the same colors but the specified number of bands. */ - final ScaledColorModel createSubsetColorModel(final int[] bands) { - return new ScaledColorModel(new ScaledColorSpace(cs, bands), transferType); + final ScaledColorModel derive(final int numBands, final int visibleBand) { + if (numBands == cs.getNumComponents() && visibleBand == cs.visibleBand) { + return this; + } + return new ScaledColorModel(new ScaledColorSpace(cs, numBands, visibleBand), transferType); } /** diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorSpace.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorSpace.java index b197f91468..c3990c1c2a 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorSpace.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ScaledColorSpace.java @@ -29,7 +29,7 @@ import org.apache.sis.util.Debug; * It should be used only when no standard color space can be used.</p> * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 1.1 + * @version 1.4 * * @see ScaledColorModel * @see ColorModelFactory#createGrayScale(int, int, int, double, double) @@ -76,26 +76,23 @@ final class ScaledColorSpace extends ColorSpace { ScaledColorSpace(final int numComponents, final int visibleBand, final double minimum, final double maximum) { super(TYPE_GRAY, numComponents); this.visibleBand = visibleBand; - this.maximum = maximum; - scale = ScaledColorModel.RANGE / (maximum - minimum); - offset = minimum; + this.maximum = maximum; + this.scale = ScaledColorModel.RANGE / (maximum - minimum); + this.offset = minimum; } /** - * Creates a color space for the same range of values than the given space, but a subset of the bands. + * Creates a color space for the same range of values than the given space, but a different number of bands. + * + * @param numComponents the new number of components. + * @param visibleBand the new band to select as the visible band. */ - ScaledColorSpace(final ScaledColorSpace parent, final int[] bands) { - super(TYPE_GRAY, bands.length); - scale = parent.scale; - offset = parent.offset; - maximum = parent.maximum; - for (int i=0; i<bands.length; i++) { - if (bands[i] == parent.visibleBand) { - visibleBand = i; - return; - } - } - visibleBand = 0; + ScaledColorSpace(final ScaledColorSpace parent, final int numComponents, final int visibleBand) { + super(TYPE_GRAY, numComponents); + this.scale = parent.scale; + this.offset = parent.offset; + this.maximum = parent.maximum; + this.visibleBand = visibleBand; } /** diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java index 86213d27b4..b065ce9b73 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java @@ -300,6 +300,7 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { /** * The color model for the bands to read (not the full set of bands in the resource). + * May be {@code null} if the color model could not be created. */ final ColorModel colorsForBandSubset; @@ -410,7 +411,7 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { this.ranges = bands; this.includedBands = includedBands; this.modelForBandSubset = rangeIndices.select(getSampleModel(), loadAllBands); - this.colorsForBandSubset = rangeIndices.select(getColorModel()).orElse(null); + this.colorsForBandSubset = rangeIndices.select(getColorModel()); this.fillValue = getFillValue(); /* * All `TiledGridCoverage` instances can share the same cache if they read all tiles fully. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java index d61a2ce50d..497a3b8b0e 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java @@ -77,7 +77,7 @@ abstract class RasterStore extends PRJDataStore implements GridCoverageResource * Band to make visible if an image contains many bands * but a color map is defined for only one band. */ - private static final int VISIBLE_BAND = 0; + private static final int VISIBLE_BAND = ColorModelFactory.DEFAULT_VISIBLE_BAND; /** * Keyword for the number of rows in the image. @@ -419,7 +419,7 @@ abstract class RasterStore extends PRJDataStore implements GridCoverageResource */ if (band == VISIBLE_BAND) { if (isRGB) { - colorModel = ColorModelFactory.createRGB(sm).get(); // Should not be empty. + colorModel = ColorModelFactory.createRGB(sm); // Should never be null. } else { try { colorModel = readColorMap(dataType, (int) (maximum + 1), bands.length); @@ -484,7 +484,7 @@ abstract class RasterStore extends PRJDataStore implements GridCoverageResource ColorModel cm = colorModel; if (!range.isIdentity()) { bands = Arrays.asList(range.select(sampleDimensions)); - cm = range.select(cm).orElse(null); + cm = range.select(cm); if (cm == null) { final SampleDimension band = bands.get(VISIBLE_BAND); cm = ColorModelFactory.createGrayScale(data.getSampleModel(), VISIBLE_BAND, band.getSampleRange().orElse(null));