sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/03: Retrofit `DefaultIterator` into `PixelIterator`. This change allows slightly better encapsulation (more fields can be made private) and reduce the need to create `WritablePixelIterator` when only read operations are desired. The previous abstraction level was apparently not needed in practice.
Date Tue, 29 Dec 2020 17:27:24 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

commit e5142b57c383620bb127cefe30dccf73858ce041
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sun Dec 27 14:12:29 2020 +0100

    Retrofit `DefaultIterator` into `PixelIterator`.
    This change allows slightly better encapsulation (more fields can be made private) and
    reduce the need to create `WritablePixelIterator` when only read operations are desired.
    The previous abstraction level was apparently not needed in practice.
---
 .../java/org/apache/sis/image/DefaultIterator.java | 725 ---------------------
 .../java/org/apache/sis/image/LinearIterator.java  |  10 +-
 .../java/org/apache/sis/image/PixelIterator.java   | 520 ++++++++++++++-
 .../apache/sis/image/WritablePixelIterator.java    |  66 +-
 .../org/apache/sis/image/LinearIteratorTest.java   |   2 +-
 ...ultIteratorTest.java => PixelIteratorTest.java} |  18 +-
 .../apache/sis/test/suite/FeatureTestSuite.java    |   2 +-
 7 files changed, 561 insertions(+), 782 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java
deleted file mode 100644
index 35f0506..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java
+++ /dev/null
@@ -1,725 +0,0 @@
-/*
- * 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.Optional;
-import java.awt.Point;
-import java.awt.Dimension;
-import java.awt.Rectangle;
-import java.awt.Shape;
-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.RasterFormatException;
-import java.nio.Buffer;
-import java.nio.IntBuffer;
-import java.nio.FloatBuffer;
-import java.nio.DoubleBuffer;
-import org.opengis.coverage.grid.SequenceType;
-import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.ArgumentChecks;
-
-
-/**
- * Default iterator used when no specialized implementation is available.
- * This iterator uses the {@link Raster} API for traversing the pixels in each tile.
- * Calls to {@link #next()} move the current position by increasing the following values, in order:
- *
- * <ol>
- *   <li>Column index in a single tile (from left to right)</li>
- *   <li>Row index in a single tile (from top to bottom).</li>
- *   <li>Then, {@code tileX} index from left to right.</li>
- *   <li>Then, {@code tileY} index from top to bottom.</li>
- * </ol>
- *
- * @author  Rémi Maréchal (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.0
- * @module
- *
- * @todo Change iteration order on tiles for using Hilbert iterator.
- */
-class DefaultIterator extends WritablePixelIterator {
-    /**
-     * Tile coordinate of {@link #currentRaster}.
-     * The {@code tileY >= tileUpperY} condition is used for detecting when we reached iteration end.
-     */
-    int tileX, tileY;
-
-    /**
-     * Current column index in current raster.
-     * The {@code x >= lowerX} condition is used for detecting if iteration started.
-     */
-    int x;
-
-    /**
-     * Current row index in current raster.
-     */
-    int y;
-
-    /**
-     * Bounds of the region traversed by the iterator in current raster.
-     * When iteration reaches the upper coordinates, the iterator needs to move to next tile.
-     */
-    int currentLowerX, currentUpperX, currentUpperY;
-
-    /**
-     * 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.
-     */
-    DefaultIterator(final Raster input, final WritableRaster output, final Rectangle subArea, final Dimension window) {
-        super(input, output, subArea, window);
-        currentLowerX = lowerX;
-        currentUpperX = upperX;
-        currentUpperY = upperY;
-        x = Math.decrementExact(lowerX);        // Set the position before first pixel.
-        y = lowerY;
-    }
-
-    /**
-     * 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.
-     */
-    DefaultIterator(final RenderedImage input, final WritableRenderedImage output, final Rectangle subArea, final Dimension window) {
-        super(input, output, subArea, window);
-        tileX = Math.decrementExact(tileLowerX);
-        tileY = tileLowerY;
-        currentLowerX = lowerX;
-        currentUpperX = lowerX;                 // Really 'lower', so the position is the tile before the first tile.
-        currentUpperY = lowerY;
-        x = Math.decrementExact(lowerX);        // Set the position before first pixel.
-        y = lowerY;
-        /*
-         * We need to ensure that `tileUpperY+1 > tileUpperY` will alway be true because `tileY` may be equal
-         * to `tileUpperY` when the `if (++tileY >= tileUpperY)` statement is excuted in the `next()` method.
-         * This is because `tileY` is used as a sentinel value for detecting when we reached iteration end.
-         */
-        if (tileUpperY == Integer.MAX_VALUE) {
-            throw new ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Integer.SIZE));
-        }
-    }
-
-    /**
-     * Restores this iterator to the same state it was after construction.
-     */
-    @Override
-    public void rewind() {
-        close();                        // Release current writable raster, if any.
-        if (image == null) {
-            tileX = 0;
-            tileY = 0;
-            currentUpperX = upperX;
-            currentUpperY = upperY;
-        } else {
-            tileX = tileLowerX - 1;     // Note: no need for decrementExact(…) because already checked by constructor.
-            tileY = tileLowerY;
-            currentUpperX = lowerX;     // Really 'lower', so the position is the tile before the first tile.
-            currentUpperY = lowerY;
-        }
-        currentLowerX = lowerX;
-        x = lowerX - 1;                 // Set the position before first pixel.
-        y = lowerY;
-    }
-
-    /**
-     * Returns the order in which pixels are traversed.
-     */
-    @Override
-    public Optional<SequenceType> getIterationOrder() {
-        if (image == null || (tileUpperX - tileLowerX) <=1 && (tileUpperY - tileLowerY) <= 1) {
-            return Optional.of(SequenceType.LINEAR);
-        } else {
-            return Optional.empty();                // Undefined order.
-        }
-    }
-
-    /**
-     * Returns the column (x) and row (y) indices of the current pixel.
-     * This implementation checks {@link #x} and {@link #tileY} for determining if the iteration is valid.
-     *
-     * @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}.
-     */
-    @Override
-    public Point getPosition() {
-        final short message;
-        if (x < lowerX) {
-            message = Resources.Keys.IterationNotStarted;
-        } else if (tileY >= tileUpperY) {
-            message = Resources.Keys.IterationIsFinished;
-        } else {
-            return new Point(x,y);
-        }
-        throw new IllegalStateException(Resources.format(message));
-    }
-
-    /**
-     * Returns {@code true} if current iterator position is inside the given shape.
-     * Current version does not verify if iteration started or finished
-     * (this method is non-public for that reason).
-     *
-     * @param  domain  the shape for which to test inclusion.
-     * @return whether current iterator position is inside the given shape.
-     */
-    @Override
-    boolean isInside(final Shape domain) {
-        return domain.contains(x, y);
-    }
-
-    /**
-     * Moves the pixel iterator to the given column (x) and row (y) indices.
-     *
-     * @param  px  the column index of the pixel to make current.
-     * @param  py  the row index of the pixel to make current.
-     * @throws IndexOutOfBoundsException if the given indices are outside the iteration domain.
-     */
-    @Override
-    public void moveTo(final int px, final int py) {
-        if (px < lowerX || px >= upperX  ||  py < lowerY || py >= upperY) {
-            throw new IndexOutOfBoundsException(Resources.format(Resources.Keys.OutOfIteratorDomain_2, px, py));
-        }
-        if (image != null) {
-            final int tx = Math.floorDiv(px - tileGridXOffset, tileWidth);
-            final int ty = Math.floorDiv(py - tileGridYOffset, tileHeight);
-            if (tx != tileX || ty != tileY) {
-                close();                                            // Release current writable raster, if any.
-                tileX = tx;
-                tileY = ty;
-                if (fetchTile() > py || currentLowerX > px) {       // `fetchTile()` must be before `currentLowerX`.
-                    throw new RasterFormatException(Resources.format(Resources.Keys.IncompatibleTile_2, tileX, tileY));
-                }
-            }
-        }
-        x = px;
-        y = py;
-    }
-
-    /**
-     * Moves the iterator to the next pixel. This default implementation moves to the next tile only after
-     * all pixels in current tiles have been traversed, but subclasses may apply a different strategy.
-     *
-     * @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.
-     */
-    @Override
-    public boolean next() {
-        if (++x >= currentUpperX) {
-            if (++y >= currentUpperY) {                     // Strict equality (==) would work, but use >= as a safety.
-                close();                                    // Release current writable raster, if any.
-                if (++tileX >= tileUpperX) {                // Strict equality (==) would work, but use >= as a safety.
-                    if (++tileY >= tileUpperY) {
-                        endOfIteration();
-                        return false;
-                    }
-                    tileX = tileLowerX;
-                }
-                y = fetchTile();
-            }
-            x = currentLowerX;
-        }
-        return true;
-    }
-
-    /**
-     * Fetches from the image a tile for the current {@link #tileX} and {@link #tileY} coordinates.
-     * All fields prefixed by {@code current} are updated by this method. The caller is responsible
-     * for updating the {@link #x} and {@link #y} fields.
-     *
-     * <p>Note that there is no {@code currentLowerY} field in this {@code DefaultIterator} class.
-     * Instead, the value that would be have been set to that field is returned by this method.</p>
-     *
-     * @return the {@link #y} value of the first row of new tile.
-     */
-    final int fetchTile() {
-        currentRaster = null;
-        if (destination != null) {
-            destRaster = destination.getWritableTile(tileX, tileY);
-            if (destination == image) {
-                currentRaster = destRaster;
-            }
-        }
-        if (currentRaster == null) {
-            currentRaster = image.getTile(tileX, tileY);
-        }
-        final int minX = currentRaster.getMinX();
-        final int minY = currentRaster.getMinY();
-        currentLowerX  = Math.max(lowerX, minX);
-        currentUpperX  = Math.min(upperX, minX + tileWidth);
-        currentUpperY  = Math.min(upperY, minY + tileHeight);
-        if (currentRaster.getNumBands() != numBands) {
-            throw new RasterFormatException(Resources.format(Resources.Keys.IncompatibleTile_2, tileX, tileY));
-        }
-        return Math.max(lowerY, minY);
-    }
-
-    /**
-     * Invoked when a call to {@link #next()} moved to the end of iteration. This method sets fields to values
-     * that will allow {@link #moveTo(int,int)} and {@link #next()} to detect that we already finished iteration.
-     */
-    final void endOfIteration() {
-        /*
-         * The `tileY` value is used for checking if next() is invoked again, in order to avoid a
-         * common misuse pattern. In principle `tileY` needs to be compared only to `tileUpperY`,
-         * but we also compare to `tileLowerY + 1` for handling the empty iterator case.
-         */
-        final boolean error = tileY > Math.max(tileUpperY, tileLowerY + 1);
-        /*
-         * Paranoiac safety: keep the x, y and tileX variables before their limits
-         * in order to avoid overflow in the `if (++foo >= limit)` statements.
-         */
-        x =  currentUpperX - 1;
-        y =  currentUpperY - 1;
-        tileX = tileUpperX - 1;
-        tileY = tileUpperY;             // Sentinel value for detecting following error condition.
-        if (error) {
-            throw new IllegalStateException(Resources.format(Resources.Keys.IterationIsFinished));
-        }
-    }
-
-    /**
-     * Returns the sample value in the specified band of current pixel, rounded toward zero.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public int getSample(final int band) {
-        return currentRaster.getSample(x, y, band);
-    }
-
-    /**
-     * Returns the sample value in the specified band of current pixel as a single-precision floating point number.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public float getSampleFloat(final int band) {
-        return currentRaster.getSampleFloat(x, y, band);
-    }
-
-    /**
-     * Returns the sample value in the specified band of current pixel, without precision lost.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public double getSampleDouble(final int band) {
-        return currentRaster.getSampleDouble(x, y, band);
-    }
-
-    /**
-     * Writes a sample value in the specified band of current pixel.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public void setSample(final int band, final int value) {
-        destRaster.setSample(x, y, band, value);
-    }
-
-    /**
-     * Writes a sample value in the specified band of current pixel.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public void setSample(final int band, final float value) {
-        destRaster.setSample(x, y, band, value);
-    }
-
-    /**
-     * Writes a sample value in the specified band of current pixel.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public void setSample(final int band, final double value) {
-        destRaster.setSample(x, y, band, value);
-    }
-
-    /**
-     * Returns the sample values of current pixel for all bands.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public int[] getPixel​(int[] dest) {
-        return currentRaster.getPixel(x, y, dest);
-    }
-
-    /**
-     * Returns the sample values of current pixel for all bands.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public float[] getPixel​(float[] dest) {
-        return currentRaster.getPixel(x, y, dest);
-    }
-
-    /**
-     * Returns the sample values of current pixel for all bands.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public double[] getPixel​(double[] values) {
-        return currentRaster.getPixel(x, y, values);
-    }
-
-    /**
-     * Returns the data elements of current pixel.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public Object getDataElements​(Object values) {
-        return currentRaster.getDataElements​(x, y, values);
-    }
-
-    /**
-     * Sets the sample values of current pixel for all bands.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public void setPixel​(int[] values) {
-        destRaster.setPixel(x, y, values);
-    }
-
-    /**
-     * Sets the sample values of current pixel for all bands.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public void setPixel​(float[] values) {
-        destRaster.setPixel(x, y, values);
-    }
-
-    /**
-     * Sets the sample values of current pixel for all bands.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public void setPixel​(double[] values) {
-        destRaster.setPixel(x, y, values);
-    }
-
-    /**
-     * Sets the data elements of current pixel.
-     * This method assumes that {@link #next()} or {@link #moveTo(int,int)} has been invoked.
-     */
-    @Override
-    public void setDataElements(Object values) {
-        destRaster.setDataElements(x, y, values);
-    }
-
-    /**
-     * Returns a moving window over the sample values in a rectangular region starting at iterator position.
-     */
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T extends Buffer> Window<T> createWindow(final TransferType<T> type) {
-        ArgumentChecks.ensureNonNull("type", type);
-        final int length = numBands * windowWidth * windowHeight;
-        final int transferLength = length - numBands * Math.min(windowWidth, windowHeight);
-        // 'transfer' will always have at least one row or one column less than 'data'.
-        switch (type.dataBufferType) {
-            case DataBuffer.TYPE_INT:    return (Window<T>) new IntWindow   (new int   [length], new int   [transferLength]);
-            case DataBuffer.TYPE_FLOAT:  return (Window<T>) new FloatWindow (new float [length], new float [transferLength]);
-            case DataBuffer.TYPE_DOUBLE: return (Window<T>) new DoubleWindow(new double[length], new double[transferLength]);
-            default: throw new AssertionError(type);  // Should never happen unless we updated TransferType and forgot to update this method.
-        }
-    }
-
-    /**
-     * The base class of all {@link Window} implementations provided by {@link DefaultIterator}.
-     * This iterator defines a callback method required by {@link DefaultIterator#update(WindowBase, Object)}.
-     *
-     * @todo keep trace of last location and use {@code System#arraycopy(…)} for moving the values that we already have.
-     */
-    private abstract static class WindowBase<T extends Buffer> extends Window<T> {
-        /**
-         * Creates a new window which will store the sample values in the given buffer.
-         */
-        WindowBase(final T buffer) {
-            super(buffer);
-        }
-
-        /**
-         * Returns the iterator that created this window.
-         */
-        abstract DefaultIterator owner();
-
-        /**
-         * Returns the width and height of this window in pixels.
-         */
-        @Override
-        public final Dimension getSize() {
-            final DefaultIterator it = owner();
-            return new Dimension(it.windowWidth, it.windowHeight);
-        }
-
-        /**
-         * Returns an array containing all samples for a rectangle of pixels in the given raster, one sample
-         * per array element. Subclasses shall delegate to one of the {@code Raster#getPixels(…)} methods
-         * depending on the buffer data type.
-         *
-         * @param  raster     the raster from which to get the pixel values.
-         * @param  subX       the X coordinate of the upper-left pixel location.
-         * @param  subY       the Y coordinate of the upper-left pixel location.
-         * @param  subWidth   width of the pixel rectangle.
-         * @param  subHeight  height of the pixel rectangle.
-         * @param  direct     {@code true} for storing directly in the final array,
-         *                     or {@code false} for using the transfer array.
-         * @return the array in which sample values have been stored.
-         */
-        abstract Object getPixels(Raster raster, int subX, int subY, int subWidth, int subHeight, boolean direct);
-    }
-
-    /**
-     * {@link Window} implementation backed by an array of {@code int[]}.
-     */
-    private final class IntWindow extends WindowBase<IntBuffer> {
-        /**
-         * Sample values in the window ({@code data}) and a temporary array ({@code transfer}).
-         * Those arrays are overwritten when {@link #update()} is invoked.
-         */
-        private final int[] data, transfer;
-
-        /**
-         * Creates a new window which will store the sample values in the given {@code data} array.
-         */
-        IntWindow(final int[] data, final int[] transfer) {
-            super(IntBuffer.wrap(data).asReadOnlyBuffer());
-            this.data = data;
-            this.transfer = transfer;
-        }
-
-        /**
-         * Returns the iterator that created this window.
-         */
-        @Override
-        final DefaultIterator owner() {
-            return DefaultIterator.this;
-        }
-
-        /**
-         * Performs the transfer between the underlying raster and this window.
-         */
-        @Override
-        Object getPixels(Raster raster, int subX, int subY, int subWidth, int subHeight, boolean direct) {
-            return raster.getPixels(subX, subY, subWidth, subHeight, direct ? data : transfer);
-        }
-
-        /**
-         * 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();
-            DefaultIterator.this.update(this, data);
-        }
-    }
-
-    /**
-     * {@link Window} implementation backed by an array of {@code float[]}.
-     */
-    private final class FloatWindow extends WindowBase<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 DefaultIterator owner() {
-            return DefaultIterator.this;
-        }
-
-        /**
-         * Performs the transfer between the underlying raster and this window.
-         */
-        @Override
-        Object getPixels(Raster raster, int subX, int subY, int subWidth, int subHeight, boolean direct) {
-            return raster.getPixels(subX, subY, subWidth, subHeight, direct ? data : transfer);
-        }
-
-        /**
-         * 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();
-            DefaultIterator.this.update(this, data);
-        }
-    }
-
-    /**
-     * {@link Window} implementation backed by an array of {@code double[]}.
-     */
-    private final class DoubleWindow extends WindowBase<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 DefaultIterator owner() {
-            return DefaultIterator.this;
-        }
-
-        /**
-         * Performs the transfer between the underlying raster and this window.
-         */
-        @Override
-        Object getPixels(Raster raster, int subX, int subY, int subWidth, int subHeight, boolean direct) {
-            return raster.getPixels(subX, subY, subWidth, subHeight, direct ? data : transfer);
-        }
-
-        /**
-         * 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();
-            DefaultIterator.this.update(this, data);
-        }
-    }
-
-    /**
-     * Updates the content of given window with the sample values in the region starting at current iterator position.
-     *
-     * @param window  the window to update.
-     * @param data    the array of primitive type where sample values are stored.
-     */
-    @SuppressWarnings("SuspiciousSystemArraycopy")
-    final void update(final WindowBase<?> window, final Object data) {
-        Raster  raster    = currentRaster;
-        int     subEndX   = (raster.getMinX() - x) + raster.getWidth();
-        int     subEndY   = (raster.getMinY() - y) + raster.getHeight();
-        int     subWidth  = Math.min(windowWidth,  subEndX);
-        int     subHeight = Math.min(windowHeight, subEndY);
-        boolean fullWidth = (subWidth == windowWidth);
-        if (fullWidth && subHeight == windowHeight) {
-            /*
-             * Optimization for the case where the full window is inside current raster.
-             * This is the vast majority of cases, so we perform this check soon before
-             * to compute more internal variables.
-             */
-            final Object transfer = window.getPixels(raster, x, y, subWidth, subHeight, true);
-            if (transfer != data) {     // Paranoiac check (arrays should always be same).
-                System.arraycopy(transfer, 0, data, 0, numBands * subWidth * subHeight);
-            }
-            return;
-        }
-        /*
-         * At this point, we determined that the window is overlapping two or more tiles.
-         * We will need more variables for iterating over the tiles around 'currentRaster'.
-         */
-        int destOffset   = 0;                       // Index in 'window' array where to copy the sample values.
-        int subX         = 0;                       // Upper-left corner of a sub-window inside the window.
-        int subY         = 0;
-        int tileSubX     = tileX;                   // The tile where is located the (subX, subY) coordinate.
-        int tileSubY     = tileY;
-        final int stride = windowWidth * numBands;  // Number of samples between two rows in the 'windows' array.
-        final int rewind = subEndX;
-        for (;;) {
-            if (subWidth > 0 && subHeight > 0) {
-                final Object transfer = window.getPixels(raster, x + subX, y + subY, subWidth, subHeight, false);
-                if (fullWidth) {
-                    final int fullLength = stride * subHeight;
-                    System.arraycopy(transfer, 0, data, destOffset, fullLength);
-                    destOffset += fullLength;
-                } else {
-                    final int  rowLength = numBands  * subWidth;
-                    final int fullLength = rowLength * subHeight;
-                    for (int srcOffset=0; srcOffset < fullLength; srcOffset += rowLength) {
-                        System.arraycopy(transfer, srcOffset, data, destOffset, rowLength);
-                        destOffset += stride;
-                    }
-                }
-            }
-            /*
-             * At this point, we copied all sample values that we could obtain from the current tile.
-             * Move to the next tile on current row, or if we reached the end of row move to the next row.
-             */
-            if (subEndX < windowWidth) {
-                subX     = subEndX;
-                subEndX += tileWidth;                       // Next tile on the same row.
-                tileSubX++;
-            } else {
-                if (subEndY >= windowHeight) {
-                    return;                                 // Completed last row of tiles.
-                }
-                subY     = subEndY;
-                subEndY += tileHeight;                      // Tile on the next row.
-                tileSubY++;
-                tileSubX = tileX;
-                subEndX  = rewind;
-                subX     = 0;                               // Move x position back to the window left border.
-            }
-            raster     = image.getTile(tileSubX, tileSubY);
-            destOffset = (subY * windowWidth + subX) * numBands;
-            subWidth   = Math.min(windowWidth,  subEndX) - subX;
-            subHeight  = Math.min(windowHeight, subEndY) - subY;
-            fullWidth  = (subWidth == windowWidth);
-        }
-    }
-
-    /**
-     * Releases the tiles acquired by this iterator, if any.
-     * This method does nothing if the iterator is read-only.
-     */
-    @Override
-    public final void close() {
-        if (destination != null && destRaster != null) {
-            destRaster = null;
-            destination.releaseWritableTile(tileX, tileY);
-        }
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java
index 9a7cdb7..e5486ae 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java
@@ -43,11 +43,11 @@ import org.apache.sis.internal.feature.Resources;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
-final class LinearIterator extends DefaultIterator {
+final class LinearIterator extends WritablePixelIterator {
     /**
      * Creates an iterator for the given region in the given raster.
      *
@@ -92,12 +92,12 @@ final class LinearIterator extends DefaultIterator {
     public boolean next() {
         if (++x >= currentUpperX) {                 // Move to next column, potentially on a different tile.
             if (x < upperX) {
-                close();                            // Must be invoked before `tileX` change.
+                releaseTile();                      // Must be invoked before `tileX` change.
                 tileX++;
             } else {
                 x = lowerX;                         // Beginning of next row.
                 if (++y >= currentUpperY) {         // Move to next line.
-                    close();                        // Must be invoked before `tileY` change.
+                    releaseTile();                  // Must be invoked before `tileY` change.
                     if (++tileY >= tileUpperY) {
                         endOfIteration();
                         return false;
@@ -105,7 +105,7 @@ final class LinearIterator extends DefaultIterator {
                 } else if (tileX == tileLowerX) {
                     return true;                    // Beginning of next row is in the same tile.
                 }
-                close();                            // Must be invoked before `tileX` change.
+                releaseTile();                      // Must be invoked before `tileX` change.
                 tileX = tileLowerX;
             }
             /*
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
index aa39093..1fff354 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
@@ -19,6 +19,9 @@ package org.apache.sis.image;
 import java.util.Arrays;
 import java.util.Optional;
 import java.nio.Buffer;
+import java.nio.IntBuffer;
+import java.nio.FloatBuffer;
+import java.nio.DoubleBuffer;
 import java.awt.Point;
 import java.awt.Dimension;
 import java.awt.Rectangle;
@@ -30,6 +33,7 @@ import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
 import java.awt.image.SampleModel;
+import java.awt.image.RasterFormatException;
 import java.util.NoSuchElementException;
 import org.opengis.coverage.grid.SequenceType;
 import org.apache.sis.util.resources.Errors;
@@ -62,6 +66,17 @@ import static org.apache.sis.internal.util.Numerics.ceilDiv;
  * }
  * </div>
  *
+ * <h2>Default implementation</h2>
+ * This base class uses the {@link Raster} API for traversing the pixels in each tile.
+ * Calls to {@link #next()} move the current position by increasing the following values, in order:
+ *
+ * <ol>
+ *   <li>Column index in a single tile (from left to right)</li>
+ *   <li>Row index in a single tile (from top to bottom).</li>
+ *   <li>Then, {@code tileX} index from left to right.</li>
+ *   <li>Then, {@code tileY} index from top to bottom.</li>
+ * </ol>
+ *
  * @author  Rémi Maréchal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
@@ -69,7 +84,7 @@ import static org.apache.sis.internal.util.Numerics.ceilDiv;
  * @since   1.0
  * @module
  */
-public abstract class PixelIterator {
+public class PixelIterator {
     /**
      * The image in which iteration is occurring, or {@code null} if none.
      * If {@code null}, then {@link #currentRaster} must be non-null.
@@ -82,13 +97,13 @@ public abstract class PixelIterator {
      *
      * @see RenderedImage#getTile(int, int)
      */
-    Raster currentRaster;
+    private 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;
+    private final int numBands;
 
     /**
      * The domain, in pixel coordinates, of the region traversed by this pixel iterator.
@@ -102,13 +117,13 @@ public abstract class PixelIterator {
     /**
      * Size of all tiles in the {@link #image}.
      */
-    final int tileWidth, tileHeight;
+    private 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;
+    private final int tileGridXOffset, tileGridYOffset;
 
     /**
      * The domain, in tile coordinates, of the region traversed by this pixel iterator.
@@ -120,7 +135,25 @@ public abstract class PixelIterator {
     /**
      * Size of the window to use in {@link #createWindow(TransferType)} method, or {@code 0} if none.
      */
-    final int windowWidth, windowHeight;
+    private final int windowWidth, windowHeight;
+
+    /**
+     * Tile coordinate of {@link #currentRaster}.
+     * The {@code tileY >= tileUpperY} condition is used for detecting when we reached iteration end.
+     */
+    int tileX, tileY;
+
+    /**
+     * Current (column, row) index in current raster.
+     * The {@code x >= lowerX} condition is used for detecting if iteration started.
+     */
+    int x, y;
+
+    /**
+     * Bounds of the region traversed by the iterator in current raster.
+     * When iteration reaches the upper coordinates, the iterator needs to move to next tile.
+     */
+    int currentLowerX, currentUpperX, currentUpperY;
 
     /**
      * Creates an iterator for the given region in the given raster.
@@ -150,6 +183,11 @@ public abstract class PixelIterator {
         upperY          = Math.addExact(lowerY, bounds.height);
         windowWidth     = (window != null) ? window.width  : 0;
         windowHeight    = (window != null) ? window.height : 0;
+        currentLowerX   = lowerX;
+        currentUpperX   = upperX;
+        currentUpperY   = upperY;
+        x               = Math.decrementExact(lowerX);          // Set to the position before first pixel.
+        y               = lowerY;
     }
 
     /**
@@ -179,6 +217,21 @@ public abstract class PixelIterator {
         tileUpperY      =  ceilDiv(Math.subtractExact(upperY, tileGridYOffset), tileHeight);
         windowWidth     = (window != null) ? window.width  : 0;
         windowHeight    = (window != null) ? window.height : 0;
+        tileX           = Math.decrementExact(tileLowerX);
+        tileY           = tileLowerY;
+        currentLowerX   = lowerX;
+        currentUpperX   = lowerX;               // Really `lower`, so the position is the tile before the first tile.
+        currentUpperY   = lowerY;
+        x               = Math.decrementExact(lowerX);          // Set to the position before first pixel.
+        y               = lowerY;
+        /*
+         * We need to ensure that `tileUpperY+1 > tileUpperY` will alway be true because `tileY` may be equal
+         * to `tileUpperY` when the `if (++tileY >= tileUpperY)` statement is excuted in the `next()` method.
+         * This is because `tileY` is used as a sentinel value for detecting when we reached iteration end.
+         */
+        if (tileUpperY == Integer.MAX_VALUE) {
+            throw new ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Integer.SIZE));
+        }
     }
 
     /**
@@ -321,7 +374,7 @@ public abstract class PixelIterator {
                 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);
+            return new PixelIterator(data, subArea, window);
         }
 
         /**
@@ -351,7 +404,7 @@ public abstract class PixelIterator {
                 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);
+            return new PixelIterator(data, subArea, window);
         }
 
         /**
@@ -395,7 +448,7 @@ public abstract class PixelIterator {
                 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);
+            return new WritablePixelIterator(input, output, subArea, window);
         }
 
         /**
@@ -415,7 +468,7 @@ public abstract class PixelIterator {
                 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);
+            return new WritablePixelIterator(input, output, subArea, window);
         }
     }
 
@@ -528,7 +581,13 @@ public abstract class PixelIterator {
      *
      * @return order in which pixels are traversed.
      */
-    public abstract Optional<SequenceType> getIterationOrder();
+    public Optional<SequenceType> getIterationOrder() {
+        if (image == null || (tileUpperX - tileLowerX) <=1 && (tileUpperY - tileLowerY) <= 1) {
+            return Optional.of(SequenceType.LINEAR);
+        } else {
+            return Optional.empty();                // Undefined order.
+        }
+    }
 
     /**
      * Returns the number of bands (samples per pixel) in the image or raster.
@@ -558,18 +617,28 @@ public abstract class PixelIterator {
      * @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();
+    public Point getPosition() {
+        final short message;
+        if (x < lowerX) {
+            message = Resources.Keys.IterationNotStarted;
+        } else if (tileY >= tileUpperY) {
+            message = Resources.Keys.IterationIsFinished;
+        } else {
+            return new Point(x,y);
+        }
+        throw new IllegalStateException(Resources.format(message));
+    }
 
     /**
      * Returns {@code true} if current iterator position is inside the given shape.
-     * Current version does not require implementations to check if iteration started or finished
+     * Current version does not verify if iteration started or finished
      * (this method is non-public for that reason).
      *
      * @param  domain  the shape for which to test inclusion.
      * @return whether current iterator position is inside the given shape.
      */
-    boolean isInside(final Shape domain) {
-        return domain.contains(getPosition());
+    final boolean isInside(final Shape domain) {
+        return domain.contains(x, y);
     }
 
     /**
@@ -587,11 +656,29 @@ public abstract class PixelIterator {
      * }
      * </div>
      *
-     * @param  x  the column index of the pixel to make current.
-     * @param  y  the row index of the pixel to make current.
+     * @param  px  the column index of the pixel to make current.
+     * @param  py  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);
+    public void moveTo(final int px, final int py) {
+        if (px < lowerX || px >= upperX  ||  py < lowerY || py >= upperY) {
+            throw new IndexOutOfBoundsException(Resources.format(Resources.Keys.OutOfIteratorDomain_2, px, py));
+        }
+        if (image != null) {
+            final int tx = Math.floorDiv(px - tileGridXOffset, tileWidth);
+            final int ty = Math.floorDiv(py - tileGridYOffset, tileHeight);
+            if (tx != tileX || ty != tileY) {
+                releaseTile();                                      // Release current writable raster, if any.
+                tileX = tx;
+                tileY = ty;
+                if (fetchTile() > py || currentLowerX > px) {       // `fetchTile()` must be before `currentLowerX`.
+                    throw new RasterFormatException(Resources.format(Resources.Keys.IncompatibleTile_2, tileX, tileY));
+                }
+            }
+        }
+        x = px;
+        y = py;
+    }
 
     /**
      * Moves the iterator to the next pixel. A pixel iterator is initially positioned before the first pixel.
@@ -607,7 +694,93 @@ public abstract class PixelIterator {
      * @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();
+    public boolean next() {
+        if (++x >= currentUpperX) {
+            if (++y >= currentUpperY) {             // Strict equality (==) would work, but use >= as a safety.
+                releaseTile();                      // Release current writable raster, if any.
+                if (++tileX >= tileUpperX) {        // Strict equality (==) would work, but use >= as a safety.
+                    if (++tileY >= tileUpperY) {
+                        endOfIteration();
+                        return false;
+                    }
+                    tileX = tileLowerX;
+                }
+                y = fetchTile();
+            }
+            x = currentLowerX;
+        }
+        return true;
+    }
+
+    /**
+     * Fetches from the image a tile for the current {@link #tileX} and {@link #tileY} coordinates.
+     * All fields prefixed by {@code current} are updated by this method. The caller is responsible
+     * for updating the {@link #x} and {@link #y} fields.
+     *
+     * <p>Note that there is no {@code currentLowerY} field in this {@code PixelIterator} class.
+     * Instead, that value is returned by this method.</p>
+     *
+     * @return the {@link #y} value of the first row of new tile.
+     */
+    final int fetchTile() {
+        currentRaster = fetchWritableTile();
+        if (currentRaster == null) {
+            currentRaster = image.getTile(tileX, tileY);
+        }
+        final int minX = currentRaster.getMinX();
+        final int minY = currentRaster.getMinY();
+        currentLowerX  = Math.max(lowerX, minX);
+        currentUpperX  = Math.min(upperX, minX + tileWidth);
+        currentUpperY  = Math.min(upperY, minY + tileHeight);
+        if (currentRaster.getNumBands() != numBands) {
+            throw new RasterFormatException(Resources.format(Resources.Keys.IncompatibleTile_2, tileX, tileY));
+        }
+        return Math.max(lowerY, minY);
+    }
+
+    /**
+     * Fetches from the writable image a tile for the current {@link #tileX} and {@link #tileY} coordinates.
+     * If the writable tile is the same tile than the one used for read operation, then that tile should be
+     * returned. This method is for {@link #fetchTile()} internal usage only and should be implemented by
+     * {@link WritablePixelIterator} only.
+     *
+     * @return a tile that can be used for <em>read</em> operation, or {@code null} if none.
+     *         This value shall be non-null only if the tile to write is the same than the tile to read.
+     */
+    Raster fetchWritableTile() {
+        return null;
+    }
+
+    /**
+     * Releases the tiles acquired by this iterator, if any.
+     * This method does nothing if the iterator is read-only.
+     */
+    void releaseTile() {
+    }
+
+    /**
+     * Invoked when a call to {@link #next()} moved to the end of iteration. This method sets fields to values
+     * that will allow {@link #moveTo(int,int)} and {@link #next()} to detect that we already finished iteration.
+     */
+    final void endOfIteration() {
+        /*
+         * The `tileY` value is used for checking if next() is invoked again, in order to avoid a
+         * common misuse pattern. In principle `tileY` needs to be compared only to `tileUpperY`,
+         * but we also compare to `tileLowerY + 1` for handling the empty iterator case.
+         */
+        final boolean error = tileY > Math.max(tileUpperY, tileLowerY + 1);
+        /*
+         * Paranoiac safety: keep the x, y and tileX variables before their limits
+         * in order to avoid overflow in the `if (++foo >= limit)` statements.
+         */
+        x =  currentUpperX - 1;
+        y =  currentUpperY - 1;
+        tileX = tileUpperX - 1;
+        tileY = tileUpperY;             // Sentinel value for detecting following error condition.
+        if (error) {
+            throw new IllegalStateException(Resources.format(Resources.Keys.IterationIsFinished));
+        }
+    }
 
     /**
      * Returns the sample value in the specified band of current pixel, rounded toward zero.
@@ -621,7 +794,9 @@ public abstract class PixelIterator {
      *
      * @see Raster#getSample(int, int, int)
      */
-    public abstract int getSample(int band);
+    public int getSample(final int band) {
+        return currentRaster.getSample(x, y, band);
+    }
 
     /**
      * Returns the sample value in the specified band of current pixel as a single-precision floating point number.
@@ -635,7 +810,9 @@ public abstract class PixelIterator {
      *
      * @see Raster#getSampleFloat(int, int, int)
      */
-    public abstract float getSampleFloat(int band);
+    public float getSampleFloat(final int band) {
+        return currentRaster.getSampleFloat(x, y, band);
+    }
 
     /**
      * Returns the sample value in the specified band of current pixel, without precision lost.
@@ -649,7 +826,9 @@ public abstract class PixelIterator {
      *
      * @see Raster#getSampleDouble(int, int, int)
      */
-    public abstract double getSampleDouble(int band);
+    public double getSampleDouble(final int band) {
+        return currentRaster.getSampleDouble(x, y, band);
+    }
 
     /**
      * Returns the sample values of current pixel for all bands.
@@ -663,7 +842,9 @@ public abstract class PixelIterator {
      *
      * @see Raster#getPixel(int, int, int[])
      */
-    public abstract int[] getPixel​(int[] dest);
+    public int[] getPixel​(final int[] dest) {
+        return currentRaster.getPixel(x, y, dest);
+    }
 
     /**
      * Returns the sample values of current pixel for all bands.
@@ -677,7 +858,9 @@ public abstract class PixelIterator {
      *
      * @see Raster#getPixel(int, int, float[])
      */
-    public abstract float[] getPixel​(float[] dest);
+    public float[] getPixel​(final float[] dest) {
+        return currentRaster.getPixel(x, y, dest);
+    }
 
     /**
      * Returns the sample values of current pixel for all bands.
@@ -691,7 +874,9 @@ public abstract class PixelIterator {
      *
      * @see Raster#getPixel(int, int, double[])
      */
-    public abstract double[] getPixel​(double[] dest);
+    public double[] getPixel​(final double[] dest) {
+        return currentRaster.getPixel(x, y, dest);
+    }
 
     /**
      * Returns the data elements (not necessarily band values) of current pixel.
@@ -715,7 +900,9 @@ public abstract class PixelIterator {
      *
      * @since 1.1
      */
-    public abstract Object getDataElements(Object dest);
+    public Object getDataElements​(final Object dest) {
+        return currentRaster.getDataElements​(x, y, dest);
+    }
 
     /**
      * Returns a moving window over the sample values in a rectangular region starting at iterator position.
@@ -770,7 +957,19 @@ public abstract class PixelIterator {
      *
      * @see Raster#getPixels(int, int, int, int, double[])
      */
-    public abstract <T extends Buffer> Window<T> createWindow(TransferType<T> type);
+    @SuppressWarnings("unchecked")
+    public <T extends Buffer> Window<T> createWindow(final TransferType<T> type) {
+        ArgumentChecks.ensureNonNull("type", type);
+        final int length = numBands * windowWidth * windowHeight;
+        final int transferLength = length - numBands * Math.min(windowWidth, windowHeight);
+        // `transfer` will always have at least one row or one column less than `data`.
+        switch (type.dataBufferType) {
+            case DataBuffer.TYPE_INT:    return (Window<T>) new IntWindow   (new int   [length], new int   [transferLength]);
+            case DataBuffer.TYPE_FLOAT:  return (Window<T>) new FloatWindow (new float [length], new float [transferLength]);
+            case DataBuffer.TYPE_DOUBLE: return (Window<T>) new DoubleWindow(new double[length], new double[transferLength]);
+            default: throw new AssertionError(type);  // Should never happen unless we updated TransferType and forgot to update this method.
+        }
+    }
 
     /**
      * Contains the sample values in a moving window over the image. Windows are created by calls to
@@ -809,13 +1008,21 @@ public abstract class PixelIterator {
         }
 
         /**
+         * Returns the iterator that created this window.
+         */
+        abstract PixelIterator owner();
+
+        /**
          * Returns the width and height of this window in pixels.
          *
          * @return the window size in pixels.
          *
          * @since 1.1
          */
-        public abstract Dimension getSize();
+        public final Dimension getSize() {
+            final PixelIterator it = owner();
+            return new Dimension(it.windowWidth, it.windowHeight);
+        }
 
         /**
          * Updates this window with the sample values in the region starting at current iterator position.
@@ -827,11 +1034,266 @@ public abstract class PixelIterator {
          * (there is no explicit bounds check for performance reasons).</p>
          */
         public abstract void update();
+
+        /**
+         * Returns an array containing all samples for a rectangle of pixels in the given raster, one sample
+         * per array element. Subclasses should delegate to one of the {@code Raster#getPixels(…)} methods
+         * depending on the buffer data type.
+         *
+         * @param  raster     the raster from which to get the pixel values.
+         * @param  subX       the X coordinate of the upper-left pixel location.
+         * @param  subY       the Y coordinate of the upper-left pixel location.
+         * @param  subWidth   width of the pixel rectangle.
+         * @param  subHeight  height of the pixel rectangle.
+         * @param  direct     {@code true} for storing directly in the final array,
+         *                    or {@code false} for using the transfer array.
+         * @return the array in which sample values have been stored.
+         */
+        abstract Object getPixels(Raster raster, int subX, int subY, int subWidth, int subHeight, boolean direct);
+    }
+
+    /**
+     * {@link Window} implementation backed by an array of {@code int[]}. This is the most efficient
+     * {@code Window} because Java2D has many optimizations for images backed by integer values:
+     *
+     * <ul>
+     *   <li>{@link Raster#getPixels(int, int, int, int, int[])} overridden in private subclasses.</li>
+     *   <li>{@link SampleModel#getPixels(int, int, int, int, int[], DataBuffer)} overridden in public subclasses.</li>
+     * </ul>
+     *
+     * In particular we should not try to get the backing {@code int[]} array ourselves
+     * because it may cause Java2D to disable GPU accelerations on that raster.
+     */
+    private final class IntWindow extends Window<IntBuffer> {
+        /**
+         * Sample values in the window ({@code data}) and a temporary array ({@code transfer}).
+         * Those arrays are overwritten when {@link #update()} is invoked.
+         */
+        private final int[] data, transfer;
+
+        /**
+         * Creates a new window which will store the sample values in the given {@code data} array.
+         */
+        IntWindow(final int[] data, final int[] transfer) {
+            super(IntBuffer.wrap(data).asReadOnlyBuffer());
+            this.data = data;
+            this.transfer = transfer;
+        }
+
+        /**
+         * Returns the iterator that created this window.
+         */
+        @Override
+        final PixelIterator owner() {
+            return PixelIterator.this;
+        }
+
+        /**
+         * Performs the transfer between the underlying raster and this window.
+         */
+        @Override
+        Object getPixels(Raster raster, int subX, int subY, int subWidth, int subHeight, boolean direct) {
+            return raster.getPixels(subX, subY, subWidth, subHeight, direct ? data : transfer);
+        }
+
+        /**
+         * 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();
+            PixelIterator.this.update(this, data);
+        }
+    }
+
+    /**
+     * {@link Window} implementation backed by an array of {@code float[]}.
+     */
+    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 PixelIterator.this;
+        }
+
+        /**
+         * Performs the transfer between the underlying raster and this window.
+         */
+        @Override
+        Object getPixels(Raster raster, int subX, int subY, int subWidth, int subHeight, boolean direct) {
+            return raster.getPixels(subX, subY, subWidth, subHeight, direct ? data : transfer);
+        }
+
+        /**
+         * 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();
+            PixelIterator.this.update(this, data);
+        }
+    }
+
+    /**
+     * {@link Window} implementation backed by an array of {@code double[]}.
+     */
+    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 PixelIterator.this;
+        }
+
+        /**
+         * Performs the transfer between the underlying raster and this window.
+         */
+        @Override
+        Object getPixels(Raster raster, int subX, int subY, int subWidth, int subHeight, boolean direct) {
+            return raster.getPixels(subX, subY, subWidth, subHeight, direct ? data : transfer);
+        }
+
+        /**
+         * 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();
+            PixelIterator.this.update(this, data);
+        }
+    }
+
+    /**
+     * Updates the content of given window with the sample values in the region starting at current iterator position.
+     *
+     * @param  window  the window to update.
+     * @param  data    the array of primitive type where sample values are stored.
+     */
+    @SuppressWarnings("SuspiciousSystemArraycopy")
+    final void update(final Window<?> window, final Object data) {
+        Raster  raster    = currentRaster;
+        int     subEndX   = (raster.getMinX() - x) + raster.getWidth();
+        int     subEndY   = (raster.getMinY() - y) + raster.getHeight();
+        int     subWidth  = Math.min(windowWidth,  subEndX);
+        int     subHeight = Math.min(windowHeight, subEndY);
+        boolean fullWidth = (subWidth == windowWidth);
+        if (fullWidth && subHeight == windowHeight) {
+            /*
+             * Optimization for the case where the full window is inside current raster.
+             * This is the vast majority of cases, so we perform this check soon before
+             * to compute more internal variables.
+             */
+            final Object transfer = window.getPixels(raster, x, y, subWidth, subHeight, true);
+            assert transfer == data;
+            return;
+        }
+        /*
+         * At this point, we determined that the window is overlapping two or more tiles.
+         * We will need more variables for iterating over the tiles around `currentRaster`.
+         */
+        int destOffset   = 0;                       // Index in `window` array where to copy the sample values.
+        int subX         = 0;                       // Upper-left corner of a sub-window inside the window.
+        int subY         = 0;
+        int tileSubX     = tileX;                   // The tile where is located the (subX, subY) coordinate.
+        int tileSubY     = tileY;
+        final int stride = windowWidth * numBands;  // Number of samples between two rows in the `windows` array.
+        final int rewind = subEndX;
+        for (;;) {
+            if (subWidth > 0 && subHeight > 0) {
+                final Object transfer = window.getPixels(raster, x + subX, y + subY, subWidth, subHeight, false);
+                if (fullWidth) {
+                    System.arraycopy(transfer, 0, data, destOffset, stride * subHeight);
+                } else {
+                    final int  rowLength = numBands  * subWidth;
+                    final int fullLength = rowLength * subHeight;
+                    for (int srcOffset=0; srcOffset < fullLength; srcOffset += rowLength) {
+                        System.arraycopy(transfer, srcOffset, data, destOffset, rowLength);
+                        destOffset += stride;
+                    }
+                }
+            }
+            /*
+             * At this point, we copied all sample values that we could obtain from the current tile.
+             * Move to the next tile on current row, or if we reached the end of row move to the next row.
+             */
+            if (subEndX < windowWidth) {
+                subX     = subEndX;
+                subEndX += tileWidth;                       // Next tile on the same row.
+                tileSubX++;
+            } else {
+                if (subEndY >= windowHeight) {
+                    return;                                 // Completed last row of tiles.
+                }
+                subY     = subEndY;
+                subEndY += tileHeight;                      // Tile on the next row.
+                tileSubY++;
+                tileSubX = tileX;
+                subEndX  = rewind;
+                subX     = 0;                               // Move x position back to the window left border.
+            }
+            raster     = image.getTile(tileSubX, tileSubY);
+            destOffset = (subY * windowWidth + subX) * numBands;
+            subWidth   = Math.min(windowWidth,  subEndX) - subX;
+            subHeight  = Math.min(windowHeight, subEndY) - subY;
+            fullWidth  = (subWidth == windowWidth);
+        }
     }
 
     /**
      * 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();
+    public void rewind() {
+        releaseTile();                  // Release current writable raster, if any.
+        if (image == null) {
+            tileX = 0;
+            tileY = 0;
+            currentUpperX = upperX;
+            currentUpperY = upperY;
+        } else {
+            tileX = tileLowerX - 1;     // Note: no need for decrementExact(…) because already checked by constructor.
+            tileY = tileLowerY;
+            currentUpperX = lowerX;     // Really `lower`, so the position is the tile before the first tile.
+            currentUpperY = lowerY;
+        }
+        currentLowerX = lowerX;
+        x = lowerX - 1;                 // Set to the position before first pixel.
+        y = lowerY;
+    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/WritablePixelIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/WritablePixelIterator.java
index 420a8d1..1dc30e9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/WritablePixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/WritablePixelIterator.java
@@ -55,13 +55,13 @@ import org.apache.sis.internal.feature.Resources;
  * @since   1.0
  * @module
  */
-public abstract class WritablePixelIterator extends PixelIterator implements Closeable {
+public class WritablePixelIterator extends PixelIterator implements Closeable {
     /**
      * The image where pixels will be written, or {@code null} if the image is read-only.
      * The destination image may or may not be the same instance than the source {@link #image}.
      * However the sample model, the minimal X and Y values and the tile grid must be the same.
      */
-    final WritableRenderedImage destination;
+    private final WritableRenderedImage destination;
 
     /**
      * The current tile where pixels will be written, or {@code null} if no write operation is under way.
@@ -70,7 +70,7 @@ public abstract class WritablePixelIterator extends PixelIterator implements Clo
      * @see WritableRenderedImage#getWritableTile(int, int)
      * @see WritableRenderedImage#releaseWritableTile(int, int)
      */
-    WritableRaster destRaster;
+    private WritableRaster destRaster;
 
     /**
      * Creates an iterator for the given region in the given raster.
@@ -167,7 +167,9 @@ public abstract class WritablePixelIterator extends PixelIterator implements Clo
      * @see WritableRaster#setSample(int, int, int, int)
      * @see #getSample(int)
      */
-    public abstract void setSample(int band, int value);
+    public void setSample(final int band, final int value) {
+        destRaster.setSample(x, y, band, value);
+    }
 
     /**
      * Writes a sample value in the specified band of current pixel.
@@ -181,7 +183,9 @@ public abstract class WritablePixelIterator extends PixelIterator implements Clo
      * @see WritableRaster#setSample(int, int, int, float)
      * @see #getSampleFloat(int)
      */
-    public abstract void setSample(int band, float value);
+    public void setSample(final int band, final float value) {
+        destRaster.setSample(x, y, band, value);
+    }
 
     /**
      * Writes a sample value in the specified band of current pixel.
@@ -195,7 +199,9 @@ public abstract class WritablePixelIterator extends PixelIterator implements Clo
      * @see WritableRaster#setSample(int, int, int, double)
      * @see #getSampleDouble(int)
      */
-    public abstract void setSample(int band, double value);
+    public void setSample(final int band, final double value) {
+        destRaster.setSample(x, y, band, value);
+    }
 
     /**
      * Sets the sample values of current pixel for all bands.
@@ -208,7 +214,9 @@ public abstract class WritablePixelIterator extends PixelIterator implements Clo
      * @see WritableRaster#setPixel(int, int, int[])
      * @see #getPixel(int[])
      */
-    public abstract void setPixel​(int[] values);
+    public void setPixel​(final int[] values) {
+        destRaster.setPixel(x, y, values);
+    }
 
     /**
      * Sets the sample values of current pixel for all bands.
@@ -221,7 +229,9 @@ public abstract class WritablePixelIterator extends PixelIterator implements Clo
      * @see WritableRaster#setPixel(int, int, float[])
      * @see #getPixel(float[])
      */
-    public abstract void setPixel​(float[] values);
+    public void setPixel​(final float[] values) {
+        destRaster.setPixel(x, y, values);
+    }
 
     /**
      * Sets the sample values of current pixel for all bands.
@@ -234,7 +244,9 @@ public abstract class WritablePixelIterator extends PixelIterator implements Clo
      * @see WritableRaster#setPixel(int, int, double[])
      * @see #getPixel(double[])
      */
-    public abstract void setPixel​(double[] values);
+    public void setPixel​(final double[] values) {
+        destRaster.setPixel(x, y, values);
+    }
 
     /**
      * Sets the data elements (not necessarily band values) of current pixel.
@@ -249,12 +261,42 @@ public abstract class WritablePixelIterator extends PixelIterator implements Clo
      *
      * @since 1.1
      */
-    public abstract void setDataElements(Object values);
+    public void setDataElements(final Object values) {
+        destRaster.setDataElements(x, y, values);
+    }
+
+    /**
+     * Invoked by {@link #fetchTile()} when iteration switch to a new tile.
+     *
+     * @return if the new writable tile can also be used for reading, that tile. Otherwise {@code null}.
+     */
+    @Override
+    final Raster fetchWritableTile() {
+        if (destination != null) {
+            destRaster = destination.getWritableTile(tileX, tileY);
+            if (destination == image) return destRaster;
+        }
+        return super.fetchWritableTile();
+    }
+
+    /**
+     * Releases the tiles acquired by this iterator, if any.
+     * This method does nothing if the iterator is read-only.
+     */
+    @Override
+    final void releaseTile() {
+        if (destination != null && destRaster != null) {
+            destRaster = null;
+            destination.releaseWritableTile(tileX, tileY);
+        }
+    }
 
     /**
      * Releases any resources hold by this iterator.
-     * Invoking this method may flush some tiles content to disk.
+     * If some pixel values have been written, the changes are committed.
      */
     @Override
-    public abstract void close();
+    public void close() {
+        releaseTile();
+    }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java
index de7a87d..35ff72c 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java
@@ -34,7 +34,7 @@ import static org.junit.Assert.*;
  * @version 1.0
  * @since   1.0
  */
-public final strictfp class LinearIteratorTest extends DefaultIteratorTest {
+public final strictfp class LinearIteratorTest extends PixelIteratorTest {
     /**
      * Creates a new test case.
      */
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/DefaultIteratorTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/PixelIteratorTest.java
similarity index 98%
rename from core/sis-feature/src/test/java/org/apache/sis/image/DefaultIteratorTest.java
rename to core/sis-feature/src/test/java/org/apache/sis/image/PixelIteratorTest.java
index 5cfcb0f..373e020 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/DefaultIteratorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/PixelIteratorTest.java
@@ -49,7 +49,7 @@ import static org.junit.Assert.*;
  * @since   1.0
  * @module
  */
-public strictfp class DefaultIteratorTest extends TestCase {
+public strictfp class PixelIteratorTest extends TestCase {
     /**
      * The pixel iterator being tested.
      * This field is initialized by a call to one of the {@code createPixelIterator(…)} methods.
@@ -119,14 +119,14 @@ public strictfp class DefaultIteratorTest extends TestCase {
      *
      * @param  dataType  the raster or image data type as one of the {@link DataBuffer} constants.
      */
-    DefaultIteratorTest(final int dataType) {
+    PixelIteratorTest(final int dataType) {
         this.dataType = dataType;
     }
 
     /**
      * Creates a new test case.
      */
-    public DefaultIteratorTest() {
+    public PixelIteratorTest() {
         this(DataBuffer.TYPE_SHORT);
     }
 
@@ -321,14 +321,14 @@ public strictfp class DefaultIteratorTest extends TestCase {
      * Creates a {@code PixelIterator} for a sub-area of given raster.
      * The iterator shall be assigned to the {@link #iterator} field.
      *
-     * <p>The default implementation creates {@link DefaultIterator} instances.
+     * <p>The default implementation creates {@link PixelIterator} instances.
      * Tests for other kinds of iterator need to override.</p>
      *
      * @param  raster   the data on which to perform iteration.
      * @param  subArea  the boundary of the raster sub-area where to perform iteration.
      */
     void createPixelIterator(WritableRaster raster, Rectangle subArea) {
-        iterator = new DefaultIterator(raster, isWritable ? raster : null, subArea, null);
+        iterator = new WritablePixelIterator(raster, isWritable ? raster : null, subArea, null);
         assertEquals("getIterationOrder()", SequenceType.LINEAR, iterator.getIterationOrder().get());
         assertEquals("isWritable", isWritable, iterator.isWritable());
     }
@@ -337,14 +337,14 @@ public strictfp class DefaultIteratorTest extends TestCase {
      * Creates a {@code PixelIterator} for a sub-area of given image.
      * The iterator shall be assigned to the {@link #iterator} field.
      *
-     * <p>The default implementation creates {@link DefaultIterator} instances.
+     * <p>The default implementation creates {@link PixelIterator} instances.
      * Tests for other kinds of iterator need to override.</p>
      *
      * @param  image    the data on which to perform iteration.
      * @param  subArea  the boundary of the image sub-area where to perform iteration.
      */
     void createPixelIterator(WritableRenderedImage image, Rectangle subArea) {
-        iterator = new DefaultIterator(image, isWritable ? image : null, subArea, null);
+        iterator = new WritablePixelIterator(image, isWritable ? image : null, subArea, null);
         assertEquals("isWritable", isWritable, iterator.isWritable());
     }
 
@@ -352,14 +352,14 @@ public strictfp class DefaultIteratorTest extends TestCase {
      * Creates a {@code PixelIterator} for a window in the given image.
      * The iterator shall be assigned to the {@link #iterator} field.
      *
-     * <p>The default implementation creates {@link DefaultIterator} instances.
+     * <p>The default implementation creates {@link PixelIterator} instances.
      * Tests for other kinds of iterator need to override.</p>
      *
      * @param  image    the data on which to perform iteration.
      * @param  window   size of the window to use in {@link PixelIterator#createWindow(TransferType)} method.
      */
     void createWindowIterator(WritableRenderedImage image, Dimension window) {
-        iterator = new DefaultIterator(image, isWritable ? image : null, null, window);
+        iterator = new WritablePixelIterator(image, isWritable ? image : null, null, window);
         assertEquals("isWritable", isWritable, iterator.isWritable());
     }
 
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 0dc881f..d2a500d 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
@@ -83,7 +83,7 @@ import org.junit.runners.Suite;
     org.apache.sis.image.DataTypeTest.class,
     org.apache.sis.image.PlanarImageTest.class,
     org.apache.sis.image.ComputedImageTest.class,
-    org.apache.sis.image.DefaultIteratorTest.class,
+    org.apache.sis.image.PixelIteratorTest.class,
     org.apache.sis.image.LinearIteratorTest.class,
     org.apache.sis.image.StatisticsCalculatorTest.class,
     org.apache.sis.image.BandSelectImageTest.class,


Mime
View raw message