blob: b73916935f3b22055473daf779b0dba4e1e45445 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.image;
import java.util.Arrays;
import java.util.Optional;
import java.nio.Buffer;
import java.awt.Point;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.WritableRenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.util.NoSuchElementException;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.measure.NumberRange;
import static java.lang.Math.floorDiv;
import static org.apache.sis.internal.util.Numerics.ceilDiv;
/**
* An iterator over sample values in a raster or an image. This iterator makes easier to read and write efficiently
* pixel or sample values. The iterator {@linkplain RenderedImage#getTile(int,int) acquires tiles} and releases them
* automatically. Unless otherwise specified, iterators are free to use an {@linkplain #getIterationOrder() iteration
* order} that minimize the "acquire / release tile" operations (in other words, iterations are not necessarily from
* left to right). Iteration can be performed on a complete image or only a sub-region of it. Some optimized iterator
* implementations exist for a few commonly used {@linkplain java.awt.image.SampleModel sample models}.
*
* <div class="note"><b>Example:</b>
* {@preformat java
* PixelIterator it = PixelIterator.create(image);
* double[] samples = null;
* while (it.next()) {
* samples = it.getPixel(samples); // Get values in all bands.
* // Perform computation here...
* }
* }
* </div>
*
* @author Rémi Maréchal (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @author Johann Sorel (Geomatys)
* @version 1.0
* @since 1.0
* @module
*/
public abstract class PixelIterator {
/**
* The image in which iteration is occurring, or {@code null} if none.
* If {@code null}, then {@link #currentRaster} must be non-null.
*/
final RenderedImage image;
/**
* The current raster in which iteration is occurring. This may change when the iterator
* reaches a new {@link #image} tile. May be {@code null} if not yet determined.
*
* @see RenderedImage#getTile(int, int)
*/
Raster currentRaster;
/**
* Number of bands in all tiles in the {@linkplain #image}.
* The {@link #currentRaster} shall always have this number of bands.
*/
final int numBands;
/**
* The domain, in pixel coordinates, of the region traversed by this pixel iterator.
* This may be smaller than the image or raster bounds, but not greater.
* The lower values are inclusive and the upper values exclusive.
*
* @see #getDomain()
*/
final int lowerX, lowerY, upperX, upperY;
/**
* Size of all tiles in the {@link #image}.
*/
final int tileWidth, tileHeight;
/**
* The X and Y coordinate of the upper-left pixel of tile (0,0).
* Note that tile (0,0) may not actually exist.
*/
final int tileGridXOffset, tileGridYOffset;
/**
* The domain, in tile coordinates, of the region traversed by this pixel iterator.
* This may be smaller than the image or raster tile grid bounds, but not greater.
* The lower values are inclusive and the upper values exclusive.
*/
final int tileLowerX, tileLowerY, tileUpperX, tileUpperY;
/**
* Size of the window to use in {@link #createWindow(TransferType)} method, or {@code 0} if none.
*/
final int windowWidth, windowHeight;
/**
* Creates an iterator for the given region in the given raster.
*
* @param data the raster which contains the sample values on which to iterate.
* @param subArea the raster region where to perform the iteration, or {@code null}
* for iterating over all the raster domain.
* @param window size of the window to use in {@link #createWindow(TransferType)} method, or {@code null} if none.
*/
PixelIterator(final Raster data, final Rectangle subArea, final Dimension window) {
final Rectangle bounds;
image = null;
currentRaster = data;
numBands = data.getNumBands();
tileWidth = data.getWidth();
tileHeight = data.getHeight();
tileGridXOffset = data.getMinX();
tileGridYOffset = data.getMinY();
tileLowerX = 0; // In this case only one raster: tile index is fixed to 0.
tileLowerY = 0;
tileUpperX = 1;
tileUpperY = 1;
bounds = intersection(tileGridXOffset, tileGridYOffset, tileWidth, tileHeight, subArea, window);
lowerX = bounds.x;
lowerY = bounds.y;
upperX = Math.addExact(lowerX, bounds.width);
upperY = Math.addExact(lowerY, bounds.height);
windowWidth = (window != null) ? window.width : 0;
windowHeight = (window != null) ? window.height : 0;
}
/**
* Creates an iterator for the given region in the given image.
*
* @param data the image which contains the sample values on which to iterate.
* @param subArea the image region where to perform the iteration, or {@code null}
* for iterating over all the image domain.
* @param window size of the window to use in {@link #createWindow(TransferType)} method, or {@code null} if none.
*/
PixelIterator(final RenderedImage data, final Rectangle subArea, final Dimension window) {
final Rectangle bounds;
image = data;
numBands = data.getSampleModel().getNumBands();
tileWidth = data.getTileWidth();
tileHeight = data.getTileHeight();
tileGridXOffset = data.getTileGridXOffset();
tileGridYOffset = data.getTileGridYOffset();
bounds = intersection(data.getMinX(), data.getMinY(), data.getWidth(), data.getHeight(), subArea, window);
lowerX = bounds.x;
lowerY = bounds.y;
upperX = Math.addExact(lowerX, bounds.width);
upperY = Math.addExact(lowerY, bounds.height);
tileLowerX = floorDiv(Math.subtractExact(lowerX, tileGridXOffset), tileWidth);
tileLowerY = floorDiv(Math.subtractExact(lowerY, tileGridYOffset), tileHeight);
tileUpperX = ceilDiv(Math.subtractExact(upperX, tileGridXOffset), tileWidth);
tileUpperY = ceilDiv(Math.subtractExact(upperY, tileGridYOffset), tileHeight);
windowWidth = (window != null) ? window.width : 0;
windowHeight = (window != null) ? window.height : 0;
}
/**
* Computes the intersection between the given bounds and and {@code subArea} if {@code subArea} is non-null.
* If the result is empty, then the width and/or height are set to zero (not negative).
*/
private static Rectangle intersection(int x, int y, int width, int height, Rectangle subArea, Dimension window) {
if (window != null) {
ArgumentChecks.ensureBetween("window.width", 1, width, window.width);
ArgumentChecks.ensureBetween("window.height", 1, height, window.height);
width -= (window.width - 1);
height -= (window.height - 1);
}
Rectangle bounds = new Rectangle(x, y, width, height);
if (subArea != null) {
bounds = bounds.intersection(subArea);
if (bounds.width < 0) bounds.width = 0;
if (bounds.height < 0) bounds.height = 0;
}
return bounds;
}
/**
* Builds pixel iterators for specified region of interest, window size or iteration order.
* By default, the builder creates iterators for all pixels in the given raster or image,
* with unspecified iteration order. Users can invoke setter methods for specifying
* desired behavior for the iterators to create.
*
* <div class="note"><b>Example:</b>
* {@preformat java
* PixelIterator iterator = new PixelIterator.Builder().setRegionOfInterest(new Rectangle(10, 10, 5, 5).create(image);
* }
* </div>
*/
public static class Builder {
/**
* The region where to perform the iteration, or {@code null} for iterating over all the domain.
*/
private Rectangle subArea;
/**
* Size of the window to use in {@link PixelIterator#createWindow(TransferType)} method,
* or {@code null} if none.
*/
private Dimension window;
/**
* The desired iteration order, or {@code null} for a default order.
*/
private SequenceType order;
/**
* Creates a new iterator builder with no region of interest, no window size and default iterator order.
*/
public Builder() {
}
/**
* Sets the region (in pixel coordinates) where to perform the iteration.
* By default, iterators will traverse all pixels in the given image or raster.
*
* @param subArea region where to iterator, or {@code null} for iterating over all image domain.
* @return {@code this} for method call chaining.
*/
public Builder setRegionOfInterest(final Rectangle subArea) {
this.subArea = subArea;
return this;
}
/**
* Sets the size of the window to use in {@link PixelIterator#createWindow(TransferType)} method.
* By default, iterators do not create windows.
*
* @param window the window size, or {@code null} if no window will be created.
* @return {@code this} for method call chaining.
*/
public Builder setWindowSize(final Dimension window) {
this.window = window;
return this;
}
/**
* Sets the desired iteration order.
* The {@code order} argument can have the following values:
*
* <table class="sis">
* <caption>Supported iteration order</caption>
* <tr><th>Value</th> <th>Iteration order</th> <th>Supported on</th></tr>
* <tr><td>{@code null}</td> <td>Most efficient iteration order.</td> <td>Image and raster</td></tr>
* <tr><td>{@link SequenceType#LINEAR}</td> <td>From left to right, then from top to bottom.</td> <td>Raster only</td></tr>
* </table>
*
* Any other {@code order} value will cause an {@link IllegalArgumentException} to be thrown.
* More iteration orders may be supported in future Apache SIS versions.
*
* @param order the desired iteration order, or {@code null} for a default order.
* @return {@code this} for method call chaining.
*/
final Builder setIteratorOrder(final SequenceType order) {
if (order == null || order.equals(SequenceType.LINEAR)) {
this.order = order;
} else {
throw new IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedType_1, order));
}
return this;
}
/**
* Creates a read-only iterator for the given raster.
*
* @param data the raster which contains the sample values on which to iterate.
* @return a new iterator traversing pixels in the given raster.
*/
public PixelIterator create(final Raster data) {
ArgumentChecks.ensureNonNull("data", data);
if (order == SequenceType.LINEAR) {
return new LinearIterator(data, null, subArea, window);
} else if (order != null) {
throw new IllegalStateException(Errors.format(Errors.Keys.UnsupportedType_1, order));
}
// TODO: check here for cases that we can optimize (after we ported corresponding implementations).
return new DefaultIterator(data, null, subArea, window);
}
/**
* Creates a read-only iterator for the given image.
*
* @param data the image which contains the sample values on which to iterate.
* @return a new iterator traversing pixels in the given image.
*/
public PixelIterator create(final RenderedImage data) {
ArgumentChecks.ensureNonNull("data", data);
if (order == SequenceType.LINEAR) {
return new LinearIterator(data, null, subArea, window);
} else if (order != null) {
throw new IllegalStateException(Errors.format(Errors.Keys.UnsupportedType_1, order));
}
// TODO: check here for cases that we can optimize (after we ported corresponding implementations).
return new DefaultIterator(data, null, subArea, window);
}
/**
* Creates a read/write iterator for the given raster.
*
* @param data the raster which contains the sample values on which to iterate.
* @return a new iterator traversing pixels in the given raster.
*/
public WritablePixelIterator createWritable(final WritableRaster data) {
ArgumentChecks.ensureNonNull("data", data);
return createWritable(data, data);
}
/**
* Creates a read/write iterator for the given image.
*
* @param data the image which contains the sample values on which to iterate.
* @return a new iterator traversing pixels in the given image.
*/
public WritablePixelIterator createWritable(final WritableRenderedImage data) {
ArgumentChecks.ensureNonNull("data", data);
return createWritable(data, data);
}
/**
* Creates an iterator which will read and write in two different rasters.
*
* @param input the raster which contains the sample values to read.
* @param output the raster where to write the sample values. Can be the same than {@code input}.
* @return a new writable iterator.
*/
public WritablePixelIterator createWritable(final Raster input, final WritableRaster output) {
ArgumentChecks.ensureNonNull("input", input);
ArgumentChecks.ensureNonNull("output", output);
if (order == SequenceType.LINEAR) {
return new LinearIterator(input, output, subArea, window);
} else if (order != null) {
throw new IllegalStateException(Errors.format(Errors.Keys.UnsupportedType_1, order));
}
// TODO: check here for cases that we can optimize (after we ported corresponding implementations).
return new DefaultIterator(input, output, subArea, window);
}
/**
* Creates an iterator which will read and write in two different images.
*
* @param input the image which contains the sample values to read.
* @param output the image where to write the sample values. Can be the same than {@code input}.
* @return a new writable iterator.
*/
public WritablePixelIterator createWritable(final RenderedImage input, final WritableRenderedImage output) {
ArgumentChecks.ensureNonNull("input", input);
ArgumentChecks.ensureNonNull("output", output);
if (order == SequenceType.LINEAR) {
return new LinearIterator(input, output, subArea, window);
} else if (order != null) {
throw new IllegalStateException(Errors.format(Errors.Keys.UnsupportedType_1, order));
}
// TODO: check here for cases that we can optimize (after we ported corresponding implementations).
return new DefaultIterator(input, output, subArea, window);
}
}
/**
* Creates an iterator for all pixels in the given image.
* This is a convenience method for {@code new Builder().create(data)}.
*
* @param data the image which contains the sample values on which to iterate.
* @return a new iterator traversing all pixels in the given image, in arbitrary order.
*/
public static PixelIterator create(final RenderedImage data) {
return new Builder().create(data);
}
/**
* Returns {@code true} if this iterator can write pixel values (after cast to {@code WritablePixelIterator}).
* This method should be used instead than {@code instanceof} check because, for some implementations, being
* an instance of {@code WritablePixelIterator} is not a sufficient condition.
*
* @return {@code true} if this iterator can safely be casted to {@link WritablePixelIterator} and used for
* writing pixel values.
*/
public boolean isWritable() {
return false;
}
/**
* Returns the most efficient type ({@code int}, {@code float} or {@code double}) for transferring data between the
* underlying rasters and this iterator. The transfer type is not necessarily the storage type used by the rasters.
* For example {@code int} values will be used for transferring data even if the underlying rasters store all sample
* values as {@code byte}s.
*
* <p>The transfer type is only a hint since all iterator methods work for any type (conversions are applied as needed).
* However if this method returns {@link TransferType#INT}, then {@link #getSample(int)} and {@link #getPixel(int[])}
* will be slightly more efficient than equivalent methods for other types. Conversely if this method returns
* {@link TransferType#DOUBLE}, then {@link #getSampleDouble(int)} will be both more efficient and avoid accuracy lost.</p>
*
* @return the most efficient data type for transferring data.
*/
public TransferType<?> getTransferType() {
return TransferType.valueOf(image != null ? image.getSampleModel().getTransferType() : currentRaster.getTransferType());
}
/**
* Returns the range of sample values that can be stored in each band of the rendered image or raster.
* The ranges depend on the data type (byte, integer, <i>etc.</i>) and the number of bits per sample.
* If the samples are stored as floating point values, then the ranges are infinite (unbounded).
*
* <p>Usually, the range is the same for all bands. A situation where the ranges may differ is when an
* image uses {@link SinglePixelPackedSampleModel}, in which case the number of bits per pixel may vary
* for different bands.</p>
*
* @return the ranges of valid sample values for each band. Ranges may be {@linkplain NumberRange#isBounded() unbounded}.
*/
public NumberRange<?>[] getSampleRanges() {
final SampleModel model = (currentRaster != null) ? currentRaster.getSampleModel() : image.getSampleModel();
final NumberRange<?>[] ranges = new NumberRange<?>[model.getNumBands()];
final NumberRange<?> range;
if (model instanceof MultiPixelPackedSampleModel) {
/*
* This model supports only unsigned integer types: DataBuffer.TYPE_BYTE, DataBuffer.TYPE_USHORT
* or DataBuffer.TYPE_INT (considered unsigned in the context of this sample model). The number
* of bits per sample is defined by the "pixel bit stride".
*/
final int numBits = ((MultiPixelPackedSampleModel) model).getPixelBitStride();
range = NumberRange.create(0, true, (1 << numBits) - 1, true);
} else if (model instanceof SinglePixelPackedSampleModel) {
/*
* This model supports only unsigned integer types: TYPE_BYTE, TYPE_USHORT, TYPE_INT (considered
* unsigned in the context of this sample model). The number of bits may vary for each band.
*/
final int[] masks = ((SinglePixelPackedSampleModel) model).getBitMasks();
for (int i=0; i<masks.length; i++) {
final int numBits = Integer.bitCount(masks[i]);
ranges[i] = NumberRange.create(0, true, (1 << numBits) - 1, true);
}
return ranges;
} else {
/*
* For all other sample models, the range is determined by the data type.
* The following cases invoke the NumberRange constructor which best fit the data type.
*/
final int type = model.getDataType();
switch (type) {
case DataBuffer.TYPE_BYTE: range = NumberRange.create((short) 0, true, (short) 0xFF, true); break;
case DataBuffer.TYPE_USHORT: range = NumberRange.create( 0, true, 0xFFFF, true); break;
case DataBuffer.TYPE_SHORT: range = NumberRange.create(Short. MIN_VALUE, true, Short. MAX_VALUE, true); break;
case DataBuffer.TYPE_INT: range = NumberRange.create(Integer.MIN_VALUE, true, Integer.MAX_VALUE, true); break;
case DataBuffer.TYPE_FLOAT: range = NumberRange.create(Float. NEGATIVE_INFINITY, false, Float. POSITIVE_INFINITY, false); break;
case DataBuffer.TYPE_DOUBLE: range = NumberRange.create(Double. NEGATIVE_INFINITY, false, Double. POSITIVE_INFINITY, false); break;
default: throw new IllegalStateException(Errors.format(Errors.Keys.UnknownType_1, type));
}
}
Arrays.fill(ranges, range);
return ranges;
}
/**
* Returns the order in which pixels are traversed. {@link SequenceType#LINEAR} means that pixels on the first
* row are traversed from left to right, then pixels on the second row from left to right, <i>etc.</i>
* An empty value means that the iteration order is unspecified.
*
* @return order in which pixels are traversed.
*/
abstract Optional<SequenceType> getIterationOrder();
/**
* Returns the number of bands (samples per pixel) in the image or raster.
*
* @return number of bands.
*/
public int getNumBands() {
return numBands;
}
/**
* Returns the pixel coordinates of the region where this iterator is doing the iteration.
* If no region was specified at construction time, then this method returns the image or raster bounds.
*
* @return pixel coordinates of the iteration region.
*/
public Rectangle getDomain() {
return new Rectangle(lowerX, lowerY, upperX - lowerX, upperY - lowerY);
}
/**
* Returns the column (x) and row (y) indices of the current pixel.
* The {@link #next()} or {@link #moveTo(int,int)} method must have been invoked before this method.
* Indices of the first pixel are not necessarily zero; they can even be negative.
*
* @return column and row indices of current iterator position.
* @throws IllegalStateException if this method is invoked before the first call to {@link #next()}
* or {@link #moveTo(int,int)}, or after {@code next()} returned {@code false}.
*/
public abstract Point getPosition();
/**
* Moves the pixel iterator to the given column (x) and row (y) indices. After this method invocation,
* the iterator state is as if the {@link #next()} method has been invoked just before to reach the
* specified position.
*
* <div class="note"><b>Usage example:</b>
* {@preformat java
* iterator.moveTo(x, y);
* do {
* int sample = iterator.getSample(band);
* // Use sample value here...
* } while (iterator.next());
* }
* </div>
*
* @param x the column index of the pixel to make current.
* @param y the row index of the pixel to make current.
* @throws IndexOutOfBoundsException if the given indices are outside the iteration domain.
*/
public abstract void moveTo(int x, int y);
/**
* Moves the iterator to the next pixel. A pixel iterator is initially positioned before the first pixel.
* The first call to {@code next()} makes the first pixel the current one; the second call makes the second
* pixel the current one, <i>etc.</i> The second pixel is not necessarily on the same row than the first one;
* iteration order is implementation dependent.
*
* <p>When a call to {@code next()} returns {@code false}, the iterator is positioned after the last pixel.
* Any invocation of a {@code getSample(int)} method will result in a {@link NoSuchElementException} to be
* thrown.</p>
*
* @return {@code true} if the current pixel is valid, or {@code false} if there is no more pixels.
* @throws IllegalStateException if this iterator already reached end of iteration in a previous call
* to {@code next()}, and {@link #rewind()} or {@link #moveTo(int,int)} have not been invoked.
*/
public abstract boolean next();
/**
* Returns the sample value in the specified band of current pixel, rounded toward zero.
* The {@link #next()} method must have returned {@code true}, or the {@link #moveTo(int,int)} method must have
* been invoked successfully, before this {@code getSample(int)} method is invoked. If above condition is not met,
* then this method behavior is undefined: it may throw any runtime exception or return a meaningless value
* (there is no explicit bounds check for performance reasons).
*
* @param band the band for which to get the sample value.
* @return sample value in specified band of current pixel.
*
* @see Raster#getSample(int, int, int)
*/
public abstract int getSample(int band);
/**
* Returns the sample value in the specified band of current pixel as a single-precision floating point number.
* The {@link #next()} method must have returned {@code true}, or the {@link #moveTo(int,int)} method must have
* been invoked successfully, before this {@code getSampleFloat(int)} method is invoked. If above condition is
* not met, then this method behavior is undefined: it may throw any runtime exception or return a meaningless
* value (there is no explicit bounds check for performance reasons).
*
* @param band the band for which to get the sample value.
* @return sample value in specified band of current pixel.
*
* @see Raster#getSampleFloat(int, int, int)
*/
public abstract float getSampleFloat(int band);
/**
* Returns the sample value in the specified band of current pixel, without precision lost.
* The {@link #next()} method must have returned {@code true}, or the {@link #moveTo(int,int)} method must have
* been invoked successfully, before this {@code getSampleDouble(int)} method is invoked. If above condition is
* not met, then this method behavior is undefined: it may throw any runtime exception or return a meaningless
* value (there is no explicit bounds check for performance reasons).
*
* @param band the band for which to get the sample value.
* @return sample value in specified band of current pixel.
*
* @see Raster#getSampleDouble(int, int, int)
*/
public abstract double getSampleDouble(int band);
/**
* Returns the sample values of current pixel for all bands.
* The {@link #next()} method must have returned {@code true}, or the {@link #moveTo(int,int)} method must have
* been invoked successfully, before this {@code getPixel(…)} method is invoked. If above condition is not met,
* then this method behavior is undefined: it may throw any runtime exception or return a meaningless value
* (there is no explicit bounds check for performance reasons).
*
* @param dest a pre-allocated array where to store the sample values, or {@code null} if none.
* @return the sample values for current pixel.
*
* @see Raster#getPixel(int, int, int[])
*/
public abstract int[] getPixel​(int[] dest);
/**
* Returns the sample values of current pixel for all bands.
* The {@link #next()} method must have returned {@code true}, or the {@link #moveTo(int,int)} method must have
* been invoked successfully, before this {@code getPixel(…)} method is invoked. If above condition is not met,
* then this method behavior is undefined: it may throw any runtime exception or return a meaningless value
* (there is no explicit bounds check for performance reasons).
*
* @param dest a pre-allocated array where to store the sample values, or {@code null} if none.
* @return the sample values for current pixel.
*
* @see Raster#getPixel(int, int, float[])
*/
public abstract float[] getPixel​(float[] dest);
/**
* Returns the sample values of current pixel for all bands.
* The {@link #next()} method must have returned {@code true}, or the {@link #moveTo(int,int)} method must have
* been invoked successfully, before this {@code getPixel(…)} method is invoked. If above condition is not met,
* then this method behavior is undefined: it may throw any runtime exception or return a meaningless value
* (there is no explicit bounds check for performance reasons).
*
* @param dest a pre-allocated array where to store the sample values, or {@code null} if none.
* @return the sample values for current pixel.
*
* @see Raster#getPixel(int, int, double[])
*/
public abstract double[] getPixel​(double[] dest);
/**
* Returns a moving window over the sample values in a rectangular region starting at iterator position.
* The <cite>window size</cite> must have been specified at {@code PixelIterator} construction time.
* The current iterator position is the window corner having the smallest <var>x</var> and <var>y</var> coordinates.
* This is typically, but not necessarily (depending on axis orientations) the window upper-left corner.
* Sample values are stored in a sequence of length
* <var>(number of bands)</var> × <var>(window width)</var> × <var>(window height)</var>.
* Values are always stored with band index varying fastest, then column index, then row index.
* Columns are traversed from left to right and rows are traversed from top to bottom
* (linear iteration order).
* That order is the same regardless the {@linkplain #getIterationOrder() iteration order} of this iterator.
*
* <div class="note"><b>Example:</b>
* for an RGB image, the 3 first values are the red, green and blue components of the pixel at
* {@linkplain #getPosition() current iterator position}. The 3 next values are the red, green
* and blue components of the pixel at the right of current iterator position (not necessarily
* the position where a call to {@link #next()} would have go), <i>etc.</i></div>
*
* Calls to {@link #next()} or {@link #moveTo(int,int)} followed by {@link Window#update()}
* replaces the window content with values starting at the new iterator position.
* Before the first {@link Window#update()} invocation, the window is filled with zero values.
*
* <p>If this iterator is used for
* {@linkplain WritablePixelIterator#setPixel(int[]) writing pixel values at current position},
* those write operations may change the content of windows at {@linkplain #next() next positions}
* unless the iteration order of this iterator is {@link SequenceType#LINEAR}.</p>
*
* <div class="note"><b>Usage example:</b>
* following code creates an iterator over the full area of given image, then a window of 5×5 pixels.
* The window is moved over all the image area in iteration order. Inside the window, data are copied
* in linear order regardless the iteration order.
*
* {@preformat java
* PixelIterator it = create(image, null, new Dimension(5, 5), null); // Windows size will be 5×5 pixels.
* PixelIterator<FloatBuffer> window = it.createWindow(TransferType.FLOAT);
* FloatBuffer values = window.values;
* while (it.next()) {
* window.update();
* while (buffer.hasRemaining()) {
* float sample = buffer.get();
* // use the sample value here.
* }
* }
* }
* </div>
*
* @param <T> the type of the data buffer to use for transferring data.
* @param type the desired type of values ({@code int}, {@code float} or {@code double}).
* Use {@link #getTransferType()} if the most efficient type is desired.
* @return a window over the sample values in the underlying image or raster.
*
* @see Raster#getPixels(int, int, int, int, double[])
*/
public abstract <T extends Buffer> Window<T> createWindow(TransferType<T> type);
/**
* Contains the sample values in a moving window over the image. Windows are created by calls to
* {@link PixelIterator#createWindow(TransferType)} and sample values are stored in {@link Buffer}s.
* The buffer content is replaced ever time {@link #update()} is invoked.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
*
* @param <T> the type of buffer which can be used for transferring data.
*
* @since 0.8
* @module
*/
public abstract static class Window<T extends Buffer> {
/**
* A buffer containing all sample values fetched by the last call to {@link #update()}. The buffer
* capacity is <var>(number of bands)</var> × <var>(window width)</var> × <var>(window height)</var>.
* Values are always stored with band index varying fastest, then column index, then row index.
* Columns are traversed from left to right and rows are traversed from top to bottom
* (linear iteration order).
* That order is the same regardless the iteration order
* of enclosing iterator.
*
* <p>Every time that {@link #update()} is invoked, the buffer content is replaced by sample values
* starting at the {@linkplain PixelIterator#getPosition() current iterator position}.
* Before the first {@code update()} invocation, the buffer is filled with zero values.</p>
*/
public final T values;
/**
* Creates a new window which will store the sample values in the given buffer.
*/
Window(final T buffer) {
values = buffer;
}
/**
* Updates this window with the sample values in the region starting at current iterator position.
* The buffer position, limit and mark are {@linkplain Buffer#clear() cleared}.
*
* <p>The {@link #next()} method must have returned {@code true}, or the {@link #moveTo(int,int)} method must have
* been invoked successfully, before this {@code update()} method is invoked. If above condition is not met,
* then this method behavior is undefined: it may throw any runtime exception or return meaningless values
* (there is no explicit bounds check for performance reasons).</p>
*/
public abstract void update();
}
/**
* Restores the iterator to the start position. After this method has been invoked,
* the iterator is in the same state than after construction.
*/
public abstract void rewind();
}