001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    http://www.geoapi.org
004 *
005 *    Copyright (C) 2012-2019 Open Geospatial Consortium, Inc.
006 *    All Rights Reserved. http://www.opengeospatial.org/ogc/legal
007 *
008 *    Permission to use, copy, and modify this software and its documentation, with
009 *    or without modification, for any purpose and without fee or royalty is hereby
010 *    granted, provided that you include the following on ALL copies of the software
011 *    and documentation or portions thereof, including modifications, that you make:
012 *
013 *    1. The full text of this NOTICE in a location viewable to users of the
014 *       redistributed or derivative work.
015 *    2. Notice of any changes or modifications to the OGC files, including the
016 *       date changes were made.
017 *
018 *    THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE
019 *    NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
020 *    TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT
021 *    THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY
022 *    PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
023 *
024 *    COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
025 *    CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
026 *
027 *    The name and trademarks of copyright holders may NOT be used in advertising or
028 *    publicity pertaining to the software without specific, written prior permission.
029 *    Title to copyright in this software and any associated documentation will at all
030 *    times remain with copyright holders.
031 */
032package org.opengis.test.coverage.image;
033
034import java.awt.Rectangle;
035import java.awt.image.DataBuffer;
036import java.awt.image.Raster;
037import java.awt.image.RenderedImage;
038import java.awt.image.SampleModel;
039
040import static java.lang.Double.doubleToLongBits;
041import static java.lang.Float.floatToIntBits;
042import static java.lang.StrictMath.*;
043import static org.junit.Assert.*;
044
045
046/**
047 * A row-major iterator over sample values in a {@link Raster} or {@link RenderedImage}.
048 * For any image (tiled or not), this class iterates first over the <em>bands</em>, then
049 * over the <em>columns</em> and finally over the <em>rows</em>. If the image is tiled,
050 * then this iterator will perform the necessary calls to the {@link RenderedImage#getTile(int, int)}
051 * method for each row in order to perform the iteration as if the image was untiled.
052 *
053 * <p>On creation, this iterator is positioned <em>before</em> the first sample value.
054 * To use this iterator, invoke the {@link #next()} method in a {@code while} loop
055 * as below:</p>
056 *
057 * <pre>PixelIterator it = new PixelIterator(image);
058 *while (it.next()) {
059 *    float value = it.getSampleFloat();
060 *    // Do some processing with the value here...
061 *}</pre>
062 *
063 * @see org.opengis.test.Assert#assertSampleValuesEqual(String, RenderedImage, RenderedImage, double)
064 *
065 * @author  RĂ©mi Marechal (Geomatys)
066 * @author  Martin Desruisseaux (Geomatys)
067 * @version 3.1
068 * @since   3.1
069 */
070public strictfp class PixelIterator {
071    /**
072     * The image in which to iterate.
073     */
074    private final RenderedImage image;
075
076    /**
077     * Current raster in which to iterate.
078     */
079    private Raster raster;
080
081    /**
082     * The bands to iterate over, or {@code null} if none.
083     */
084    private final int[] sourceBands;
085
086    /**
087     * The subsampling to apply during the iteration.
088     */
089    private final int xSubsampling, ySubsampling;
090
091    /**
092     * Number of bands to iterate over.
093     */
094    private final int numBands;
095
096    /**
097     * The iteration bounds in the image, in pixel coordinates.
098     * This rectangle may span an arbitrary number of tiles.
099     */
100    private final int minX, maxX, maxY;
101
102    /**
103     * The iteration bounds in the image, in tile coordinates.
104     */
105    private final int minTileX, maxTileX, maxTileY;
106
107    /**
108     * The intersection of the iteration bounds together with the current
109     * {@linkplain #raster} bounds.
110     */
111    private int currentMaxX, currentMaxY;
112
113    /**
114     * Current band and pixel position in the current {@linkplain #raster}.
115     */
116    private int band, x, y;
117
118    /**
119     * Current raster position in the {@linkplain #image}.
120     */
121    private int tileX, tileY;
122
123    /**
124     * Creates an iterator for the whole area of the given raster.
125     *
126     * @param raster The raster for which to create an iterator.
127     */
128    public PixelIterator(final Raster raster) {
129        this(new RasterImage(raster));
130    }
131
132    /**
133     * Creates an iterator for the whole area of the given image.
134     *
135     * @param image The image for which to create an iterator.
136     */
137    public PixelIterator(final RenderedImage image) {
138        this(image, null, 1, 1, null);
139    }
140
141    /**
142     * Creates an iterator for a sub-area of the given raster.
143     *
144     * @param raster        the raster to iterate over.
145     * @param subArea       rectangle which represent raster sub area iteration, or {@code null} if none.
146     * @param xSubsampling  the iteration step when moving to the next pixel.
147     * @param ySubsampling  the iteration step when moving to the next scan line.
148     * @param sourceBands   the source bands, or {@code null} if none.
149     */
150    public PixelIterator(final Raster raster, final Rectangle subArea,
151            final int xSubsampling, final int ySubsampling, final int[] sourceBands)
152    {
153        this(new RasterImage(raster), subArea, xSubsampling, ySubsampling, sourceBands);
154    }
155
156    /**
157     * Creates an iterator for a sub-area of the given image.
158     *
159     * @param image         the image to iterate over.
160     * @param subArea       rectangle which represent image sub area iteration, or {@code null} if none.
161     * @param xSubsampling  the iteration step when moving to the next pixel.
162     * @param ySubsampling  the iteration step when moving to the next scan line.
163     * @param sourceBands   the source bands, or {@code null} if none.
164     */
165    public PixelIterator(final RenderedImage image, final Rectangle subArea,
166            final int xSubsampling, final int ySubsampling, final int[] sourceBands)
167    {
168        this.image        = image;
169        this.numBands     = (sourceBands != null) ? sourceBands.length : image.getSampleModel().getNumBands();
170        this.sourceBands  = sourceBands;
171        this.xSubsampling = xSubsampling;
172        this.ySubsampling = ySubsampling;
173
174        int minX = image.getMinX();
175        int minY = image.getMinY();
176        int maxX = image.getWidth()  + minX;
177        int maxY = image.getHeight() + minY;
178        if (subArea != null) {
179            minX = max(minX, subArea.x);
180            minY = max(minY, subArea.y);
181            maxX = min(maxX, subArea.x + subArea.width);
182            maxY = min(maxY, subArea.y + subArea.height);
183        }
184        this.minX = minX;
185        this.maxX = maxX;
186        this.maxY = maxY;
187
188        final int gridXOffset = image.getTileGridXOffset();
189        final int gridYOffset = image.getTileGridYOffset();
190        final int tileWidth   = image.getTileWidth();
191        final int tileHeight  = image.getTileHeight();
192
193        final int minTileY;
194        minTileX = divide(minX - gridXOffset, tileWidth,  false);
195        minTileY = divide(minY - gridYOffset, tileHeight, false);
196        maxTileX = divide(maxX - gridXOffset, tileWidth,  true);
197        maxTileY = divide(maxY - gridYOffset, tileHeight, true);
198
199        // Initialize attributes to first iteration.
200        x     = minX;
201        y     = minY;
202        band  = -1;
203        tileX = minTileX;
204        tileY = minTileY;
205        updateRaster();
206    }
207
208    /**
209     * Rounds the given numbers, rounding toward floor or ceil depending on the value
210     * of the {@code ceil} argument. This method works for negative numerator too.
211     */
212    private static int divide(final int numerator, final int denominator, final boolean ceil) {
213        assertTrue("Require a non-negative denominator.", denominator > 0);
214        int div = numerator / denominator;
215        if (ceil) {
216            if (numerator > 0 && (numerator % denominator) != 0) {
217                div++;
218            }
219        } else {
220            if (numerator < 0 && (numerator % denominator) != 0) {
221                div--;
222            }
223        }
224        return div;
225    }
226
227    /**
228     * Updates the {@linkplain #raster} and related fields for the current
229     * {@link #tileX} and {@link #tileY} values.
230     */
231    private void updateRaster() {
232        raster = image.getTile(tileX, tileY);
233        currentMaxX = min(maxX, raster.getMinX() + raster.getWidth());
234        currentMaxY = min(maxY, raster.getMinY() + raster.getHeight());
235    }
236
237    /**
238     * Moves to the next sample values and returns {@code true} if the iteration has more pixels.
239     *
240     * @return {@code true} if the next sample value exist.
241     */
242    public boolean next() {
243        if (++band == numBands) {
244            if ((x += xSubsampling) >= currentMaxX) {
245                int nextTile = tileX + 1;               // Needed only when the iteration stops before the maxX of the last tile in a row.
246                tileX = divide(x - image.getTileGridXOffset(), image.getTileWidth(), false);
247                if (max(nextTile, tileX) >= maxTileX) {
248                    if ((y += ySubsampling) >= currentMaxY) {
249                        nextTile = tileY + 1;           // Needed only when the iteration stops before the maxY of the last row of tiles.
250                        tileY = divide(y - image.getTileGridYOffset(), image.getTileHeight(), false);
251                        if (max(nextTile, tileY) >= maxTileY) {
252                            return false;
253                        }
254                    }
255                    x = minX;
256                    tileX = minTileX;
257                }
258                updateRaster();
259            }
260            band = 0;
261        }
262        return true;
263    }
264
265    /**
266     * Returns the current <var>x</var> coordinate. The coordinate values range from
267     * {@linkplain RenderedImage#getMinX() image X minimum} (inclusive) to that minimum
268     * plus the {@linkplain RenderedImage#getWidth() image width} (exclusive).
269     *
270     * @return the current <var>x</var> coordinate.
271     *
272     * @see RenderedImage#getMinX()
273     * @see RenderedImage#getWidth()
274     */
275    public int getX() {
276        return x;
277    }
278
279    /**
280     * Returns the current <var>y</var> coordinate. The coordinate values range from
281     * {@linkplain RenderedImage#getMinY() image Y minimum} (inclusive) to that minimum
282     * plus the {@linkplain RenderedImage#getHeight() image height} (exclusive).
283     *
284     * @return the current <var>y</var> coordinate.
285     *
286     * @see RenderedImage#getMinY()
287     * @see RenderedImage#getHeight()
288     */
289    public int getY() {
290        return y;
291    }
292
293    /**
294     * Returns the current band index. The index values range from 0 (inclusive) to
295     * the {@linkplain SampleModel#getNumBands() number of bands} (exclusive), or to
296     * the {@code sourceBands} array length (exclusive) if the array given to the
297     * constructor was non-null.
298     *
299     * @return the current band index.
300     *
301     * @see SampleModel#getNumBands()
302     */
303    public int getBand() {
304        return (sourceBands != null) ? sourceBands[band] : band;
305    }
306
307    /**
308     * Returns the type of the sample values, as one of the {@code TYPE_*} constants
309     * defined in the {@link DataBuffer} class.
310     *
311     * @return the type of the sample values.
312     *
313     * @see SampleModel#getDataType()
314     * @see DataBuffer#TYPE_BYTE
315     * @see DataBuffer#TYPE_SHORT
316     * @see DataBuffer#TYPE_USHORT
317     * @see DataBuffer#TYPE_INT
318     * @see DataBuffer#TYPE_FLOAT
319     * @see DataBuffer#TYPE_DOUBLE
320     */
321    public int getDataType() {
322        return image.getSampleModel().getDataType();
323    }
324
325    /**
326     * Returns the sample value at the current position, as an integer.
327     * This method is appropriate for the
328     * {@linkplain DataBuffer#TYPE_BYTE byte},
329     * {@linkplain DataBuffer#TYPE_SHORT short},
330     * {@linkplain DataBuffer#TYPE_USHORT unsigned short} and
331     * {@linkplain DataBuffer#TYPE_INT integer}
332     * {@linkplain #getDataType() datatypes}.
333     *
334     * @return the sample value at the current position.
335     *
336     * @see Raster#getSample(int, int, int)
337     * @see DataBuffer#TYPE_BYTE
338     * @see DataBuffer#TYPE_SHORT
339     * @see DataBuffer#TYPE_USHORT
340     * @see DataBuffer#TYPE_INT
341     */
342    public int getSample() {
343        return raster.getSample(x, y, getBand());
344    }
345
346    /**
347     * Returns the sample value at the current position, as a floating point number.
348     *
349     * @return the sample value at the current position.
350     *
351     * @see Raster#getSampleFloat(int, int, int)
352     * @see DataBuffer#TYPE_FLOAT
353     */
354    public float getSampleFloat() {
355        return raster.getSampleFloat(x, y, getBand());
356    }
357
358    /**
359     * Returns the sample value at the current position, as a double-precision floating point number.
360     *
361     * @return the sample value at the current position.
362     *
363     * @see Raster#getSampleDouble(int, int, int)
364     * @see DataBuffer#TYPE_DOUBLE
365     */
366    public double getSampleDouble() {
367        return raster.getSampleDouble(x, y, getBand());
368    }
369
370    /**
371     * Compares all sample values iterated by this {@code PixelIterator} with the sample values
372     * iterated by the given iterator. If a mismatch is found, then an {@link AssertionError} is
373     * thrown with a detailed error message.
374     *
375     * <p>This method does not verify the image sizes, number of tiles, number of bands, color
376     * model or datatype. Consequently this method is robust to the following differences:</p>
377     *
378     * <ul>
379     *   <li>Differences in the ({@linkplain RenderedImage#getMinX() x},
380     *       {@linkplain RenderedImage#getMinY() y}) origin;</li>
381     *   <li>Differences in tile layout (images are compared as if they were untiled);</li>
382     *   <li>Differences in the datatype (values are compared using the widest of this iterator
383     *       {@linkplain #getDataType() datatype} and the datatype of the given iterator).</li>
384     * </ul>
385     *
386     * If the images have different sizes, then an <cite>"Unexpected end of iteration"</cite>
387     * exception will be thrown when the first iterator reaches the iteration end.
388     *
389     * @param  actual     the iterator that contains the actual values to be compared with the "expected" sample values.
390     * @param  tolerance  the tolerance threshold for floating point comparison. This threshold does not apply to integer types.
391     * @throws AssertionError if a value in this iterator is not equals to a value in the given iterator with the given
392     *         tolerance threshold.
393     */
394    public void assertSampleValuesEqual(final PixelIterator actual, final double tolerance) throws AssertionError {
395        final int dataType = Math.max(getDataType(), actual.getDataType());
396        while (next()) {
397            assertTrue("Unexpected end of pixel iteration.", actual.next());
398            switch (dataType) {
399                case DataBuffer.TYPE_DOUBLE: {
400                    final double a = actual.getSampleDouble();
401                    final double e = this.  getSampleDouble();
402                    if (doubleToLongBits(a) == doubleToLongBits(e)) {
403                        continue;                                       // All variants of NaN values are considered equal.
404                    }
405                    if (abs(a-e) <= tolerance) {
406                        continue;                                       // Negative and positive zeros are considered equal.
407                    }
408                    break;
409                }
410                case DataBuffer.TYPE_FLOAT: {
411                    final float a = actual.getSampleFloat();
412                    final float e = this.  getSampleFloat();
413                    if (floatToIntBits(a) == floatToIntBits(e)) {
414                        continue;                                       // All variants of NaN values are considered equal.
415                    }
416                    if (abs(a-e) <= tolerance) {
417                        continue;                                       // Negative and positive zeros are considered equal.
418                    }
419                    break;
420                }
421                default: {
422                    if (actual.getSample() == getSample()) continue;
423                    break;
424                }
425            }
426            /*
427             * Remainder of this block is for formatting the error message.
428             */
429            final Number ev, av;
430            switch (dataType) {
431                case DataBuffer.TYPE_DOUBLE: ev = getSampleDouble(); av = actual.getSampleDouble(); break;
432                case DataBuffer.TYPE_FLOAT:  ev = getSampleFloat();  av = actual.getSampleFloat();  break;
433                default:                     ev = getSample();       av = actual.getSample();       break;
434            }
435            final String lineSeparator = System.getProperty("line.separator", "\n");
436            final StringBuilder buffer = new StringBuilder(1024);
437            buffer.append("Mismatched sample value: expected ").append(ev).append(" but got ").append(av).append(lineSeparator);
438            buffer.append("Pixel coordinate in the complete image: "); position(buffer); buffer.append(lineSeparator);
439            buffer.append("Pixel coordinate in the compared image: "); actual.position(buffer); buffer.append(lineSeparator);
440            actual.completeComparisonFailureMessage(buffer, lineSeparator);
441            fail(buffer.toString());
442        }
443        assertFalse("Expected end of pixel iteration, but found more values.", actual.next());
444    }
445
446    /**
447     * Invoked when a sample value mismatch has been found, for allowing {@link PixelIteratorForIO}
448     * to append to the error message the I/O parameters used for the reading or writing process.
449     */
450    void completeComparisonFailureMessage(final StringBuilder buffer, final String lineSeparator) {
451    }
452
453    /**
454     * Formats the current position of this iterator in the given buffer.
455     */
456    private void position(final StringBuilder buffer) {
457        buffer.append('(').append(getX()).append(", ").append(getY()).append(") band ").append(getBand());
458    }
459
460    /**
461     * Returns a string representation of this iterator position for debugging purpose.
462     *
463     * @return a string representation if this iterator position.
464     */
465    @Override
466    public String toString() {
467        final StringBuilder buffer = new StringBuilder(48);
468        position(buffer.append(getClass().getSimpleName()).append('['));
469        return buffer.append(']').toString();
470    }
471}