/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package com.bc.ceres.glevel.support;
import com.bc.ceres.glevel.MultiLevelModel;
import com.bc.ceres.glevel.MultiLevelSource;
import javax.media.jai.Interpolation;
import javax.media.jai.OpImage;
import javax.media.jai.TileCache;
import javax.media.jai.operator.ScaleDescriptor;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
/**
* A default implementation for the {@link MultiLevelSource} interface.
*/
public class DefaultMultiLevelSource extends AbstractMultiLevelSource {
/**
* Default interpolation is "Nearest Neighbour".
*/
public static final Interpolation DEFAULT_INTERPOLATION = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
public static final MultiLevelSource NULL = createNull();
private final RenderedImage sourceImage;
private final Interpolation interpolation;
/**
* Constructs a new instance with {@link #DEFAULT_INTERPOLATION}.
*
* @param sourceImage The source image.
* @param levelCount The level count.
*/
public DefaultMultiLevelSource(RenderedImage sourceImage, int levelCount) {
this(sourceImage, levelCount, DEFAULT_INTERPOLATION);
}
/**
* Constructs a new instance.
*
* @param sourceImage The source image.
* @param levelCount The level count.
* @param interpolation The interpolation.
*/
public DefaultMultiLevelSource(RenderedImage sourceImage, int levelCount, Interpolation interpolation) {
this(sourceImage, createDefaultMultiLevelModel(sourceImage, levelCount), interpolation);
}
/**
* Constructs a new instance with {@link #DEFAULT_INTERPOLATION}.
*
* @param sourceImage The source image.
* @param multiLevelModel The multi level model.
*/
public DefaultMultiLevelSource(RenderedImage sourceImage, MultiLevelModel multiLevelModel) {
this(sourceImage, multiLevelModel, DEFAULT_INTERPOLATION);
}
/**
* Constructs a new instance with {@link #DEFAULT_INTERPOLATION}.
*
* @param sourceImage The source image.
* @param multiLevelModel The multi level model.
* @param interpolation The interpolation.
*/
public DefaultMultiLevelSource(RenderedImage sourceImage, MultiLevelModel multiLevelModel, Interpolation interpolation) {
super(multiLevelModel);
this.sourceImage = sourceImage;
this.interpolation = interpolation;
}
public RenderedImage getSourceImage() {
return sourceImage;
}
public Interpolation getInterpolation() {
return interpolation;
}
/**
* Returns the level-0 image if {@code level} equals zero, otherwise calls {@code super.getLevelImage(level)}.
* This override prevents the base class from storing a reference to the source image (the level-0 image).
* See {@link AbstractMultiLevelSource#createImage(int)}.
*
* @param level The level.
* @return The image.
*/
@Override
public synchronized RenderedImage getImage(int level) {
if (level == 0) {
return sourceImage;
}
return super.getImage(level);
}
/**
* Creates a scaled version of the level-0 image for the given level.
* See {@link #getImage(int)} and {@link AbstractMultiLevelSource#createImage(int) super.createImage(int)}.
*
* @param level The level.
* @return The image.
*/
@Override
protected RenderedImage createImage(int level) {
if (level == 0) {
return sourceImage;
}
double scale = getModel().getScale(level);
double invScale = 1.0 / scale;
int jaiW = getLevelImageSizeJAI(sourceImage.getWidth(), scale);
int jaiH = getLevelImageSizeJAI(sourceImage.getHeight(), scale);
int j2kW = getLevelImageSizeJ2K(sourceImage.getWidth(), scale);
int j2kH = getLevelImageSizeJ2K(sourceImage.getHeight(), scale);
// Force JAI ScaleDescriptor to compute J2K-sized lower resolution images
float scaleX;
if (jaiW == j2kW) {
scaleX = (float) invScale;
} else {
scaleX = (float) ((double) j2kW / (double) sourceImage.getWidth());
}
float scaleY;
if (jaiH == j2kH) {
scaleY = (float) invScale;
} else {
scaleY = (float) ((double) j2kH /(double) sourceImage.getHeight());
}
return ScaleDescriptor.create(sourceImage, scaleX, scaleY, 0.0F, 0.0F, interpolation, null);
}
@Override
public void reset() {
removeTilesFromCache(sourceImage);
super.reset();
}
public static MultiLevelModel createDefaultMultiLevelModel(RenderedImage sourceImage, int levelCount) {
return new DefaultMultiLevelModel(levelCount,
new AffineTransform(),
sourceImage.getWidth(),
sourceImage.getHeight());
}
/**
* Computes the boundaries of an image at a given resolution scaling from the given source image boundaries (at level zero).
*
* @param sourceBounds The image boundaries of the level zero image.
* @param scale The scale at a given level as returned by {@link MultiLevelModel#getScale(int)}.
* @return The level image boundaries in pixel coordinates.
* @since BEAM 5
*/
public static Rectangle getLevelImageBounds(Rectangle sourceBounds, double scale) {
return new Rectangle(getLevelImageSizeJ2K(sourceBounds.x, scale),
getLevelImageSizeJ2K(sourceBounds.y, scale),
getLevelImageSizeJ2K(sourceBounds.width, scale),
getLevelImageSizeJ2K(sourceBounds.height, scale));
}
// JPEG2000 Style:
// Used in order to support S-2 MSI image data.
// Will ensure that no data loss takes place due to truncation.
// Return value will always be >= 1.
private static int getLevelImageSizeJ2K(int sourceSize, double scale) {
return (int) Math.ceil(sourceSize / scale);
}
// JAI ScaleDescriptor Style:
// Return value may be zero.
private static int getLevelImageSizeJAI(int sourceSize, double scale) {
return (int) Math.ceil(sourceSize / scale - 0.5);
}
private static MultiLevelSource createNull() {
final BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY);
final DefaultMultiLevelModel model = new DefaultMultiLevelModel(1, new AffineTransform(), null);
return new DefaultMultiLevelSource(image, model);
}
// todo - very useful method, make it accessible from outside.
private static void removeTilesFromCache(RenderedImage image) {
if (image instanceof OpImage) {
OpImage opImage = (OpImage) image;
TileCache tileCache = opImage.getTileCache();
if (tileCache != null) {
tileCache.removeTiles(image);
}
}
}
}