This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit c773a3c2e7c61464a13731675b56dc5a70050e13 Author: Martin Desruisseaux <[email protected]> AuthorDate: Sat Aug 8 02:25:45 2026 +0200 Provided more information about the `RenderedImage` layout in its `toString()` representation. This is an attempt to provide more debugging help. --- .../apache/sis/coverage/grid/GridCoverage2D.java | 56 +--- .../apache/sis/coverage/grid/GridDerivation.java | 2 +- .../org/apache/sis/coverage/grid/GridExtent.java | 4 +- .../org/apache/sis/feature/internal/Resources.java | 15 ++ .../sis/feature/internal/Resources.properties | 3 + .../sis/feature/internal/Resources_fr.properties | 3 + .../main/org/apache/sis/image/PlanarImage.java | 58 +---- .../image/internal/shared/ColorModelFactory.java | 54 +++- .../sis/image/internal/shared/ImageUtilities.java | 30 --- .../image/internal/shared/ScaledColorSpace.java | 11 +- .../sis/image/internal/shared/Summarizer.java | 288 +++++++++++++++++++++ .../apache/sis/coverage/grid/GridExtentTest.java | 36 ++- .../sis/util/resources/IndexedResourceBundle.java | 14 +- .../org/apache/sis/util/resources/Vocabulary.java | 10 +- .../sis/util/resources/Vocabulary.properties | 2 +- .../sis/util/resources/Vocabulary_fr.properties | 4 +- 16 files changed, 408 insertions(+), 182 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java index 3ff94f38e6..8c9e4362e0 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java @@ -22,10 +22,6 @@ import java.util.ArrayList; import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import java.text.NumberFormat; -import java.text.FieldPosition; -import java.io.IOException; -import java.io.UncheckedIOException; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; @@ -48,6 +44,7 @@ import org.apache.sis.image.DataType; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.image.internal.shared.ImageUtilities; import org.apache.sis.image.internal.shared.ReshapedImage; +import org.apache.sis.image.internal.shared.Summarizer; import org.apache.sis.feature.internal.Resources; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Debug; @@ -710,7 +707,7 @@ public class GridCoverage2D extends GridCoverage { } /** - * Appends a "data layout" branch (if it exists) to the tree representation of this coverage. + * Appends a "data layout" branch to the tree representation of this coverage. * That branch will be inserted between "coverage domain" and "sample dimensions" branches. * * @param root root of the tree where to add a branch. @@ -719,50 +716,9 @@ public class GridCoverage2D extends GridCoverage { */ @Debug @Override - void appendDataLayout(final TreeTable.Node root, final Vocabulary vocabulary, final TableColumn<CharSequence> column) { - final TreeTable.Node branch = root.newChild(); - branch.setValue(column, vocabulary.getString(Vocabulary.Keys.ImageLayout)); - final var nf = NumberFormat.getIntegerInstance(vocabulary.getLocale()); - final var pos = new FieldPosition(0); - final var buffer = new StringBuffer(); -write: for (int item=0; ; item++) try { - switch (item) { - case 0: { - vocabulary.appendLabel(Vocabulary.Keys.Origin, buffer); - nf.format(data.getMinX(), buffer.append(' '), pos); - nf.format(data.getMinY(), buffer.append(", "), pos); - break; - } - case 1: { - final int tx = data.getTileWidth(); - final int ty = data.getTileHeight(); - if (tx == data.getWidth() && ty == data.getHeight()) continue; - vocabulary.appendLabel(Vocabulary.Keys.TileSize, buffer); - nf.format(tx, buffer.append( ' ' ), pos); - nf.format(ty, buffer.append(" × "), pos); - break; - } - case 2: { - final String type = ImageUtilities.getDataTypeName(data.getSampleModel()); - if (type == null) continue; - vocabulary.appendLabel(Vocabulary.Keys.DataType, buffer); - buffer.append(' ').append(type); - break; - } - case 3: { - final short t = ImageUtilities.getTransparencyDescription(data.getColorModel()); - if (t != 0) { - final String desc = Resources.forLocale(vocabulary.getLocale()).getString(t); - branch.newChild().setValue(column, desc); - } - continue; - } - default: break write; - } - branch.newChild().setValue(column, buffer.toString()); - buffer.setLength(0); - } catch (IOException e) { - throw new UncheckedIOException(e); // Should never happen since we are writing to StringBuilder. - } + void appendDataLayout(TreeTable.Node root, final Vocabulary vocabulary, final TableColumn<CharSequence> column) { + root = root.newChild(); + root.setValue(column, vocabulary.getString(Vocabulary.Keys.RenderedImage)); + new Summarizer(root, vocabulary, column).layout(data); } } diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java index 9babcebf44..bfdcf66b50 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java @@ -1419,7 +1419,7 @@ public class GridDerivation { /** * Returns the extent of the modified grid geometry, ignoring subsampling or changes in resolution. - * This is the intersection of the {@link #base} grid geometry with the (grid or geospatial) envelope + * This is the {@link #base} grid geometry intersected with the grid extent or geospatial) envelope * given to a {@link #subgrid(Envelope, double...) subgrid(…)} method, * expanded by the {@linkplain #margin(int...) specified margin} (if any) * and potentially with some {@linkplain GridExtent#getSize(int) grid sizes} set to 1 diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java index d990d7b7bc..6c2ab5285b 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java @@ -1821,7 +1821,7 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable if (s > 1) { final int j = i + m; long low = coordinates[i]; - long size = coordinates[j] - low + 1; // Result is an unsigned number. + long size = coordinates[j] - low + 1; // Result handled as an unsigned number. if (size == 0) { throw new ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Long.SIZE)); } @@ -2484,7 +2484,7 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable final long lower = coordinates[i]; final long upper = coordinates[i + dimension]; table.setCellAlignment(TableAppender.ALIGN_LEFT); - table.append(name).append(": ").nextColumn(); + table.append(vocabulary.toLabel(name)).append(' ').nextColumn(); table.append('[').nextColumn(); table.setCellAlignment(TableAppender.ALIGN_RIGHT); table.append(Long.toString(lower)).append(" … ").nextColumn(); diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java index ab67b06ea4..9981631559 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java @@ -78,6 +78,11 @@ public class Resources extends IndexedResourceBundle { */ public static final short AmbiguousGridAxisOmission_1 = 84; + /** + * {0,number,integer} band{0,choice,1#|2#s} + */ + public static final short BandCount_1 = 94; + /** * Cannot assign characteristics to the “{0}” property. */ @@ -422,6 +427,11 @@ public class Resources extends IndexedResourceBundle { */ public static final short OutOfIteratorDomain_2 = 57; + /** + * Palette of {0,number,integer} colors + */ + public static final short PaletteOfColors_1 = 96; + /** * Point ({0}) is outside the coverage domain. */ @@ -447,6 +457,11 @@ public class Resources extends IndexedResourceBundle { */ public static final short SourceImagesDoNotIntersect = 80; + /** + * {0} tiles of {1} pixels + */ + public static final short TileCountAndSize_2 = 95; + /** * Tile ({0}, {1}) is unavailable because of error in a previous calculation attempt. */ diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties index 5f66a5a6ea..c7aa83a018 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties @@ -21,6 +21,7 @@ # AbstractFeatureType_1 = Feature type \u2018{0}\u2019 is abstract. AmbiguousGridAxisOmission_1 = Omission of the \u201c{0}\u201d grid axes would create ambiguity. +BandCount_1 = {0,number,integer} band{0,choice,1#|2#s} CanNotAssignCharacteristics_1 = Cannot assign characteristics to the \u201c{0}\u201d property. CanNotBuildGridCoverage = Cannot build the grid coverage. CanNotComputeTile_2 = Cannot compute tile ({0}, {1}). @@ -88,11 +89,13 @@ NotStrictlyOrderedDimensions = The specified dimensions are not in strictly OperationRequiresSingleBand = This operation requires an image with only one band. OptionalLibraryNotFound_2 = The {0} optional library is not available. Geometric operations will ignore that library.\nCause is {1}. OutOfIteratorDomain_2 = The ({0,number}, {1,number}) pixel coordinate is outside iterator domain. +PaletteOfColors_1 = Palette of {0,number,integer} colors PointOutsideCoverageDomain_1 = Point ({0}) is outside the coverage domain. PropertyAlreadyExists_2 = Property \u201c{1}\u201d already exists in feature \u201c{0}\u201d. PropertyNameCannotBeXPath_1 = Property name \u201c{0}\u201d is invalid because names cannot be XPath. PropertyNotFound_2 = No property named \u201c{1}\u201d has been found in \u201c{0}\u201d feature. SourceImagesDoNotIntersect = Source images do not intersect. +TileCountAndSize_2 = {0} tiles of {1} pixels TileErrorFlagSet_2 = Tile ({0}, {1}) is unavailable because of error in a previous calculation attempt. TileNotWritable_2 = Tile ({0}, {1}) is not writable. TooManyQualitatives = Too many qualitative categories. diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties index 9ec75f2a56..d3d959c150 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties @@ -26,6 +26,7 @@ # AbstractFeatureType_1 = Le type d\u2019entit\u00e9 \u2018{0}\u2019 est abstrait. AmbiguousGridAxisOmission_1 = L\u2019omission de l\u2019axe \u201c{0}\u201d de la grille cr\u00e9e une ambigu\u00eft\u00e9. +BandCount_1 = {0,number,integer} bande{0,choice,1#|2#s} CanNotAssignCharacteristics_1 = Ne peut pas assigner des caract\u00e9ristiques \u00e0 la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb. CanNotBuildGridCoverage = Ne peut pas construire la couverture de donn\u00e9es. CanNotComputeTile_2 = Ne peut pas calculer la tuile ({0}, {1}). @@ -93,11 +94,13 @@ NotStrictlyOrderedDimensions = Les dimensions sp\u00e9cifi\u00e9es ne sont OperationRequiresSingleBand = Cette op\u00e9ration n\u00e9cessite une image avec une seule bande. OptionalLibraryNotFound_2 = La biblioth\u00e8que optionnelle {0} n\u2019est pas disponible. Les op\u00e9rations g\u00e9om\u00e9triques ignoreront cette biblioth\u00e8que.\nLa cause est {1}. OutOfIteratorDomain_2 = La coordonn\u00e9e pixel ({0,number}, {1,number}) est en dehors du domaine de l\u2019it\u00e9rateur. +PaletteOfColors_1 = Palette de {0,number,integer} couleurs PointOutsideCoverageDomain_1 = Le point ({0}) est en dehors du domaine de la couverture de donn\u00e9es. PropertyAlreadyExists_2 = La propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb existe d\u00e9j\u00e0 dans l\u2019entit\u00e9 \u00ab\u202f{0}\u202f\u00bb. PropertyNameCannotBeXPath_1 = Le nom de propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb est invalide parce que ces noms ne doivent pas \u00eatre des XPaths. PropertyNotFound_2 = Aucune propri\u00e9t\u00e9 nomm\u00e9e \u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9e dans l\u2019entit\u00e9 \u00ab\u202f{0}\u202f\u00bb. SourceImagesDoNotIntersect = Des images sources ne s\u2019intersectent pas. +TileCountAndSize_2 = {0} tuiles de {1} pixels TileErrorFlagSet_2 = La tuile ({0}, {1}) est indisponible pour cause d\u2019erreur lors d\u2019une tentative ant\u00e9rieure de calcul. TileNotWritable_2 = La tuile ({0}, {1}) n\u2019est pas d\u00e9clar\u00e9e en \u00e9criture. TooManyQualitatives = Trop de cat\u00e9gories qualitatives. diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PlanarImage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PlanarImage.java index 38545fbc49..c6e50e198b 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PlanarImage.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PlanarImage.java @@ -20,7 +20,6 @@ import java.awt.Image; import java.awt.Shape; import java.awt.Rectangle; import java.awt.image.ColorModel; -import java.awt.image.IndexColorModel; import java.awt.image.SampleModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; @@ -33,12 +32,11 @@ import static java.lang.Math.multiplyFull; import org.apache.sis.util.Classes; import org.apache.sis.util.Disposable; import org.apache.sis.util.resources.Errors; -import org.apache.sis.util.resources.Messages; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.grid.GridGeometry; // For javadoc import org.apache.sis.image.internal.shared.ImageUtilities; import org.apache.sis.image.internal.shared.TileOpExecutor; -import org.apache.sis.image.internal.shared.ColorModelFactory; +import org.apache.sis.image.internal.shared.Summarizer; import org.apache.sis.feature.internal.Resources; import org.apache.sis.pending.jdk.JDK18; @@ -734,59 +732,7 @@ public abstract class PlanarImage implements RenderedImage { */ @Override public String toString() { - final var buffer = new StringBuilder(100).append(Classes.getShortClassName(this)).append('['); - final Object name = getProperty(SOURCE_NAME_KEY); - if (name != null && name != Image.UndefinedProperty) { - buffer.append('“').append(name).append("”: "); - } - buffer.append('(').append(getWidth()).append(" × ").append(getHeight()).append(") pixels starting at ") - .append('(').append(getMinX()) .append(", ") .append(getMinY()).append(')'); - final SampleModel sm = getSampleModel(); - if (sm != null) { - buffer.append(" in ").append(sm.getNumBands()).append(" bands"); - final String type = ImageUtilities.getDataTypeName(sm); - if (type != null) { - buffer.append(" of type ").append(type); - } - } - /* - * Write details about color model only if there is "useful" information for a geospatial raster. - * The main category of interest are "color palette" versus "gray scale" versus everything else, - * and whether the image may have transparent pixels. - */ - final ColorModel cm = getColorModel(); -colors: if (cm != null) { - buffer.append(". Colors: "); - if (cm instanceof IndexColorModel) { - buffer.append(((IndexColorModel) cm).getMapSize()).append(" indexed colors"); - } else { - ColorModelFactory.formatDescription(cm.getColorSpace(), buffer); - } - final String transparency; - switch (cm.getTransparency()) { - case ColorModel.OPAQUE: transparency = "opaque"; break; - case ColorModel.TRANSLUCENT: transparency = "translucent"; break; - case ColorModel.BITMASK: transparency = "bitmask transparency"; break; - default: break colors; - } - buffer.append("; ").append(transparency); - } - /* - * Tiling information last because it is usually a secondary aspect compared to above information. - * If a warning is emitted, it will usually be a tiling problem so it is useful to keep it close. - */ - final int tx = getNumXTiles(); - final int ty = getNumYTiles(); - if (tx != 1 || ty != 1) { - buffer.append("; ").append(tx).append(" × ").append(ty).append(" tiles"); - } - buffer.append(']'); - final String error = verify(); - if (error != null) { - buffer.append(System.lineSeparator()).append("└─") - .append(Messages.format(Messages.Keys.PossibleInconsistency_1, error)); - } - return buffer.toString(); + return Summarizer.toString(this); } /* diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ColorModelFactory.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ColorModelFactory.java index da40bae51a..b127ef326b 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ColorModelFactory.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ColorModelFactory.java @@ -41,6 +41,8 @@ import org.apache.sis.util.internal.shared.Numerics; import org.apache.sis.util.internal.shared.Strings; import org.apache.sis.util.collection.WeakHashSet; import org.apache.sis.util.collection.WeakValueHashMap; +import org.apache.sis.util.resources.Vocabulary; +import org.apache.sis.feature.internal.Resources; /** @@ -715,21 +717,55 @@ public final class ColorModelFactory { } /** - * Appends a description of the given color space in the given buffer. + * Appends a description of the given color model. * This is used for {@code toString()} method implementations. * - * @param cs the color space to describe, or {@code null}. - * @param buffer where to append the description. + * @param cm the color model to describe, or {@code null}. + * @param resources resources in the desired locale. + * @param buffer where to append the description if a buffer is needed. + * @return the description as {@code buffer} or as a {@link String}, or {@code null} if none. */ @Debug - public static void formatDescription(final ColorSpace cs, final StringBuilder buffer) { - if (cs != null) { - if (cs instanceof ScaledColorSpace) { - ((ScaledColorSpace) cs).formatRange(buffer.append("showing ")); - } else if (cs.getType() == ColorSpace.TYPE_GRAY) { - buffer.append("grayscale"); + public static CharSequence formatDescription(final ColorModel cm, final Resources resources, final StringBuilder buffer) { + if (cm != null) { + if (cm instanceof IndexColorModel) { + return resources.getString(Resources.Keys.PaletteOfColors_1, ((IndexColorModel) cm).getMapSize()); + } + final ColorSpace cs = cm.getColorSpace(); + if (cs != null) { + if (cs instanceof ScaledColorSpace) { + return ((ScaledColorSpace) cs).formatRange(buffer.append("showing ")); + } else if (cs.getType() == ColorSpace.TYPE_GRAY) { + return Vocabulary.forLocale(resources.getLocale()).getString(Vocabulary.Keys.Grayscale); + } + } + } + return null; + } + + /** + * Returns the key of a localizable text that describes the transparency. + * + * @param cm the color model from which to get the transparency, or {@code null}. + * @param resources resources in the desired locale. + * @return a description of the transparency, or {@code null} if unknown. + */ + public static String describeTransparency(final ColorModel cm, final Resources resources) { + if (cm == null) { + return null; + } + final short key; + if (cm.hasAlpha()) { + key = Resources.Keys.ImageHasAlphaChannel; + } else { + switch (cm.getTransparency()) { + case ColorModel.TRANSLUCENT: + case ColorModel.BITMASK: key = Resources.Keys.ImageAllowsTransparency; break; + case ColorModel.OPAQUE: key = Resources.Keys.ImageIsOpaque; break; + default: return null; } } + return resources.getString(key); } /** diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java index e52c1a543b..137751b326 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java @@ -35,7 +35,6 @@ import static java.lang.Math.rint; import static java.lang.Math.floorDiv; import static java.lang.Math.toIntExact; import static java.lang.Math.multiplyFull; -import org.apache.sis.feature.internal.Resources; import org.apache.sis.image.DataType; import org.apache.sis.image.PlanarImage; import org.apache.sis.system.Modules; @@ -204,35 +203,6 @@ public final class ImageUtilities { return null; } - /** - * Returns the key of a localizable text that describes the transparency. - * This method returns one of the following values: - * <ul> - * <li>{@link Resources.Keys#ImageAllowsTransparency}</li> - * <li>{@link Resources.Keys#ImageHasAlphaChannel}</li> - * <li>{@link Resources.Keys#ImageIsOpaque}</li> - * <li>0 if the transparency is unknown.</li> - * </ul> - * - * @param cm the color model from which to get the transparency, or {@code null}. - * @return a {@link Resources.Keys} value for the transparency, or 0 if unknown. - * - * @see #hasAlpha(RenderedImage) - */ - public static short getTransparencyDescription(final ColorModel cm) { - if (cm != null) { - if (cm.hasAlpha()) { - return Resources.Keys.ImageHasAlphaChannel; - } - switch (cm.getTransparency()) { - case ColorModel.TRANSLUCENT: - case ColorModel.BITMASK: return Resources.Keys.ImageAllowsTransparency; - case ColorModel.OPAQUE: return Resources.Keys.ImageIsOpaque; - } - } - return 0; - } - /** * Returns names of bands based on inspection of the sample model and color model. * The bands are identified by {@link Vocabulary.Keys} values for diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ScaledColorSpace.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ScaledColorSpace.java index 6ec5cdbb07..b5cb1e923a 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ScaledColorSpace.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ScaledColorSpace.java @@ -176,9 +176,7 @@ final class ScaledColorSpace extends ColorSpace { */ @Override public String toString() { - final StringBuilder buffer = new StringBuilder(20).append(getClass().getSimpleName()); - formatRange(buffer); - return buffer.toString(); + return formatRange(new StringBuilder(20).append(getClass().getSimpleName())).toString(); } /** @@ -186,10 +184,11 @@ final class ScaledColorSpace extends ColorSpace { * This method is used for {@link #toString()} implementation and may change in any future version. * * @param buffer where to append the range of values. + * @return {@code buffer}. */ @Debug - final void formatRange(final StringBuilder buffer) { - buffer.append('[').append(offset) + final StringBuilder formatRange(final StringBuilder buffer) { + return buffer.append('[').append(offset) .append(" … ").append(maximum) .append(" in band ").append(visibleBand).append(']'); } @@ -210,7 +209,7 @@ final class ScaledColorSpace extends ColorSpace { @Override public boolean equals(final Object obj) { if (obj instanceof ScaledColorSpace) { - final ScaledColorSpace that = (ScaledColorSpace) obj; + final var that = (ScaledColorSpace) obj; return Numerics.equals(scale, that.scale) && Numerics.equals(offset, that.offset) && Numerics.equals(maximum, that.maximum) && diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/Summarizer.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/Summarizer.java new file mode 100644 index 0000000000..87b25accda --- /dev/null +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/Summarizer.java @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.image.internal.shared; + +import java.util.Locale; +import java.text.NumberFormat; +import java.awt.Image; +import java.awt.image.RenderedImage; +import org.apache.sis.image.PlanarImage; +import org.apache.sis.util.CharSequences; +import org.apache.sis.util.Classes; +import org.apache.sis.util.Localized; +import org.apache.sis.util.collection.DefaultTreeTable; +import org.apache.sis.util.collection.TableColumn; +import org.apache.sis.util.collection.TreeTable; +import org.apache.sis.util.resources.Messages; +import org.apache.sis.util.resources.Vocabulary; +import org.apache.sis.feature.internal.Resources; + + +/** + * Helper class for formatting a description of an image. + * Used mostly for implementation of {@code toString()} methods for debugging purposes. + * + * @author Martin Desruisseaux (Geomatys) + */ +public final class Summarizer implements Localized { + /** + * Root of the tree where to add a branch. + */ + private final TreeTable.Node root; + + /** + * Localized resources for vocabulary. + */ + private final Vocabulary vocabulary; + + /** + * The single column where to write texts. + */ + private final TableColumn<CharSequence> column; + + /** + * Creates a new formatter for appending the image description in an existing tree. + * + * @param root root of the tree where to add a branch. + * @param vocabulary localized resources for vocabulary. + * @param column the single column where to write texts. + */ + public Summarizer(final TreeTable.Node root, final Vocabulary vocabulary, final TableColumn<CharSequence> column) { + this.root = root; + this.vocabulary = vocabulary; + this.column = column; + } + + /** + * Returns the locale used by this formatter. + * + * @return the locale. + */ + @Override + public final Locale getLocale() { + return vocabulary.getLocale(); + } + + /** + * A row to format. + */ + private static final class Row { + /** A {@link Vocabulary.Keys} constant, or 0 if none. */ + private final short key; + + /** Label created from the vocabulary key. */ + private String label; + + /** Description to write after the label. */ + private final String desc; + + /** Optional note as a child of the description. */ + String note; + + /** Prepares a new node. */ + Row(final short key, final CharSequence desc) { + this.key = key; + this.desc = desc.toString(); + } + + /** Resolves {@code #label} and returns its length. */ + final int formatLabel(final Vocabulary vocabulary) { + if (key == 0) return 0; + label = vocabulary.getLabel(key); + return label.length(); + } + + /** Returns a string representation using the given buffer as a temporary buffer. */ + final String toString(final StringBuilder buffer, final int margin) { + if (label != null) { + buffer.append(label).append(CharSequences.spaces(margin - label.length())); + } + return buffer.append(desc).toString(); + } + } + + /** + * Appends a "data layout" branch to the tree representation of an image description. + * Contains information about image location, image size, tile size, sample model, color model. + * + * @param data the image to describe. + */ + public void layout(final RenderedImage data) { + final int width = data.getWidth(); + final int height = data.getHeight(); + final var model = data.getSampleModel(); + final var nf = NumberFormat.getIntegerInstance(getLocale()); + final var resources = Resources.forLocale(getLocale()); + final var buffer = new StringBuilder(); + final var rows = new Row[8]; + int count = 0; + for (int c = 0; c < rows.length; c++) { + final Row row; + switch (c) { + /* + * Image name defined by a property (optional). + */ + case 0: { + final Object name = data.getProperty(PlanarImage.SOURCE_NAME_KEY); + if (name == null || name == Image.UndefinedProperty) continue; + row = new Row(Vocabulary.Keys.Name, + vocabulary.getString(Vocabulary.Keys.Quoted_1, name)); + break; + } + /* + * Image location, omitted in the common case of image starting at (0, 0). + */ + case 1: { + final int x = data.getMinX(); + final int y = data.getMinY(); + if ((x | y) == 0) continue; + row = new Row(Vocabulary.Keys.Origin, + buffer.append(nf.format(x)).append(", ") + .append(nf.format(y))); + break; + } + /* + * Image size and number of bands, always shown. + * The sample model should never be null, but we check anyway for safety. + */ + case 2: { + appendCount(buffer, nf, width, height).append(" pixels"); + if (model != null) { + buffer.append(" × ") + .append(resources.getString(Resources.Keys.BandCount_1, model.getNumBands())); + } + row = new Row(Vocabulary.Keys.ImageSize, buffer); + break; + } + /* + * Image tiling, omitted if the properties are consistent with an untiled image. + */ + case 3: { + final int nx = data.getNumXTiles(); + final int ny = data.getNumYTiles(); + final int tx = data.getTileWidth(); + final int ty = data.getTileHeight(); + if (nx == 1 && ny == 1 && tx == width && ty == height) continue; + final String n = appendCount(buffer, nf, nx, ny).toString(); buffer.setLength(0); + final String s = appendCount(buffer, nf, tx, ty).toString(); + row = new Row(Vocabulary.Keys.Tiling, resources.getString(Resources.Keys.TileCountAndSize_2, n, s)); + break; + } + /* + * Number of bits for each band and the type of number where they are stored. + */ + case 4: { + if (model == null) continue; + int[] size = model.getSampleSize(); + buffer.append('{'); + for (int i=0; i < size.length; i++) { + if (i != 0) buffer.append(", "); + buffer.append(nf.format(size[i])); + } + buffer.append("} bits"); + String type = ImageUtilities.getDataTypeName(model); + if (type != null) { + buffer.append(vocabulary.getString(Vocabulary.Keys.InBetweenWords)) + .append('‘').append(type).append('’'); + } + row = new Row(Vocabulary.Keys.DataType, buffer); + break; + } + /* + * Sample model (mandatory). If the sample model is nevertheless missing, + * some placeholder will be shown for suggesting that there is a problem. + */ + case 5: { + row = new Row(Vocabulary.Keys.Layout, + CharSequences.camelCaseToSentence(Classes.getShortClassName(model))); + break; + } + /* + * Color model (optional) with a note about transparency, + * which is an important information for images overlay. + */ + case 6: { + final var cm = data.getColorModel(); + CharSequence desc = ColorModelFactory.formatDescription(cm, resources, buffer); + String note = ColorModelFactory.describeTransparency(cm, resources); + if (desc == null) { + if (note == null) continue; + desc = note; + note = null; + } + row = new Row(Vocabulary.Keys.Colors, desc); + row.note = note; + break; + } + /* + * Warning about potential inconsistency. + */ + case 7: { + if (!(data instanceof PlanarImage)) continue; + String warning = ((PlanarImage) data).verify(); + if (warning == null) continue; + row = new Row((short) 0, + Messages.forLocale(getLocale()).getString(Messages.Keys.PossibleInconsistency_1, warning)); + break; + } + /* + * End of rows. + */ + default: continue; + } + rows[count++] = row; + buffer.setLength(0); + } + int margin = 0; + for (int i=0; i<count; i++) { + margin = Math.max(margin, rows[i].formatLabel(vocabulary)); + } + margin++; + for (int i=0; i<count; i++) { + final Row row = rows[i]; + final TreeTable.Node child = root.newChild(); + child.setValue(column, row.toString(buffer, margin)); + buffer.setLength(0); + if (row.note != null) { + child.newChild().setValue(column, row.note); + } + } + } + + /** + * Appends an image size, tile size or number of tiles. + */ + private static StringBuilder appendCount(StringBuilder buffer, NumberFormat nf, int nx, int ny) { + return buffer.append( '(').append(nf.format(nx)) + .append(" × ").append(nf.format(ny)).append(')'); + } + + /** + * Returns a summary of the given image as a tree. + * + * @param image the image to summarize. + * @return a tree presentation of the properties of the image. + */ + public static String toString(final RenderedImage image) { + final TableColumn<CharSequence> column = TableColumn.VALUE_AS_TEXT; + final var tree = new DefaultTreeTable(column); + final var c = new Summarizer(tree.getRoot(), Vocabulary.forLocale(null), column); + c.root.setValue(column, Classes.getShortClassName(image)); + c.layout(image); + return tree.toString(); + } +} diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java index 1eea817b3c..e6d97c55e4 100644 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java @@ -84,6 +84,16 @@ public final class GridExtentTest extends TestCase { false); } + /** + * Verifies that the given extent has the dimension name types specified by {@link #create3D()}. + */ + private static void verifyDimensions(final GridExtent extent) { + assertEquals(3, extent.getDimension(), "dimension"); + assertEquals(DimensionNameType.COLUMN, extent.getAxisType(0).get()); + assertEquals(DimensionNameType.ROW, extent.getAxisType(1).get()); + assertEquals(DimensionNameType.TIME, extent.getAxisType(2).get()); + } + /** * Verifies the low and high values in the specified dimension of the given extent */ @@ -99,11 +109,12 @@ public final class GridExtentTest extends TestCase { */ @Test public void testSubsample() { - GridExtent extent = create3D(); + GridExtent extent = create3D(); // low = (100, 200, 40) and size = (400 × 600 × 10). extent = extent.subsample(4, 3, 9); - assertExtentEquals(extent, 0, 25, 124); // 100 cells - assertExtentEquals(extent, 1, 66, 265); // 200 cells - assertExtentEquals(extent, 2, 4, 5); // 2 cells + verifyDimensions(extent); + assertExtentEquals(extent, 0, 25, 124); // 100 cells, exact division of everything. + assertExtentEquals(extent, 1, 66, 265); // 200 cells, exact division of size, truncation of low coordinate. + assertExtentEquals(extent, 2, 4, 5); // 2 cells, size rounded upward, low coordinate truncated. } /** @@ -111,11 +122,12 @@ public final class GridExtentTest extends TestCase { */ @Test public void testUpsample() { - GridExtent extent = create3D(); + GridExtent extent = create3D(); // low = (100, 200, 40) and size = (400 × 600 × 10). extent = extent.upsample(4, 3, 9); - assertExtentEquals(extent, 0, 400, 1999); // 1600 cells - assertExtentEquals(extent, 1, 600, 2399); // 1800 cells - assertExtentEquals(extent, 2, 360, 449); // 90 cells + verifyDimensions(extent); + assertExtentEquals(extent, 0, 400, 1999); // 1600 cells. + assertExtentEquals(extent, 1, 600, 2399); // 1800 cells. + assertExtentEquals(extent, 2, 360, 449); // 90 cells. } /** @@ -232,6 +244,7 @@ public final class GridExtentTest extends TestCase { GridExtent extent = create3D(); assertSame(extent, extent.withRange(1, 200, 799)); extent = extent.withRange(2, 30, 60); + verifyDimensions(extent); assertExtentEquals(extent, 0, 100, 499); assertExtentEquals(extent, 1, 200, 799); assertExtentEquals(extent, 2, 30, 60); @@ -245,6 +258,7 @@ public final class GridExtentTest extends TestCase { GridExtent extent = create3D(); assertSame(extent, extent.expand(new long[3])); extent = extent.expand(20, -10); // One less dimension than `exent` dimension. + verifyDimensions(extent); assertExtentEquals(extent, 0, 80, 519); assertExtentEquals(extent, 1, 210, 789); assertExtentEquals(extent, 2, 40, 49); @@ -257,6 +271,7 @@ public final class GridExtentTest extends TestCase { public void testForChunkSize() { GridExtent extent = create3D(); extent = extent.forChunkSize(300, 200, 15); + verifyDimensions(extent); assertExtentEquals(extent, 0, 0, 599); assertExtentEquals(extent, 1, 200, 799); assertExtentEquals(extent, 2, 30, 59); @@ -269,6 +284,7 @@ public final class GridExtentTest extends TestCase { public void testResize() { GridExtent extent = create3D(); extent = extent.resize(200, 150); + verifyDimensions(extent); assertExtentEquals(extent, 0, 50, 249); assertExtentEquals(extent, 1, 50, 199); assertExtentEquals(extent, 2, 40, 49); @@ -322,6 +338,7 @@ public final class GridExtentTest extends TestCase { public void testIntersect() { final GridExtent domain = createOther(); final GridExtent extent = create3D().intersect(domain); + verifyDimensions(extent); assertExtentEquals(extent, 0, 150, 399); assertExtentEquals(extent, 1, 220, 799); assertExtentEquals(extent, 2, 40, 46); @@ -339,6 +356,7 @@ public final class GridExtentTest extends TestCase { public void testUnion() { final GridExtent domain = createOther(); final GridExtent extent = create3D().union(domain); + verifyDimensions(extent); assertExtentEquals(extent, 0, 100, 499); assertExtentEquals(extent, 1, 200, 819); assertExtentEquals(extent, 2, 35, 49); @@ -389,7 +407,7 @@ public final class GridExtentTest extends TestCase { final var slicePoint = new GeneralDirectPosition(226.7, 47.2); final GridExtent extent = create3D(); final GridExtent slice = extent.slice(slicePoint, new int[] {1, 2}); - assertEquals(3, slice.getDimension(), "dimension"); + verifyDimensions(slice); assertExtentEquals(slice, 0, 100, 499); assertExtentEquals(slice, 1, 227, 227); assertExtentEquals(slice, 2, 47, 47); diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java index 8e5dc79eec..034f41e792 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java @@ -482,13 +482,7 @@ public abstract class IndexedResourceBundle extends ResourceBundle implements Lo * @throws IOException if an error occurred while writing to the given destination. */ public final void appendLabel(final short key, final Appendable toAppendTo) throws IOException { - toAppendTo.append(getString(key)); - final String colon = colon(); - if (colon != null) { - toAppendTo.append(colon); - } else { - toAppendTo.append(':'); - } + toAppendTo.append(getString(key)).append(colon()); } /** @@ -510,9 +504,7 @@ public abstract class IndexedResourceBundle extends ResourceBundle implements Lo * @return localized string followed by a colon. */ public final String getLabel(final short key) { - final String text = getString(key); - final String colon = colon(); - return (colon != null) ? (text + colon) : (text + ':'); + return toLabel(getString(key)); } /** @@ -521,7 +513,7 @@ public abstract class IndexedResourceBundle extends ResourceBundle implements Lo * @todo Should be a localized resource by itself. */ private String colon() { - return Locale.FRENCH.getLanguage().equals(getLocale().getLanguage()) ? "\u00A0:" : null; + return Locale.FRENCH.getLanguage().equals(getLocale().getLanguage()) ? "\u00A0:" : ":"; } /** diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java index 5621f15b1d..d054704d1d 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java @@ -653,11 +653,6 @@ public class Vocabulary extends IndexedResourceBundle { */ public static final short Image = 102; - /** - * Image layout - */ - public static final short ImageLayout = 103; - /** * Image size */ @@ -1138,6 +1133,11 @@ public class Vocabulary extends IndexedResourceBundle { */ public static final short RemoteConfiguration = 170; + /** + * Rendered image + */ + public static final short RenderedImage = 103; + /** * Representative value */ diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties index 2ce3cf511b..39ccbb51ac 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties @@ -134,7 +134,6 @@ Identity = Identity Image = Image Image_1 = Image #{0} ImageSize = Image size -ImageLayout = Image layout Implementation = Implementation InBetweenWords = \u2002in\u2002 Index = Index @@ -229,6 +228,7 @@ Reflectance = Reflectance Reflective = Reflective Remarks = Remarks RemoteConfiguration = Remote configuration +RenderedImage = Rendered image RepresentativeValue = Representative value Resolution = Resolution ResourceIdentification = Resource identification diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties index 0a28ebff61..fee589028b 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties @@ -141,7 +141,6 @@ Identity = Identit\u00e9 Image = Image Image_1 = Image {0} ImageSize = Taille de l\u2019image -ImageLayout = Agencement de l\u2019image Implementation = Impl\u00e9mentation InBetweenWords = \u2002dans\u2002 Index = Index @@ -157,7 +156,7 @@ Julian = Julien Latitude = Latitude Longitude = Longitude Layers = Couches -Layout = Disposition +Layout = Agencement Legend = L\u00e9gende Level = Niveau Libraries = Biblioth\u00e8ques @@ -236,6 +235,7 @@ Reflectance = R\u00e9flectance Reflective = R\u00e9fl\u00e9chissant Remarks = Remarques RemoteConfiguration = Configuration distante +RenderedImage = Image rendue RepresentativeValue = Valeur repr\u00e9sentative Resolution = R\u00e9solution ResourceIdentification = Identification de la ressource
