/*
* Copyright (C) 2015 Patryk Strach
*
* This file is part of Virtual Slide Viewer.
*
* Virtual Slide Viewer 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.
*
* Virtual Slide Viewer 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 Virtual Slide Viewer.
* If not, see <http://www.gnu.org/licenses/>.
*/
package virtualslideviewer.core;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import virtualslideviewer.util.ByteArrayPool;
import virtualslideviewer.util.ImageUtil;
import virtualslideviewer.util.ParameterValidator;
/**
* Single image from a virtual slide.
*/
public abstract class VirtualSlideImage implements AutoCloseable
{
private final ByteArrayPool mTileBufferPool = new ByteArrayPool();
private final PropertyChangeSupport mPropertyListeners = new PropertyChangeSupport(this);
/**
* Returns image size at specified resolution level.
* The resolutions are in increasing order, that is, the higher the resolution index, the bigger the image is.
*
* @param resIndex Index of resolution level.
*/
public abstract Dimension getImageSize(int resIndex);
/**
* Returns number of resolution levels this image has.
*/
public abstract int getResolutionCount();
public abstract int getChannelCount();
public abstract int getZPlaneCount();
public abstract int getTimePointCount();
/**
* Returns tile size at specified resolution level.
*
* Beware that the tiles lying at the edges of image will be clipped to image size.
* To get accurate tile size use Tile.getBounds().
*
* @param resIndex Index of resolution level.
*/
public abstract Dimension getTileSize(int resIndex);
/**
* Informs whether the image's pixels are in RGB format.
*/
public abstract boolean isRGB();
/**
* Reads pixels of specified tile.
*
* In this function overload, the buffer for pixels is allocated automatically.
* If the pixels are in RGB format, the layout of pixels in returned data is always RGBRGB...
*
* @param tile The tile whose pixels should be loaded.
*
* @return Buffer containing tile's pixels.
*/
public byte[] getTileData(Tile tile)
{
Dimension tileSize = tile.getBounds(this).getSize();
byte[] tileData = new byte[tileSize.width * tileSize.height * (isRGB() ? 3 : 1)];
getTileData(tileData, tile);
return tileData;
}
/**
* Reads pixels of specified tile.
*
* If the pixels are in RGB format, the layout of pixels in returned data is always RGBRGB...
*
* This method HAS to be Thread Safe.
*
* @param dst Preallocated buffer where the pixels will be stored.
* @param tile The tile whose pixels should be loaded.
*/
public abstract void getTileData(byte[] dst, Tile tile);
/**
* Reads pixels from specified region.
*
* In this function overload, the buffer for pixels is allocated automatically.
* If the pixels are in RGB format, the layout of pixels in returned data is always RGBRGB...
*
* @param bounds The bounds of region from which the pixels will be read.
* @param imageIndex The image index.
*
* @return Buffer containing read pixels.
*/
public byte[] getPixels(Rectangle bounds, ImageIndex imageIndex)
{
if(bounds == null)
throw new IllegalArgumentException("bounds cannot be null.");
byte[] tileData = new byte[bounds.width * bounds.height * (isRGB() ? 3 : 1)];
getPixels(tileData, bounds, imageIndex);
return tileData;
}
/**
* Reads pixels from specified region.
*
* If the pixels are in RGB format, the layout of pixels in returned data is always RGBRGB...
*
* @param dst Preallocated buffer where the pixels will be stored.
* @param bounds The bounds of region from which the pixels will be read.
* @param resIndex The resolution index.
*/
public void getPixels(byte[] dst, Rectangle bounds, ImageIndex imageIndex)
{
if(dst == null)
throw new IllegalArgumentException("dst cannot be null.");
if(bounds == null)
throw new IllegalArgumentException("bounds cannot be null.");
if(imageIndex == null)
throw new IllegalArgumentException("imageIndex cannot be null.");
if(!imageIndex.isValid(this))
throw new IllegalArgumentException("Invalid resolution index.");
final int channelsCount = (isRGB() ? 3 : 1);
Dimension tileSize = getTileSize(imageIndex.getResolutionIndex());
byte[] tileData = mTileBufferPool.borrow(tileSize.width * tileSize.height * channelsCount);
{
for(Tile tile : ImageUtil.getTilesInArea(bounds, tileSize, imageIndex))
{
getTileData(tileData, tile);
ImageUtil.copyIntersectingPartOfImage(tileData, tile.getBounds(this), dst, bounds, channelsCount);
}
}
mTileBufferPool.putBack(tileData);
}
public void addPropertyChangeListener(PropertyChangeListener listener)
{
ParameterValidator.throwIfNull(listener, "listener");
mPropertyListeners.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener(String property, PropertyChangeListener listener)
{
ParameterValidator.throwIfNull(listener, "listener");
mPropertyListeners.addPropertyChangeListener(property, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener)
{
ParameterValidator.throwIfNull(listener, "listener");
mPropertyListeners.removePropertyChangeListener(listener);
}
public void removePropertyChangeListener(String property, PropertyChangeListener listener)
{
ParameterValidator.throwIfNull(listener, "listener");
mPropertyListeners.removePropertyChangeListener(property, listener);
}
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue)
{
mPropertyListeners.firePropertyChange(propertyName, oldValue, newValue);
}
/**
* Returns the name of image.
*/
public abstract String getName();
/**
* Sets the name of image.
*/
public abstract void setName(String newName);
/**
* Returns unique ID representing the image.
*
* This ID is used to identify the image during caching of image's data, so the following should apply:
* - when two different instances of image returns identical data they should have identical ID,
* - when pixels data returned by two instances of image are different, their IDs <b>must</b> be different,
* - the ID does not have to be unique across different virtual slides, as the cache is cleared when a virtual slide is changed.
*/
public abstract String getID();
@Override
public abstract void close();
@Override
public String toString()
{
return getName();
}
}