/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2013, 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.storage.coverage;
import org.apache.sis.util.logging.Logging;
import javax.imageio.ImageReader;
import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.collection.Cache;
import org.geotoolkit.math.XMath;
/**
* Implementation of RenderedImage using GridMosaic.
* With this a GridMosaic can be see as a RenderedImage.
*
* @author Thomas Rouby (Geomatys)
* @author Quentin Boileau (Geomatys)
* @author Johann Sorel (Geomatys)
* @module
*/
public class GridMosaicRenderedImage implements RenderedImage {
private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.storage.coverage");
/**
* A tile cache
*/
private final Cache<Point,Raster> tileCache = new Cache<>(10, 12, true);
/**
* The original mosaic to read
*/
private final GridMosaic mosaic;
/**
* Tile range to map as a rendered image in the mosaic
*/
private final Rectangle gridRange;
/**
* The color model of the mosaic rendered image
*/
private ColorModel colorModel = null;
/**
* The sample model of the mosaic rendered image
*/
private SampleModel sampleModel = null;
/**
* Constructor
* @param mosaic the mosaic to read as a rendered image
*/
public GridMosaicRenderedImage(final GridMosaic mosaic){
this(mosaic,new Rectangle(mosaic.getGridSize()));
}
/**
* Constructor
* @param mosaic the mosaic to read as a rendered image
* @param gridRange the tile to include in the rendered image.
* rectangle max max values are exclusive.
*/
public GridMosaicRenderedImage(final GridMosaic mosaic, Rectangle gridRange){
ArgumentChecks.ensureNonNull("mosaic", mosaic);
ArgumentChecks.ensureNonNull("range", gridRange);
if(mosaic.getGridSize().width == 0 || mosaic.getGridSize().height == 0){
throw new IllegalArgumentException("Mosaic grid can not be empty.");
}
this.mosaic = mosaic;
this.gridRange = gridRange;
RenderedImage firstTile = getFirstTile();
if (firstTile != null) {
this.sampleModel = firstTile.getSampleModel();
this.colorModel = firstTile.getColorModel();
}
}
private RenderedImage getFirstTile() {
RenderedImage firstTile = null;
if (colorModel == null && sampleModel == null) {
try {
//search the first non missing tile of the Mosaic
final Rectangle dataArea = mosaic.getDataArea();
if (dataArea != null) {
final TileReference tile = mosaic.getTile(dataArea.x, dataArea.y, null);
if (tile != null) {
if (tile.getInput() instanceof RenderedImage) {
firstTile = (RenderedImage) tile.getInput();
} else {
final ImageReader reader = tile.getImageReader();
firstTile = reader.read(0);
reader.dispose();
}
}
}
} catch (IOException e) {
throw new IllegalArgumentException("First tile can't be read.", e);
} catch (DataStoreException e) {
throw new IllegalArgumentException("Input mosaic doesn't have any tile.", e);
}
}
return firstTile;
}
/**
* Return intern GridMosaic
* @return GridMosaic
*/
public GridMosaic getGridMosaic(){
return this.mosaic;
}
/**
*
* @return
*/
public Rectangle getGridRange() {
return (Rectangle) gridRange.clone();
}
/**
* {@inheritDoc}
*/
@Override
public Vector<RenderedImage> getSources() {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Object getProperty(String name) {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public String[] getPropertyNames() {
return new String[0];
}
/**
* {@inheritDoc}
*/
@Override
public ColorModel getColorModel() {
if (colorModel == null) {
RenderedImage firstTile = getFirstTile();
if (firstTile != null) {
this.colorModel = firstTile.getColorModel();
}
}
return this.colorModel;
}
/**
* {@inheritDoc}
*/
@Override
public SampleModel getSampleModel() {
if (sampleModel == null) {
RenderedImage firstTile = getFirstTile();
if (firstTile != null) {
this.sampleModel = firstTile.getSampleModel();
}
}
return this.sampleModel;
}
/**
* {@inheritDoc}
*/
@Override
public int getWidth() {
return gridRange.width * this.mosaic.getTileSize().width;
}
/**
* {@inheritDoc}
*/
@Override
public int getHeight() {
return gridRange.height * this.mosaic.getTileSize().height;
}
/**
* {@inheritDoc}
*/
@Override
public int getMinX() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getMinY() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getNumXTiles() {
return gridRange.width;
}
/**
* {@inheritDoc}
*/
@Override
public int getNumYTiles() {
return gridRange.height;
}
/**
* {@inheritDoc}
*/
@Override
public int getMinTileX() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getMinTileY() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getTileWidth() {
return this.mosaic.getTileSize().width;
}
/**
* {@inheritDoc}
*/
@Override
public int getTileHeight() {
return this.mosaic.getTileSize().width;
}
/**
* {@inheritDoc}
*/
@Override
public int getTileGridXOffset() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getTileGridYOffset() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public Raster getTile(int tileX, int tileY) {
tileX += gridRange.x;
tileY += gridRange.y;
Raster raster;
try {
raster = this.tileCache.get(new Point(tileX, tileY));
} catch (IllegalArgumentException ex) {
raster = null;
}
if (raster == null) {
try {
DataBuffer buffer = null;
if (!mosaic.isMissing(tileX,tileY)) {
final TileReference tile = mosaic.getTile(tileX,tileY, null);
if (tile != null) {
if (tile.getInput() instanceof RenderedImage) {
buffer = ((RenderedImage)tile.getInput()).getData().getDataBuffer();
} else {
final ImageReader reader = tile.getImageReader();
buffer = reader.read(0).getData().getDataBuffer();
reader.dispose();
}
}
}
if(buffer==null){
//create an empty buffer
buffer = getSampleModel().createDataBuffer();
}
//create a raster from tile image with tile position offset.
LOGGER.log(Level.FINE, "Request tile {0}:{1} ", new Object[]{tileX,tileY});
final int rX = tileX*this.getTileWidth();
final int rY = tileY*this.getTileHeight();
raster = Raster.createWritableRaster(getSampleModel(), buffer, new Point(rX, rY));
this.tileCache.put(new Point(tileX, tileY), raster);
} catch ( DataStoreException | IOException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
}
return raster;
}
private boolean isTileMissing(int x, int y) throws DataStoreException{
return mosaic.isMissing(x+gridRange.x, y+gridRange.y);
}
private TileReference getTileReference(int x, int y) throws DataStoreException{
return mosaic.getTile(x+gridRange.x,y+gridRange.y,null);
}
/**
* {@inheritDoc}
*/
@Override
public Raster getData() {
final RenderedImage firstTile = getFirstTile();
Raster rasterOut = null;
if (firstTile != null) {
rasterOut = firstTile.getTile(0, 0).createCompatibleWritableRaster(getWidth(), getHeight());
// Clear dataBuffer to 0 value for all bank
for (int s = 0; s < rasterOut.getDataBuffer().getSize(); s++) {
for (int b = 0; b < rasterOut.getDataBuffer().getNumBanks(); b++) {
rasterOut.getDataBuffer().setElem(b, s, 0);
}
}
try {
for (int y = 0; y < this.getNumYTiles(); y++) {
for (int x = 0; x < this.getNumYTiles(); x++) {
if (!isTileMissing(x, y)) {
final TileReference tile = getTileReference(x, y);
final RenderedImage sourceImg;
if (tile.getInput() instanceof RenderedImage) {
sourceImg = (RenderedImage) tile.getInput();
} else {
sourceImg = tile.getImageReader().read(tile.getImageIndex());
}
final Raster rasterIn = sourceImg.getData();
rasterOut.getSampleModel().setDataElements(x * this.getTileWidth(), y * this.getTileHeight(), this.getTileWidth(), this.getTileHeight(),
rasterIn.getSampleModel().getDataElements(0, 0, this.getTileWidth(), this.getTileHeight(), null, rasterIn.getDataBuffer()),
rasterOut.getDataBuffer());
}
}
}
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "", ex);
}
}
return rasterOut;
}
/**
* {@inheritDoc}
*/
@Override
public Raster getData(Rectangle rect) {
final RenderedImage firstTile = getFirstTile();
Raster rasterOut = null;
if (firstTile != null) {
rasterOut = firstTile.getTile(0, 0).createCompatibleWritableRaster(rect.width, rect.height);
// Clear dataBuffer to 0 value for all bank
for (int s = 0; s < rasterOut.getDataBuffer().getSize(); s++) {
for (int b = 0; b < rasterOut.getDataBuffer().getNumBanks(); b++) {
rasterOut.getDataBuffer().setElem(b, s, 0);
}
}
try {
final Point upperLeftPosition = this.getPositionOf(rect.x, rect.y);
final Point lowerRightPosition = this.getPositionOf(rect.x + rect.width - 1, rect.y + rect.height - 1);
for (int y = Math.max(upperLeftPosition.y, 0); y < Math.min(lowerRightPosition.y + 1, this.getNumYTiles()); y++) {
for (int x = Math.max(upperLeftPosition.x, 0); x < Math.min(lowerRightPosition.x + 1, this.getNumXTiles()); x++) {
if (!isTileMissing(x, y)) {
final TileReference tile = getTileReference(x, y);
final Rectangle tileRect = new Rectangle(x * this.getTileWidth(), y * this.getTileHeight(), this.getTileWidth(), this.getTileHeight());
final int minX, maxX, minY, maxY;
minX = XMath.clamp(rect.x, tileRect.x, tileRect.x + tileRect.width);
maxX = XMath.clamp(rect.x + rect.width, tileRect.x, tileRect.x + tileRect.width);
minY = XMath.clamp(rect.y, tileRect.y, tileRect.y + tileRect.height);
maxY = XMath.clamp(rect.y + rect.height, tileRect.y, tileRect.y + tileRect.height);
final Rectangle rectIn = new Rectangle(minX, minY, maxX - minX, maxY - minY);
rectIn.translate(-tileRect.x, -tileRect.y);
final Rectangle rectOut = new Rectangle(minX, minY, maxX - minX, maxY - minY);
rectOut.translate(-rect.x, -rect.y);
if (rectIn.width <= 0 || rectIn.height <= 0 || rectOut.width <= 0 || rectOut.height <= 0) {
continue;
}
final RenderedImage sourceImg;
if (tile.getInput() instanceof RenderedImage) {
sourceImg = (RenderedImage) tile.getInput();
} else {
sourceImg = tile.getImageReader().read(tile.getImageIndex());
}
final Raster rasterIn = sourceImg.getData();
rasterOut.getSampleModel().setDataElements(rectOut.x, rectOut.y, rectOut.width, rectOut.height,
rasterIn.getSampleModel().getDataElements(rectIn.x, rectIn.y, rectIn.width, rectIn.height, null, rasterIn.getDataBuffer()),
rasterOut.getDataBuffer());
}
}
}
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "", ex);
}
}
return rasterOut;
}
/**
* Get the tile column and row position for a pixel.
* Return value can be out of the gridSize
* @param x
* @param y
* @return
*/
private Point getPositionOf(int x, int y){
final int posX = (int)(Math.floor(x/this.getTileWidth()));
final int posY = (int)(Math.floor(y/this.getTileHeight()));
return new Point(posX, posY);
}
/**
* {@inheritDoc}
*/
@Override
public WritableRaster copyData(WritableRaster raster) {
return null;
}
}