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}