/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.image.iterator; import java.awt.Rectangle; import java.awt.image.ComponentSampleModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import org.opengis.coverage.grid.SequenceType; /** * An Iterator for traversing anyone rendered Image. * <p> * Iteration transverse each tiles(raster) from rendered image or raster source one by one in order. * Iteration to follow tiles(raster) begin by raster bands, next, raster x coordinates, * and to finish raster y coordinates. * <p> * Iteration follow this scheme : * tiles band --> tiles x coordinates --> tiles y coordinates --> next rendered image tiles. * * Moreover iterator traversing a read-only each rendered image tiles(raster) in top-to-bottom, left-to-right order. * Furthermore iterator directly read in data table within raster {@code DataBuffer}. * * @author RĂ©mi Marechal (Geomatys). * @author Alexis Manin (Geomatys). * @author Martin Desruisseaux (Geomatys). */ abstract class DefaultDirectIterator extends PixelIterator { /** * Current X coordinate of the pixel iterator (in image grid) */ protected int currentX; /** * Minimum coordinate allowed for iteration in current raster. */ protected int minX, minY; /** * Current raster width. */ protected final int rasterWidth; /** * Step value to move on the next line. * @see DefaultDirectIterator#next() */ private int cursorStep; /** * First iteration position. * @see DefaultDirectIterator#rewind() */ private int baseCursor; /** * Cursor position in current raster {@link java.awt.image.DataBuffer}. */ protected int dataCursor; /** * Maximum iteration value from current raster. */ protected int maxBanks; /** * Tile scanLineStride from Raster or RenderedImage Sample model. */ protected int scanLineStride; /** * Number of steps to move to get to next sample. */ protected int pixelStride; /** * Current raster lower corner X coordinate; */ protected int crMinX; /** * Current raster lower corner Y coordinate; */ protected int crMinY; /** * An array specifying increment value to get to the next sample. */ protected int[] bandSteps; protected int[] bandOffsets; /** * Create raster iterator to follow from minX, minY raster and rectangle intersection coordinate. * * @param raster will be followed by this iterator. * @param subArea {@code Rectangle} which define read iterator area. * @throws IllegalArgumentException if subArea don't intersect raster boundary. */ protected DefaultDirectIterator(final Raster raster, final Rectangle subArea) { super(raster, subArea); final SampleModel sampleM = raster.getSampleModel(); if (sampleM instanceof ComponentSampleModel) { ComponentSampleModel sModel = (ComponentSampleModel) sampleM; pixelStride = sModel.getPixelStride(); scanLineStride = sModel.getScanlineStride(); bandOffsets = sModel.getBandOffsets(); bandSteps = getBandSteps(bandOffsets, pixelStride); } else { throw new IllegalArgumentException("DefaultDirectIterator constructor : sample model not conform"); } rasterWidth = raster.getWidth(); crMinX = raster.getMinX(); crMinY = raster.getMinY(); /* Initialize iteration attributes to be at a virtual position before the first pixel. Doing so, the first call * to next() will give us first sample of the first pixel. */ maxBanks = (areaIterateMaxX - crMinX) * pixelStride + (areaIterateMaxY - crMinY - 1) * scanLineStride; cursorStep = scanLineStride - ((areaIterateMaxX - areaIterateMinX) * pixelStride) + bandSteps[0]; // Virtual position : last sample of px[-1] dataCursor = baseCursor = (areaIterateMinX - crMinX - 1) * pixelStride + (areaIterateMinY - crMinY) * scanLineStride + bandOffsets[rasterNumBand - 1]; minX = areaIterateMinX; maxX = areaIterateMaxX; minY = areaIterateMinY; maxY = areaIterateMaxY; currentX = minX -1; band = -1; } /** * Create default rendered image iterator. * * @param renderedImage image which will be follow by iterator. * @param subArea {@code Rectangle} which represent image sub area iteration. * @throws IllegalArgumentException if subArea don't intersect image boundary. */ protected DefaultDirectIterator(final RenderedImage renderedImage, final Rectangle subArea) { super(renderedImage, subArea); final SampleModel sampleM = renderedImage.getSampleModel(); if (sampleM instanceof ComponentSampleModel) { ComponentSampleModel sModel = (ComponentSampleModel) sampleM; pixelStride = sModel.getPixelStride(); scanLineStride = sModel.getScanlineStride(); bandSteps = getBandSteps(sModel.getBandOffsets(), pixelStride); } else { throw new IllegalArgumentException("DefaultDirectIterator constructor : sample model not conform"); } rasterWidth = renderedImage.getTileWidth(); rasterNumBand = sampleM.getNumBands(); //initialize attributes to update current raster on first next() call. band = -1; tY = tMinY; tX = tMinX - 1; } /** * {@inheritDoc }. */ @Override public boolean next() { band = ++band % rasterNumBand; // Change pixel, end of a line if (band == 0 && ++currentX >= maxX) { // Check if we must change tile if ((dataCursor += cursorStep) >= maxBanks) { if (++tX >= tMaxX) { tX = tMinX; if (++tY >= tMaxY) { //-- initialize attribut with expected values to throw exception if another next() is called. band = -1; tX = tMaxX; if (tY - 1 >= tMaxY)//-- at first out tY == tMaxY and with another next() tY = tMaxY + 1. throw new IllegalStateException("Out of raster boundary. Illegal next call, you should rewind iterator first."); return false; } } updateCurrentRaster(tX, tY); } else { // Just change line currentX = minX; } // next sample } else { dataCursor += bandSteps[band]; } return true; } /** * Update current data array and current raster from tiles array coordinates. Data cursor will be positioned at first * sample of the first pixel to browse. * * @param tileX current X coordinate from rendered image tiles array. * @param tileY current Y coordinate from rendered image tiles array. */ protected void updateCurrentRaster(int tileX, int tileY) { //update raster this.currentRaster = renderedImage.getTile(tileX, tileY); this.crMinX = currentRaster.getMinX(); this.crMinY = currentRaster.getMinY(); final ComponentSampleModel sModel = (ComponentSampleModel) currentRaster.getSampleModel(); this.scanLineStride = sModel.getScanlineStride(); this.pixelStride = sModel.getPixelStride(); this.bandOffsets = sModel.getBandOffsets(); this.bandSteps = getBandSteps(bandOffsets, pixelStride); //update min max from subArea and raster boundary this.minX = Math.max(areaIterateMinX, crMinX); this.maxX = Math.min(areaIterateMaxX, crMinX + rasterWidth); this.minY = Math.max(areaIterateMinY, crMinY); this.maxY = Math.min(areaIterateMaxY, crMinY + currentRaster.getHeight()); final int minx = this.minX - crMinX; final int miny = this.minY - crMinY; final int maxx = this.maxX - crMinX; final int maxy = this.maxY - crMinY; this.maxBanks = maxx * pixelStride + (maxy-1) * scanLineStride; this.cursorStep = scanLineStride - ((maxx - minx) * pixelStride) + bandSteps[0]; this.dataCursor = minx * pixelStride + miny * scanLineStride + bandOffsets[0]; currentX = minX; band = 0; } /** * {@inheritDoc }. */ @Override public int getX() { return currentX; } /** * {@inheritDoc }. */ @Override public int getY() { return crMinY + (dataCursor - bandOffsets[band]) / scanLineStride; } /** * {@inheritDoc }. */ @Override public void rewind() { //initialize attributes like it was at built. if (renderedImage == null) { tX = tY = 0; tMaxX = tMaxY = 1; minX = areaIterateMinX; maxX = areaIterateMaxX; minY = areaIterateMinY; maxY = areaIterateMaxY; currentX = minX -1; dataCursor = baseCursor; } else { // Prepare Iterator to update its current raster on next call to next(). currentX = dataCursor = maxX = maxBanks = 0; this.tY = tMinY; this.tX = tMinX - 1; } band = -1; } /** * {@inheritDoc }. */ @Override public void moveTo(int x, int y, int b) { super.moveTo(x, y, b); if (renderedImage != null) { final int riMinX = renderedImage.getMinX(); final int riMinY = renderedImage.getMinY(); // tX = (x - riMinX) / rasterWidth + renderedImage.getMinTileX(); // tY = (y - riMinY) / renderedImage.getTileHeight() + renderedImage.getMinTileY(); final int tTempX = (x - riMinX) / rasterWidth + renderedImage.getMinTileX(); final int tTempY = (y - riMinY) / renderedImage.getTileHeight() + renderedImage.getMinTileY(); if (tTempX != tX || tTempY != tY) { tX = tTempX; tY = tTempY; updateCurrentRaster(tX, tY); } } // We are on the right tile, but not on the right pixel. this.dataCursor = (x - crMinX) * pixelStride + (y - crMinY) * scanLineStride + bandOffsets[0]; currentX = x; // Do not perform first sample step, the move above already put cursor on it. band = 0; while (band < b) { dataCursor += bandSteps[++band]; } } /** * {@inheritDoc }. */ @Override public SequenceType getIterationDirection() { if (renderedImage == null) return SequenceType.LINEAR;//1 raster seul if (renderedImage.getNumXTiles() <=1 && renderedImage.getNumYTiles() <= 1) return SequenceType.LINEAR; return null; } }