sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Complete AbstractRenderedImage and add an initial test.
Date Wed, 25 Dec 2019 22:45:57 GMT
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 <martin.desruisseaux@geomatys.com>
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
 })


Mime
View raw message