blob: 7bca9dbf6a1d8f79db1ace5a9cfb9d81e847fd40 [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.nio.FloatBuffer;
import java.nio.DoubleBuffer;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.image.DataBuffer;
import java.awt.image.SampleModel;
import java.awt.image.BandedSampleModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.WritableRenderedImage;
/**
* A pixel iterator reading values directly from a {@link DataBuffer} instead of using {@link Raster} API.
* This iterator has the same behavior as the default implementation and is provided only for performance reasons.
* It can bring performance benefits when reading values as {@code float} or {@code double} values, but the benefits
* are more dubious for {@code int} values because Java2D has optimizations for that specific type.
*
* <p>This class assumes a {@link BandedSampleModel} or other models having an equivalently simple mapping from pixel
* coordinates to indices in banks. For other kinds of sample model, the default implementation should be used.
* More specifically assumptions are!</p>
*
* <ul>
* <li>One sample value per band, or (equivalently) only one band.</li>
* <li>{@linkplain ComponentSampleModel#getPixelStride() Pixel stride} equals to 1.</li>
* <li>{@linkplain ComponentSampleModel#getBankIndices() Bank indices} are the 0, 1, 2, … sequence.</li>
* <li>{@linkplain ComponentSampleModel#getBandOffsets() Band offsets} are all zero.</li>
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
*/
final class BandedIterator extends WritablePixelIterator {
/**
* The buffer from where to read data. This is the buffer backing {@link #currentRaster}.
* It is the lowest-level object we can use before the plain Java array. We do not fetch
* the Java array because doing so may cause Java2D to disable GPU accelerations.
*/
private DataBuffer buffer;
/**
* The buffer where to write data, or {@code null} if none.
* May be the same instance as {@link #buffer}.
*/
private DataBuffer destBuffer;
/**
* The translation from {@link SampleModel} coordinates to {@link Raster} coordinates.
*/
private int sampleModelTranslateX, sampleModelTranslateY;
/**
* The translation from {@link Raster} <var>x</var> coordinates to {@link #buffer} indices.
* This is constant for a row and needs to be updated only when {@link #y} changed.
* Given buffer index computed by the following formula:
*
* <pre>index = (y - sampleModelTranslateY) * scanlineStride + (x - sampleModelTranslateX)</pre>
*
* Then {@code xToBuffer} is above value with <var>x</var> = 0. This value may be negative.
*
* @see #changedRowOrTile()
*/
private int xToBuffer;
/**
* Number of {@linkplain #buffer} elements between a given sample and the corresponding sample
* in the same column of the next row. This value shall be the same for all tiles.
*/
private final int scanlineStride;
/**
* Creates an iterator for the given region in the given raster.
*
* @param input the raster which contains the sample values to read.
* @param output the raster where to write the sample values, or {@code null} for read-only iterator.
* @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.
* @param order {@code null} or {@link SequenceType#LINEAR}. Other values may be added in future versions.
* @param scanlineStride value of {@code getScanlineStride(input.getSampleModel()}. Shall be greater than zero.
*/
BandedIterator(final Raster input, final WritableRaster output, final Rectangle subArea,
final Dimension window, final SequenceType order, final int scanlineStride)
{
super(input, output, subArea, window, order);
this.scanlineStride = scanlineStride;
acquiredTile(input);
changedRowOrTile();
}
/**
* Creates an iterator for the given region in the given image.
*
* @param input the image which contains the sample values to read.
* @param output the image where to write the sample values, or {@code null} for read-only iterator.
* @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.
* @param order {@code null} or {@link SequenceType#LINEAR}. Other values may be added in future versions.
* @param scanlineStride value of {@code getScanlineStride(input.getSampleModel()}. Shall be greater than zero.
*/
BandedIterator(final RenderedImage input, final WritableRenderedImage output, final Rectangle subArea,
final Dimension window, final SequenceType order, final int scanlineStride)
{
super(input, output, subArea, window, order);
this.scanlineStride = scanlineStride;
// `acquiredTile(…)` will be invoked later by `fetchTile()`.
}
/**
* Recomputes {@link #xToBuffer} for the new {@link #y} value. This method shall be invoked
* when the iterator moved to a new row, or when the iterator fetched a new tile but only
* after the (x,y) coordinates have been updated.
*/
@Override
final void changedRowOrTile() {
xToBuffer = (y - sampleModelTranslateY) * scanlineStride - sampleModelTranslateX;
}
/**
* Moves the pixel iterator to the given column (x) and row (y) indices.
*
* @throws IndexOutOfBoundsException if the given indices are outside the iteration domain.
*/
@Override
public void moveTo(final int px, final int py) {
if (isSameRowAndTile(px, py)) {
x = px;
} else {
super.moveTo(px, py);
changedRowOrTile();
}
}
/**
* Invoked when the iterator fetched a new tile. This method updates {@code BandedIterator} fields
* with raster properties, except {@link #xToBuffer} which is not updated here because {@link #y}
* is not yet updated to its new value. {@code BandedIterator} must override all methods invoking
* {@link #fetchTile()} ane ensure that {@link #changedRowOrTile()} is invoked after (x,y) have
* been updated.
*/
@Override
final void acquiredTile(Raster tile) {
assert PixelIterator.Builder.getScanlineStride(tile.getSampleModel()) == scanlineStride;
sampleModelTranslateY = tile.getSampleModelTranslateY();
sampleModelTranslateX = tile.getSampleModelTranslateX();
buffer = tile.getDataBuffer();
tile = destination();
if (tile != null) {
destBuffer = tile.getDataBuffer();
assert PixelIterator.Builder.getScanlineStride(tile.getSampleModel()) == scanlineStride &&
tile.getSampleModelTranslateX() == sampleModelTranslateX &&
tile.getSampleModelTranslateY() == sampleModelTranslateY;
}
}
/**
* Releases the buffer acquired by this iterator, if any.
* This is safety for avoiding accidental usage of wrong buffer.
* Also avoid to retain large array if the tile is garbage collected.
*/
@Override
final void releaseTile() {
if (image != null) {
buffer = null;
destBuffer = null;
}
super.releaseTile();
}
/** Returns the sample value in the specified band of current pixel. */
@Override public int getSample (final int band) {return buffer.getElem (band, x + xToBuffer);}
@Override public float getSampleFloat (final int band) {return buffer.getElemFloat (band, x + xToBuffer);}
@Override public double getSampleDouble(final int band) {return buffer.getElemDouble(band, x + xToBuffer);}
@Override public void setSample(int band, int value) {destBuffer.setElem (band, x + xToBuffer, value);}
@Override public void setSample(int band, float value) {destBuffer.setElemFloat (band, x + xToBuffer, value);}
@Override public void setSample(int band, double value) {destBuffer.setElemDouble(band, x + xToBuffer, value);}
/**
* Returns the sample values of current pixel for all bands. If the iterator is not in a valid position
* as documented in parent class, then this method behavior is undetermined: It may either throw an
* {@link ArrayIndexOutOfBoundsException} or return a random value.
*/
@Override
public int[] getPixel(int[] dest) {
if (dest == null) {
dest = new int[numBands];
}
/*
* `getElement(index)` is synonymous to `getElement(0, index)` but possibly slightly faster
* since it is implemented with a single array access instead of 3. After that, the loop is
* not executed at all in the common case of an image with a single band.
*/
final int index = x + xToBuffer;
dest[0] = buffer.getElem(index);
for (int i=1; i<numBands; i++) {
dest[i] = buffer.getElem(i, index);
}
return dest;
}
/**
* Returns the sample values of current pixel for all bands. If the iterator is not in a valid position
* as documented in parent class, then this method behavior is undetermined: It may either throw an
* {@link ArrayIndexOutOfBoundsException} or return a random value.
*/
@Override
public float[] getPixel(float[] dest) {
if (dest == null) {
dest = new float[numBands];
}
final int index = x + xToBuffer;
dest[0] = buffer.getElemFloat(index); // See comment in `getPixel(int[])`.
for (int i=1; i<numBands; i++) {
dest[i] = buffer.getElemFloat(i, index);
}
return dest;
}
/**
* Returns the sample values of current pixel for all bands. If the iterator is not in a valid position
* as documented in parent class, then this method behavior is undetermined: It may either throw an
* {@link ArrayIndexOutOfBoundsException} or return a random value.
*/
@Override
public double[] getPixel(double[] dest) {
if (dest == null) {
dest = new double[numBands];
}
final int index = x + xToBuffer;
dest[0] = buffer.getElemDouble(index); // See comment in `getPixel(int[])`.
for (int i=1; i<numBands; i++) {
dest[i] = buffer.getElemDouble(i, index);
}
return dest;
}
/**
* Sets the sample values of current pixel for all bands. If the iterator is not in a valid position
* as documented in parent class, then this method behavior is undetermined: It may either throw an
* {@link ArrayIndexOutOfBoundsException} or return a random value.
*/
@Override
public void setPixel(final int[] values) {
final int index = x + xToBuffer;
destBuffer.setElem(index, values[0]); // See comment in `getPixel(int[])`.
for (int i=1; i<numBands; i++) {
destBuffer.setElem(i, index, values[i]);
}
}
/**
* Sets the sample values of current pixel for all bands. If the iterator is not in a valid position
* as documented in parent class, then this method behavior is undetermined: It may either throw an
* {@link ArrayIndexOutOfBoundsException} or return a random value.
*/
@Override
public void setPixel(final float[] values) {
final int index = x + xToBuffer;
destBuffer.setElemFloat(index, values[0]); // See comment in `getPixel(int[])`.
for (int i=1; i<numBands; i++) {
destBuffer.setElemFloat(i, index, values[i]);
}
}
/**
* Sets the sample values of current pixel for all bands. If the iterator is not in a valid position
* as documented in parent class, then this method behavior is undetermined: It may either throw an
* {@link ArrayIndexOutOfBoundsException} or return a random value.
*/
@Override
public void setPixel(final double[] values) {
final int index = x + xToBuffer;
destBuffer.setElemDouble(index, values[0]); // See comment in `getPixel(int[])`.
for (int i=1; i<numBands; i++) {
destBuffer.setElemDouble(i, index, values[i]);
}
}
/**
* Creates a window for floating point values using the given arrays.
*/
@Override Window<FloatBuffer> createWindow( float[] data, float[] transfer) {return new FloatWindow(data, transfer);}
@Override Window<DoubleBuffer> createWindow(double[] data, double[] transfer) {return new DoubleWindow(data, transfer);}
/**
* {@link Window} implementation backed by an array of {@code float[]}.
* This is a copy of {@link org.apache.sis.image.PixelIterator.FloatWindow}
* except in {@code getPixels(…)} implementation.
*/
private final class FloatWindow extends Window<FloatBuffer> {
/**
* Sample values in the window ({@code data}) and a temporary array ({@code transfer}).
* Those arrays are overwritten when {@link #update()} is invoked.
*/
private final float[] data, transfer;
/**
* Creates a new window which will store the sample values in the given {@code data} array.
*/
FloatWindow(final float[] data, final float[] transfer) {
super(FloatBuffer.wrap(data).asReadOnlyBuffer());
this.data = data;
this.transfer = transfer;
}
/**
* Returns the iterator that created this window.
*/
@Override
final PixelIterator owner() {
return BandedIterator.this;
}
/**
* Performs the transfer between the underlying raster and this window.
*/
@Override
Object getPixels(final Raster raster, int subX, int subY, final int subWidth, int subHeight, final int mode) {
final float[] target = (mode == DIRECT) ? data : transfer;
if (mode != TRANSFER_FROM_OTHER && subY == y) {
final DataBuffer source = buffer;
final int toNext = scanlineStride - subWidth;
final int numBds = numBands;
int srcOff = subX + xToBuffer;
int tgtOff = 0;
do {
int c = subWidth;
do {
target[tgtOff++] = source.getElemFloat(srcOff);
for (int b=1; b<numBds; b++) {
target[tgtOff++] = source.getElemFloat(b, srcOff);
}
srcOff++;
} while (--c != 0);
srcOff += toNext;
} while (--subHeight != 0);
return target;
}
// Fallback for all cases that we cannot handle with above loop.
return raster.getPixels(subX, subY, subWidth, subHeight, target);
}
/**
* Updates this window with the sample values in the region starting at current iterator position.
* This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
*/
@Override
public void update() {
values.clear();
fetchValues(this, data);
}
}
/**
* {@link Window} implementation backed by an array of {@code double[]}.
* This is a copy of {@link org.apache.sis.image.PixelIterator.DoubleWindow}
* except in {@code getPixels(…)} implementation.
*/
private final class DoubleWindow extends Window<DoubleBuffer> {
/**
* Sample values in the window ({@code data}) and a temporary array ({@code transfer}).
* Those arrays are overwritten when {@link #update()} is invoked.
*/
private final double[] data, transfer;
/**
* Creates a new window which will store the sample values in the given {@code data} array.
*/
DoubleWindow(final double[] data, final double[] transfer) {
super(DoubleBuffer.wrap(data).asReadOnlyBuffer());
this.data = data;
this.transfer = transfer;
}
/**
* Returns the iterator that created this window.
*/
@Override
final PixelIterator owner() {
return BandedIterator.this;
}
/**
* Performs the transfer between the underlying raster and this window.
*/
@Override
Object getPixels(final Raster raster, int subX, int subY, final int subWidth, int subHeight, final int mode) {
final double[] target = (mode == DIRECT) ? data : transfer;
if (mode != TRANSFER_FROM_OTHER && subY == y) {
final DataBuffer source = buffer;
final int toNext = scanlineStride - subWidth;
final int numBds = numBands;
int srcOff = subX + xToBuffer;
int tgtOff = 0;
do {
int c = subWidth;
do {
target[tgtOff++] = source.getElemDouble(srcOff);
for (int b=1; b<numBds; b++) {
target[tgtOff++] = source.getElemDouble(b, srcOff);
}
srcOff++;
} while (--c != 0);
srcOff += toNext;
} while (--subHeight != 0);
return target;
}
// Fallback for all cases that we cannot handle with above loop.
return raster.getPixels(subX, subY, subWidth, subHeight, target);
}
/**
* Updates this window with the sample values in the region starting at current iterator position.
* This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
*/
@Override
public void update() {
values.clear();
fetchValues(this, data);
}
}
}