/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2012-2015, 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.io.large; import java.awt.Dimension; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.image.*; import java.util.Vector; import org.apache.sis.util.ArgumentChecks; import org.geotoolkit.image.iterator.PixelIterator; import org.geotoolkit.image.iterator.PixelIteratorFactory; /** * Abstract large rendered image. * This rendered image may be used to represent a very large image but only loading it * tile by tile. * * Subclasses mush implement the getTile method, the caching mechanism is leaved to * the implementor. * * @author Remi Marechal (Geomatys). * @author Johann Sorel (Geomatys). */ public abstract class AbstractLargeRenderedImage implements RenderedImage { /** * Default tile size. */ private static final int DEFAULT_TILE_SIZE = 256; /** * Minimum required tile size. */ private static final int MIN_TILE_SIZE = 64; /** * Upper left corner of currently stored {@link Raster}. */ private static final Point ptOffset = new Point(); /** * Image attributs. */ private final int minX; private final int minY; private final int width; private final int height; private final int tileWidth; private final int tileHeight; private final int tileGridXOffset; private final int tileGridYOffset; private int minTileGridX; private int minTileGridY; private final int nbrTileX; private final int nbrTileY; private final ColorModel cm; private final SampleModel sm; /** * Create {@link LargeRenderedImage} with default upper corner at position (0, 0), * a default tile size of 256 x 256 pixels and a default tile grid offset at position (0, 0). * * @param width image width. * @param height image height. * @param colorModel {@link ColorModel} use to build {@link Raster} (image tiles). */ public AbstractLargeRenderedImage(int width, int height, SampleModel sampleModel, ColorModel colorModel) { this(0, 0, width, height, null, 0, 0, sampleModel, colorModel); } /** * Create {@link LargeRenderedImage} object. * * @param minX image upper left corner min X values. * @param minY image upper left corner min Y values. * @param width image width. * @param height image height. * @param tileSize size of tile or raster within this image. * @param tileGridXOffset tile grid offset in X direction. * @param tileGridYOffset tile grid offset in Y direction. * @param sampleModel * @param colorModel {@link ColorModel} use to build {@link WritableRaster} (image tiles). */ public AbstractLargeRenderedImage(int minX, int minY, int width, int height, Dimension tileSize, int tileGridXOffset, int tileGridYOffset, SampleModel sampleModel, ColorModel colorModel) { ArgumentChecks.ensureNonNull("ColorModel", colorModel); ArgumentChecks.ensureStrictlyPositive("image width", width); ArgumentChecks.ensureStrictlyPositive("image height", height); this.minX = minX; this.minY = minY; this.width = width; this.height = height; if (tileSize == null) { this.tileWidth = this.tileHeight = DEFAULT_TILE_SIZE; } else { tileWidth = Math.min(Math.max(MIN_TILE_SIZE, tileSize.width), DEFAULT_TILE_SIZE); tileHeight = Math.min(Math.max(MIN_TILE_SIZE, tileSize.height), DEFAULT_TILE_SIZE); } this.tileGridXOffset = tileGridXOffset; this.tileGridYOffset = tileGridYOffset; this.nbrTileX = (width + tileWidth - 1) / tileWidth; this.nbrTileY = (height + tileHeight - 1) / tileHeight; this.cm = colorModel; this.sm = (colorModel.isCompatibleSampleModel(sampleModel)) ? sampleModel : colorModel.createCompatibleSampleModel(tileWidth, tileHeight); this.minTileGridX = (minX - tileGridXOffset) / tileWidth; this.minTileGridY = (minY - tileGridYOffset) / tileHeight; if (tileGridXOffset < minX) minTileGridX--; if (tileGridYOffset < minY) minTileGridY--; } /** * {@inheritDoc }. */ @Override public Vector<RenderedImage> getSources() { throw new UnsupportedOperationException("Not supported yet."); } /** * {@inheritDoc }. */ @Override public Object getProperty(String name) { return Image.UndefinedProperty; } /** * {@inheritDoc }. */ @Override public String[] getPropertyNames() { return null; } /** * {@inheritDoc }. */ @Override public ColorModel getColorModel() { return cm; } /** * {@inheritDoc }. */ @Override public SampleModel getSampleModel() { return sm; } /** * {@inheritDoc }. */ @Override public int getWidth() { return width; } /** * {@inheritDoc }. */ @Override public int getHeight() { return height; } /** * {@inheritDoc }. */ @Override public int getMinX() { return minX; } /** * {@inheritDoc }. */ @Override public int getMinY() { return minY; } /** * {@inheritDoc }. */ @Override public int getNumXTiles() { return nbrTileX; } /** * {@inheritDoc }. */ @Override public int getNumYTiles() { return nbrTileY; } /** * {@inheritDoc }. */ @Override public int getMinTileX() { return minTileGridX; } /** * {@inheritDoc }. */ @Override public int getMinTileY() { return minTileGridY; } /** * {@inheritDoc }. */ @Override public int getTileWidth() { return tileWidth; } /** * {@inheritDoc }. */ @Override public int getTileHeight() { return tileHeight; } /** * {@inheritDoc }. */ @Override public int getTileGridXOffset() { return tileGridXOffset; } /** * {@inheritDoc }. */ @Override public int getTileGridYOffset() { return tileGridYOffset; } /** * {@inheritDoc }. */ @Override public abstract Raster getTile(int tileX, int tileY); /** * {@inheritDoc }. */ @Override public Raster getData() { // in contradiction with this class aim. // in attempt to replace JAI dependencies. if (width <= 5000 && height <= 5000) { ptOffset.setLocation(minX, minY); final WritableRaster wr = Raster.createWritableRaster(cm.createCompatibleSampleModel(width, height), ptOffset); final Rectangle rect = new Rectangle(); int my = minY; for (int ty = minTileGridY, tmy = minTileGridY + nbrTileY; ty < tmy; ty++) { int mx = minX; for (int tx = minTileGridX, tmx = minTileGridX + nbrTileX; tx < tmx; tx++) { final Raster r = getTile(tx, ty); rect.setBounds(mx, my, tileWidth, tileHeight); //recopie final PixelIterator copix = PixelIteratorFactory.createDefaultWriteableIterator(wr, wr, rect); final PixelIterator pix = PixelIteratorFactory.createDefaultIterator(r, rect); while (copix.next()) { pix.next(); copix.setSampleDouble(pix.getSampleDouble()); } mx += tileWidth; } my += tileHeight; } return wr; } throw new UnsupportedOperationException("Not supported yet. Raster weight too expensive."); } /** * {@inheritDoc }. */ @Override public Raster getData(Rectangle rect) { // in contradiction with this class aim. // in attempt to replace JAI dependencies. final int rx = Math.max(rect.x, minX); final int ry = Math.max(rect.y, minY); final int rw = Math.min(rect.x+rect.width, minX+width)-rx; final int rh = Math.min(rect.y+rect.height, minY+height)-ry; if (rw <= 5000 && rh <= 5000) { ptOffset.setLocation(rx, ry); final WritableRaster wr = Raster.createWritableRaster(cm.createCompatibleSampleModel(rw, rh), ptOffset); final Rectangle area = new Rectangle(); int ty = minTileGridY + (ry - minY) / tileHeight; int tbx = minTileGridX + (rx - minX) / tileWidth; int tmaxY = (ry+rh-minY+tileHeight-1)/tileHeight; int tmaxX = (rx+rw-minX+tileWidth-1)/tileWidth; for (; ty < tmaxY; ty++) { for (int tx = tbx; tx < tmaxX; tx++) { final Raster r = getTile(tx, ty); final int ix = Math.max(rx, minX + (tx-minTileGridX) * tileWidth); final int iy = Math.max(ry, minY + (ty-minTileGridY) * tileHeight); final int imx = Math.min(rx + rw, minX + (tx + 1 - minTileGridX) * tileWidth); final int imy = Math.min(ry + rh, minY + (ty + 1 - minTileGridY) * tileHeight); area.setBounds(ix, iy, imx-ix, imy-iy); //recopie final PixelIterator copix = PixelIteratorFactory.createDefaultWriteableIterator(wr, wr, area); final PixelIterator pix = PixelIteratorFactory.createDefaultIterator(r, area); while (copix.next()) { pix.next(); copix.setSampleDouble(pix.getSampleDouble()); } } } return wr; } throw new UnsupportedOperationException("Not supported yet.Raster weight too expensive."); } /** * {@inheritDoc }. */ @Override public WritableRaster copyData(WritableRaster raster) { throw new UnsupportedOperationException("Not supported yet."); } }