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 d768f64 Complete AbstractRenderedImage and add an initial test.
d768f64 is described below
commit d768f64d61b034510ffe0b0ef7d52d7fe588fba0
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Dec 25 23:45:03 2019 +0100
Complete AbstractRenderedImage and add an initial test.
---
.../org/apache/sis/coverage/grid/GridCoverage.java | 4 +-
.../coverage/j2d/AbstractRenderedImage.java | 367 ++++++++++++++++-----
.../sis/internal/coverage/j2d/ImageUtilities.java | 23 ++
.../internal/coverage/j2d/ScaledColorSpace.java | 16 +-
.../coverage/j2d/AbstractRenderedImageTest.java | 175 ++++++++++
.../internal/coverage/j2d/ImageUtilitiesTest.java | 14 +
.../java/org/apache/sis/test/FeatureAssert.java | 67 ++++
.../test/java/org/apache/sis/test/package-info.txt | 3 +
.../apache/sis/test/suite/FeatureTestSuite.java | 1 +
9 files changed, 575 insertions(+), 95 deletions(-)
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 08513af..bb9d7f1 100644
---
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -230,8 +230,8 @@ public abstract class GridCoverage {
* @throws IndexOutOfBoundsException if a coordinate is out of bounds.
*/
static double[] evaluate(final RenderedImage data, final int x, final int
y, final double[] buffer) {
- final int tx = Math.floorDiv(Math.subtractExact(x,
data.getTileGridXOffset()), data.getTileWidth());
- final int ty = Math.floorDiv(Math.subtractExact(y,
data.getTileGridYOffset()), data.getTileHeight());
+ final int tx = Math.toIntExact(Math.floorDiv(x - (long)
data.getTileGridXOffset(), data.getTileWidth()));
+ final int ty = Math.toIntExact(Math.floorDiv(y - (long)
data.getTileGridYOffset(), data.getTileHeight()));
return data.getTile(tx, ty).getPixel(x, y, buffer);
}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/AbstractRenderedImage.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/AbstractRenderedImage.java
index 4d1c4cf..2decb87 100644
---
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/AbstractRenderedImage.java
+++
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/AbstractRenderedImage.java
@@ -16,141 +16,328 @@
*/
package org.apache.sis.internal.coverage.j2d;
-import java.awt.Point;
+import java.awt.Image;
import java.awt.Rectangle;
-import java.awt.image.Raster;
-import java.awt.image.RenderedImage;
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
import java.awt.image.SampleModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.RasterFormatException;
import java.awt.image.WritableRaster;
+import java.awt.image.RenderedImage;
import java.util.Vector;
+import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Classes;
+
/**
+ * Skeleton implementation of {@link RenderedImage}.
+ * Current implementation does not hold any state.
*
- * @author Johann Sorel (Geomatys)
- * @version 2.0
- * @since 2.0
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
* @module
*/
public abstract class AbstractRenderedImage implements RenderedImage {
+ /**
+ * Approximate size of the buffer to use for copying data from the image
to a raster, in bits.
+ * The actual buffer size may be smaller or larger, depending on the
actual tile size.
+ */
+ private static final int BUFFER_SIZE = 8192 * Byte.SIZE;
+ /**
+ * Creates a new rendered image.
+ */
+ protected AbstractRenderedImage() {
+ }
+
+ /**
+ * Returns the immediate sources of image data for this image.
+ * This method returns {@code null} if the image has no information about
its immediate sources.
+ * It returns an empty vector if the image object has no immediate sources.
+ *
+ * <p>The default implementation returns {@code null}.
+ * Note that this is not equivalent to an empty vector.</p>
+ *
+ * @return the immediate sources, or {@code null} if unknown.
+ */
@Override
+ @SuppressWarnings("UseOfObsoleteCollectionType")
public Vector<RenderedImage> getSources() {
- return new Vector<>();
+ return null;
}
+ /**
+ * Gets a property from this image.
+ * This method returns {@link Image#UndefinedProperty} if the specified
property is not defined.
+ *
+ * <p>The default implementation returns {@link Image#UndefinedProperty}
in all cases.</p>
+ *
+ * @param name the name of the property to get.
+ * @return the property value, or {@link Image#UndefinedProperty} if none.
+ */
@Override
public Object getProperty(String name) {
- return null;
+ return Image.UndefinedProperty;
}
+ /**
+ * Returns the names of all recognized properties,
+ * or {@code null} if this image has no properties.
+ *
+ * <p>The default implementation returns {@code null}.</p>
+ *
+ * @return names of all recognized properties, or {@code null} if none.
+ */
@Override
public String[] getPropertyNames() {
- return new String[0];
+ return null;
}
+ /**
+ * Returns the number of tiles in the X direction.
+ *
+ * <p>The default implementation computes this value from {@link
#getWidth()} and {@link #getTileWidth()}.</p>
+ *
+ * @return returns the number of tiles in the X direction.
+ */
@Override
- public Raster getData() {
- final SampleModel sm =
getSampleModel().createCompatibleSampleModel(getWidth(), getHeight());
- final Raster rasterOut = Raster.createWritableRaster(sm, new
Point(getMinX(), getMinY()));
-
- // Clear dataBuffer to 0 value for all bank
- for (int s = 0; s < rasterOut.getDataBuffer().getSize(); s++) {
- for (int b = 0; b < rasterOut.getDataBuffer().getNumBanks(); b++) {
- rasterOut.getDataBuffer().setElem(b, s, 0);
- }
- }
-
- for (int y = 0, yn = this.getNumYTiles(); y < yn; y++) {
- for (int x = 0, xn = this.getNumXTiles(); x < xn; x++) {
- final Raster rasterIn = getTile(x, y);
- rasterOut.getSampleModel()
- .setDataElements(
- x * this.getTileWidth(),
- y * this.getTileHeight(),
- this.getTileWidth(),
- this.getTileHeight(),
- rasterIn.getSampleModel().getDataElements(0,
0, this.getTileWidth(), this.getTileHeight(), null, rasterIn.getDataBuffer()),
- rasterOut.getDataBuffer());
- }
- }
-
- return rasterOut;
+ public int getNumXTiles() {
+ return Numerics.ceilDiv(getWidth(), getTileWidth());
}
+ /**
+ * Returns the number of tiles in the Y direction.
+ *
+ * <p>The default implementation computes this value from {@link
#getHeight()} and {@link #getTileHeight()}.</p>
+ *
+ * @return returns the number of tiles in the Y direction.
+ */
@Override
- public Raster getData(Rectangle rect) {
- final SampleModel sm =
getSampleModel().createCompatibleSampleModel(rect.width, rect.height);
- Raster rasterOut = Raster.createWritableRaster(sm, null);
-
- // Clear dataBuffer to 0 value for all bank
- for (int s = 0; s < rasterOut.getDataBuffer().getSize(); s++) {
- for (int b = 0; b < rasterOut.getDataBuffer().getNumBanks(); b++) {
- rasterOut.getDataBuffer().setElem(b, s, 0);
- }
- }
-
- final Point upperLeftPosition = this.getPositionOf(rect.x, rect.y);
- final Point lowerRightPosition =
this.getPositionOf(rect.x+rect.width-1, rect.y+rect.height-1);
-
- for (int y = Math.max(upperLeftPosition.y,0); y <
Math.min(lowerRightPosition.y + 1,this.getNumYTiles()); y++) {
- for (int x = Math.max(upperLeftPosition.x,0); x <
Math.min(lowerRightPosition.x + 1, this.getNumXTiles()); x++) {
- final Rectangle tileRect = new Rectangle(x *
this.getTileWidth(), y * this.getTileHeight(), this.getTileWidth(),
this.getTileHeight());
-
- final int minX, maxX, minY, maxY;
- minX = clamp(rect.x, tileRect.x, tileRect.x +
tileRect.width);
- maxX = clamp(rect.x + rect.width, tileRect.x, tileRect.x +
tileRect.width);
- minY = clamp(rect.y, tileRect.y, tileRect.y +
tileRect.height);
- maxY = clamp(rect.y + rect.height, tileRect.y, tileRect.y +
tileRect.height);
+ public int getNumYTiles() {
+ return Numerics.ceilDiv(getHeight(), getTileHeight());
+ }
- final Rectangle rectIn = new Rectangle(minX, minY, maxX-minX,
maxY-minY);
- rectIn.translate(-tileRect.x, -tileRect.y);
- final Rectangle rectOut = new Rectangle(minX, minY, maxX-minX,
maxY-minY);
- rectOut.translate(-rect.x, -rect.y);
+ /**
+ * Returns the X coordinate of the upper-left pixel of tile (0, 0).
+ * That tile (0, 0) may not actually exist.
+ *
+ * <p>The default implementation computes this value from {@link
#getMinX()},
+ * {@link #getMinTileX()} and {@link #getTileWidth()}.</p>
+ *
+ * @return the X offset of the tile grid relative to the origin.
+ */
+ @Override
+ public int getTileGridXOffset() {
+ return Math.subtractExact(getMinX(), Math.multiplyExact(getMinTileX(),
getTileWidth()));
+ }
- if (rectIn.width <= 0 || rectIn.height <= 0 || rectOut.width
<= 0 || rectOut.height <= 0){
- continue;
- }
+ /**
+ * Returns the Y coordinate of the upper-left pixel of tile (0, 0).
+ * That tile (0, 0) may not actually exist.
+ *
+ * <p>The default implementation computes this value from {@link
#getMinY()},
+ * {@link #getMinTileY()} and {@link #getTileHeight()}.</p>
+ *
+ * @return the Y offset of the tile grid relative to the origin.
+ */
+ @Override
+ public int getTileGridYOffset() {
+ return Math.subtractExact(getMinY(), Math.multiplyExact(getMinTileY(),
getTileHeight()));
+ }
- final Raster rasterIn = getTile(x, y);
+ /**
+ * Creates a raster with the same sample model than this image and with
the given size and location.
+ * This method does not verify argument validity.
+ */
+ private WritableRaster createWritableRaster(final Rectangle aoi) {
+ final SampleModel sm =
getSampleModel().createCompatibleSampleModel(aoi.width, aoi.height);
+ return Raster.createWritableRaster(sm, aoi.getLocation());
+ }
- rasterOut.getSampleModel().setDataElements(rectOut.x,
rectOut.y, rectOut.width, rectOut.height,
- rasterIn.getSampleModel().getDataElements(rectIn.x,
rectIn.y, rectIn.width, rectIn.height, null, rasterIn.getDataBuffer()),
- rasterOut.getDataBuffer());
- }
+ /**
+ * Returns the size in bits of the transfer type, or an arbitrary value if
that type is unknown.
+ * For this class it is okay if the value is not accurate; this method is
used only for adjusting
+ * the {@link #BUFFER_SIZE} value.
+ *
+ * @param raster the raster for which to get transfer type size.
+ * @return size in bits of transfer type. May be an arbitrary size.
+ */
+ private static int getTransferTypeSize(final Raster raster) {
+ try {
+ return DataBuffer.getDataTypeSize(raster.getTransferType());
+ } catch (IllegalArgumentException e) {
+ return Short.SIZE;
}
+ }
- if (rect.x != 0 && rect.y != 0) {
- rasterOut = rasterOut.createTranslatedChild(rect.x, rect.y);
- }
- return rasterOut;
+ /**
+ * Returns a copy of this image as one large tile.
+ * The returned raster will not be updated if the image is changed.
+ *
+ * @return a copy of this image as one large tile.
+ */
+ @Override
+ public Raster getData() {
+ final Rectangle aoi = ImageUtilities.getBounds(this);
+ final WritableRaster raster = createWritableRaster(aoi);
+ copyData(aoi, raster);
+ return raster;
}
+ /**
+ * Returns a copy of an arbitrary region of this image.
+ * The returned raster will not be updated if the image is changed.
+ *
+ * @param aoi the region of this image to copy.
+ * @return a copy of this image in the given area of interest.
+ * @throws IllegalArgumentException if the given rectangle is not
contained in this image bounds.
+ */
+ @Override
+ public Raster getData(final Rectangle aoi) {
+ ArgumentChecks.ensureNonNull("aoi", aoi);
+ if (!ImageUtilities.getBounds(this).contains(aoi)) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.OutsideDomainOfValidity));
+ }
+ final WritableRaster raster = createWritableRaster(aoi);
+ copyData(aoi, raster);
+ return raster;
+ }
+ /**
+ * Copies an arbitrary rectangular region of this image to the supplied
writable raster.
+ * The region to be copied is determined from the bounds of the supplied
raster.
+ * The supplied raster must have a {@link SampleModel} that is compatible
with this image.
+ * If the raster is {@code null}, an raster is created by this method.
+ *
+ * @param raster the raster to hold a copy of this image, or {@code
null}.
+ * @return the given raster if it was not-null, or a new raster otherwise.
+ */
@Override
public WritableRaster copyData(WritableRaster raster) {
- //TODO
- throw new UnsupportedOperationException("Not supported yet.");
+ final Rectangle aoi;
+ if (raster != null) {
+ aoi = raster.getBounds();
+ } else {
+ aoi = ImageUtilities.getBounds(this);
+ raster = createWritableRaster(aoi);
+ }
+ copyData(aoi, raster);
+ return raster;
}
/**
- * Get the tile column and row position for a pixel.
- * Return value can be out of the gridSize
+ * Implementation of {@link #getData()}, {@link #getData(Rectangle)} and
{@link #copyData(WritableRaster)}.
+ * It is caller responsibility to ensure that all arguments are non-null
and that the rectangle is contained
+ * inside both this image and the given raster.
+ *
+ * @param aoi the region of this image to copy.
+ * @param raster the raster to hold a copy of this image, or {@code
null}.
*/
- protected Point getPositionOf(int x, int y){
- final int posX = (int) (Math.floor(x / this.getTileWidth()));
- final int posY = (int) (Math.floor(y / this.getTileHeight()));
- return new Point(posX, posY);
+ private void copyData(final Rectangle aoi, final WritableRaster raster) {
+ final int tileWidth = getTileWidth();
+ final int tileHeight = getTileHeight();
+ final long tileGridXOffset = getTileGridXOffset(); // We want
64 bits arithmetic in operations below.
+ final long tileGridYOffset = getTileGridYOffset();
+ final int minTileX = Math.toIntExact(Math.floorDiv(aoi.x
- tileGridXOffset, tileWidth));
+ final int minTileY = Math.toIntExact(Math.floorDiv(aoi.y
- tileGridYOffset, tileHeight));
+ final int maxTileX = Math.toIntExact(Math.floorDiv(aoi.x + (aoi.width
- 1L) - tileGridXOffset, tileWidth));
+ final int maxTileY = Math.toIntExact(Math.floorDiv(aoi.y +
(aoi.height - 1L) - tileGridYOffset, tileHeight));
+ /*
+ * Iterate over all tiles that interesect the area of interest. For
each tile,
+ * copy a few rows in a temporary buffer, then copy that buffer to
destination.
+ * The buffer will be reused for each transfer, unless its size is
insufficient.
+ */
+ Object buffer = null;
+ int bufferCapacity = 0;
+ for (int ty = minTileY; ty <= maxTileY; ty++) {
+ for (int tx = minTileX; tx <= maxTileX; tx++) {
+ final Raster tile = getTile(tx, ty);
+ final Rectangle tb = aoi.intersection(tile.getBounds());
// Bounds of transfer buffer.
+ if (tb.isEmpty()) {
+ /*
+ * Should never happen since we iterate only on the tiles
+ * that intersect the given area of interest.
+ */
+ throw new RasterFormatException("Inconsistent tile
matrix.");
+ }
+ final int afterLastRow = Math.addExact(tb.y, tb.height);
+ tb.height = Math.max(1, Math.min(BUFFER_SIZE /
(getTransferTypeSize(tile) * tb.width), tb.height));
+ final int transferCapacity = tb.width * tb.height;
+ if (transferCapacity > bufferCapacity) {
+ bufferCapacity = transferCapacity;
+ buffer = null; // Will be
allocated by Raster.getDataElements(…).
+ }
+ while (tb.y < afterLastRow) {
+ final int height = Math.min(tb.height, afterLastRow -
tb.y);
+ buffer = tile.getDataElements(tb.x, tb.y, tb.width,
height, buffer);
+ raster.setDataElements(tb.x, tb.y, tb.width, height,
buffer);
+ tb.y += height;
+ }
+ }
+ }
}
/**
- * Clamps a value between min value and max value.
- *
- * @param val the value to clamp
- * @param min the minimum value
- * @param max the maximum value
- * @return val clamped between min and max
+ * Returns a string representation of this image for debugging purpose.
+ * This string representation may change in any future SIS version.
*/
- private static int clamp(int val, int min, int max) {
- return Math.min(Math.max(val, min), max);
+ @Override
+ public String toString() {
+ final StringBuilder buffer = new
StringBuilder(100).append(Classes.getShortClassName(this))
+ .append('[').append(getWidth()).append(" ×
").append(getHeight()).append(" pixels");
+ final SampleModel sm = getSampleModel();
+ if (sm != null) {
+ buffer.append(" × ").append(sm.getNumBands()).append(" bands");
+ final String type = ImageUtilities.dataTypeName(sm.getDataType());
+ 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("; ");
+ if (cm instanceof IndexColorModel) {
+ buffer.append(((IndexColorModel) cm).getMapSize()).append("
indexed colors");
+ } else {
+ final ColorSpace cs = cm.getColorSpace();
+ if (cs != null) {
+ if (cs instanceof ScaledColorSpace) {
+ ((ScaledColorSpace)
cs).formatRange(buffer.append("showing "));
+ } else if (cs.getType() == ColorSpace.TYPE_GRAY) {
+ buffer.append("; grayscale");
+ }
+ }
+ }
+ 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.
+ */
+ final int tx = getNumXTiles();
+ final int ty = getNumYTiles();
+ if (tx != 1 || ty != 1) {
+ buffer.append("; ").append(tx).append(" × ").append(ty).append("
tiles");
+ }
+ return buffer.append(']').toString();
}
}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
index e9a8495..afad309 100644
---
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
+++
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
@@ -98,6 +98,29 @@ public final class ImageUtilities {
}
/**
+ * Names of {@link DataBuffer} types.
+ */
+ private static final String[] TYPE_NAMES = new
String[DataBuffer.TYPE_DOUBLE + 1];
+ static {
+ TYPE_NAMES[DataBuffer.TYPE_BYTE] = "byte";
+ TYPE_NAMES[DataBuffer.TYPE_SHORT] = "short";
+ TYPE_NAMES[DataBuffer.TYPE_USHORT] = "ushort";
+ TYPE_NAMES[DataBuffer.TYPE_INT] = "int";
+ TYPE_NAMES[DataBuffer.TYPE_FLOAT] = "float";
+ TYPE_NAMES[DataBuffer.TYPE_DOUBLE] = "double";
+ }
+
+ /**
+ * Returns the name of a {@link DataBuffer} type.
+ *
+ * @param type one of {@link DataBuffer} constants.
+ * @return name of the given constant, or {@code null} if unknown.
+ */
+ public static String dataTypeName(final int type) {
+ return (type >= 0 && type < TYPE_NAMES.length) ? TYPE_NAMES[type] :
null;
+ }
+
+ /**
* Returns names of bands based on inspection of the color model.
* The bands are identified by {@link Vocabulary.Keys} values for
* red, green, blue, cyan, magenta, yellow, black, gray, <i>etc</i>.
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 0392867..56d4049 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
@@ -17,7 +17,6 @@
package org.apache.sis.internal.coverage.j2d;
import java.awt.color.ColorSpace;
-import org.apache.sis.internal.util.Strings;
/**
@@ -29,7 +28,7 @@ import org.apache.sis.internal.util.Strings;
* It should be used only when no standard color space can be used.</p>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 1.0
* @module
*/
@@ -166,6 +165,17 @@ final class ScaledColorSpace extends ColorSpace {
*/
@Override
public String toString() {
- return Strings.range(getClass(), getMinValue(visibleBand),
getMaxValue(visibleBand));
+ final StringBuilder buffer = new
StringBuilder(20).append(getClass().getSimpleName());
+ formatRange(buffer);
+ return buffer.toString();
+ }
+
+ /**
+ * Formats the range of values in the given buffer.
+ */
+ final void formatRange(final StringBuilder buffer) {
+ buffer.append('[').append(getMinValue(visibleBand))
+ .append(" … ").append(getMaxValue(visibleBand))
+ .append(" in band ").append(visibleBand).append(']');
}
}
diff --git
a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/AbstractRenderedImageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/AbstractRenderedImageTest.java
new file mode 100644
index 0000000..5520a56
--- /dev/null
+++
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/AbstractRenderedImageTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.internal.coverage.j2d;
+
+import java.util.Random;
+import java.awt.Point;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.WritableRaster;
+import org.apache.sis.test.TestCase;
+import org.apache.sis.test.TestUtilities;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.apache.sis.test.FeatureAssert.assertValuesEqual;
+
+
+/**
+ * Tests {@link AbstractRenderedImage}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public final strictfp class AbstractRenderedImageTest extends TestCase {
+ /**
+ * Size of tiles in this test. The width should be different than the
height
+ * for increasing the chances to detect errors in index calculations.
+ */
+ private static final int TILE_WIDTH = 3, TILE_HEIGHT = 2;
+
+ /**
+ * Image size. Shall be multiple of tile width and height.
+ */
+ private static final int WIDTH = TILE_WIDTH * 4,
+ HEIGHT = TILE_HEIGHT * 3;
+
+ /**
+ * Random number generator for this test.
+ */
+ private final Random random;
+
+ /**
+ * Creates a new test.
+ */
+ public AbstractRenderedImageTest() {
+ random = TestUtilities.createRandomNumberGenerator();
+ }
+
+ /**
+ * A rendered image which can contain an arbitrary number of tiles. Tiles
are stored in memory.
+ * We use this class for testing purpose only because tiled images in
production need a more
+ * sophisticated implementation capable to store some tiles on disk (for
memory consumption reasons).
+ */
+ private static final class TiledImage extends AbstractRenderedImage {
+ /**
+ * Index of the first tile in the image. Should be a non-trivial value
+ * for increasing the chances to detect error in index calculation.
+ */
+ private final int minTileX, minTileY;
+
+ /**
+ * Location of the upper-left pixel of the image. Should be a
non-trivial
+ * value for increasing the chances to detect error in index
calculation.
+ */
+ private final int minX, minY;
+
+ /**
+ * The tiles.
+ */
+ private final Raster[] tiles;
+
+ /**
+ * Creates a new tiled image.
+ */
+ TiledImage(final Random random) {
+ minTileX = random.nextInt(20) - 10;
+ minTileY = random.nextInt(20) - 10;
+ minX = random.nextInt(20) - 10;
+ minY = random.nextInt(20) - 10;
+ final int numXTiles = getNumXTiles();
+ final int numYTiles = getNumYTiles();
+ tiles = new WritableRaster[numXTiles * numYTiles];
+ int i = 0;
+ for (int ty = 0; ty < numYTiles; ty++) {
+ for (int tx = 0; tx < numXTiles; tx++) {
+ tiles[i] = createTile(tx * TILE_WIDTH + minX,
+ ty * TILE_HEIGHT + minY,
+ ++i * 100);
+ }
+ }
+ assertEquals(tiles.length, i);
+ }
+
+ /**
+ * Creates a tile at the given location and with values starting at
the given value.
+ *
+ * @param x column index of the upper-left pixel.
+ * @param y row index of the upper-left pixel.
+ * @param value value of the upper-left pixel.
+ */
+ private static WritableRaster createTile(final int x, final int y,
final int value) {
+ final WritableRaster raster =
Raster.createBandedRaster(DataBuffer.TYPE_USHORT, TILE_WIDTH, TILE_HEIGHT, 1,
new Point(x,y));
+ for (int j=0; j<TILE_HEIGHT; j++) {
+ for (int i=0; i<TILE_WIDTH; i++) {
+ raster.setSample(x+i, y+j, 0, value + 10*j + i);
+ }
+ }
+ return raster;
+ }
+
+ /*
+ * No source, no property, no color model since this test images is
not for rendering on screen.
+ */
+ @Override public ColorModel getColorModel() {return null;}
+ @Override public SampleModel getSampleModel() {return
tiles[0].getSampleModel();}
+
+ /*
+ * Size and tiling information.
+ */
+ @Override public int getMinX() {return minX;}
+ @Override public int getMinY() {return minY;}
+ @Override public int getWidth() {return WIDTH;}
+ @Override public int getHeight() {return HEIGHT;}
+ @Override public int getTileWidth() {return TILE_WIDTH;}
+ @Override public int getTileHeight() {return TILE_HEIGHT;}
+ @Override public int getMinTileX() {return minTileX;}
+ @Override public int getMinTileY() {return minTileY;}
+
+ /**
+ * Returns the tile at the given location in tile coordinates.
+ */
+ @Override
+ public Raster getTile(int tileX, int tileY) {
+ final int numXTiles = getNumXTiles();
+ final int numYTiles = getNumYTiles();
+ assertTrue((tileX -= minTileX) >= 0 && tileX < numXTiles);
+ assertTrue((tileY -= minTileY) >= 0 && tileY < numYTiles);
+ return tiles[tileY * numXTiles + tileX];
+ }
+ }
+
+ /**
+ * Tests {@link AbstractRenderedImage#getData()} on a tiled image.
+ */
+ @Test
+ public void testGetData() {
+ final AbstractRenderedImage image = new TiledImage(random);
+ assertValuesEqual(image.getData(), 0, new int[][] {
+ { 100, 101, 102 , 200, 201, 202 , 300, 301, 302 ,
400, 401, 402},
+ { 110, 111, 112 , 210, 211, 212 , 310, 311, 312 ,
410, 411, 412},
+ { 500, 501, 502 , 600, 601, 602 , 700, 701, 702 ,
800, 801, 802},
+ { 510, 511, 512 , 610, 611, 612 , 710, 711, 712 ,
810, 811, 812},
+ { 900, 901, 902 , 1000, 1001, 1002 , 1100, 1101, 1102 ,
1200, 1201, 1202},
+ { 910, 911, 912 , 1010, 1011, 1012 , 1110, 1111, 1112 ,
1210, 1211, 1212}
+ });
+ }
+}
diff --git
a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
index 9797daa..71693be 100644
---
a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
+++
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ImageUtilitiesTest.java
@@ -18,6 +18,7 @@ package org.apache.sis.internal.coverage.j2d;
import java.awt.image.ColorModel;
import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.test.TestCase;
@@ -36,6 +37,19 @@ import static org.junit.Assert.*;
*/
public final strictfp class ImageUtilitiesTest extends TestCase {
/**
+ * Tests {@link ImageUtilities#dataTypeName(int)}.
+ */
+ @Test
+ public void testDataTypeName() {
+ assertEquals("byte",
ImageUtilities.dataTypeName(DataBuffer.TYPE_BYTE));
+ assertEquals("short",
ImageUtilities.dataTypeName(DataBuffer.TYPE_SHORT));
+ assertEquals("ushort",
ImageUtilities.dataTypeName(DataBuffer.TYPE_USHORT));
+ assertEquals("int",
ImageUtilities.dataTypeName(DataBuffer.TYPE_INT));
+ assertEquals("float",
ImageUtilities.dataTypeName(DataBuffer.TYPE_FLOAT));
+ assertEquals("double",
ImageUtilities.dataTypeName(DataBuffer.TYPE_DOUBLE));
+ }
+
+ /**
* Verifies that {@link ImageUtilities#bandNames(RenderedImage)} returns
expected band names.
*
* @param nde expected number of data elements. This number
categorizes the tests in this class.
diff --git
a/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java
b/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java
new file mode 100644
index 0000000..3387d3a
--- /dev/null
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java
@@ -0,0 +1,67 @@
+/*
+ * 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.test;
+
+import java.awt.image.Raster;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Assertion methods used by the {@code sis-feature} module in addition of the
ones inherited
+ * from other modules and libraries.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public strictfp class FeatureAssert extends ReferencingAssert {
+ /**
+ * For subclass constructor only.
+ */
+ protected FeatureAssert() {
+ }
+
+ /**
+ * Verifies that sample values in the given raster are equal to the
expected values.
+ *
+ * @param raster the raster to verify.
+ * @param band the band to verify.
+ * @param expected the expected sample values.
+ */
+ public static void assertValuesEqual(final Raster raster, final int band,
final int[][] expected) {
+ final int minX = raster.getMinX();
+ final int minY = raster.getMinY();
+ assertEquals("Height", expected.length, raster.getHeight());
+ for (int j=0; j<expected.length; j++) {
+ final int[] row = expected[j];
+ assertEquals("Width", row.length, raster.getWidth());
+ final int y = minY + j;
+ for (int i=0; i<row.length; i++) {
+ final int x = minX + i;
+ final int actual = raster.getSample(x, y, band);
+ final int e = row[i];
+ if (actual != e) {
+ fail("Mismatched sample value at image coordinates (" + x
+ ", " + y + ") "
+ + "— matrix indices (" + i + ", " + j + ") band "
+ band
+ + ": expected " + e + " but found " + actual);
+ }
+ }
+ }
+ }
+}
diff --git
a/core/sis-feature/src/test/java/org/apache/sis/test/package-info.txt
b/core/sis-feature/src/test/java/org/apache/sis/test/package-info.txt
new file mode 100644
index 0000000..ac895b5
--- /dev/null
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/package-info.txt
@@ -0,0 +1,3 @@
+Different modules provide classes in this package - be careful about
collisions.
+This package is initially defined by the sis-utility module, which also
provides
+the package-info.java file.
diff --git
a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index a23bfe4..4731240 100644
---
a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++
b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -88,6 +88,7 @@ import org.junit.runners.Suite;
org.apache.sis.coverage.grid.GridCoverage2DTest.class,
org.apache.sis.internal.coverage.j2d.ImageUtilitiesTest.class,
org.apache.sis.internal.coverage.j2d.ScaledColorSpaceTest.class,
+ org.apache.sis.internal.coverage.j2d.AbstractRenderedImageTest.class,
org.apache.sis.internal.coverage.j2d.BufferedGridCoverageTest.class,
org.apache.sis.internal.coverage.j2d.TranslatedRenderedImageTest.class
})