/* * 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.imageviewing; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import virtualslideviewer.core.BufferedVirtualSlideImage; import virtualslideviewer.core.ImageIndex; import virtualslideviewer.core.Tile; import virtualslideviewer.core.VirtualSlideImage; import virtualslideviewer.util.ImageUtil; /** * Tile placeholder generator which generates tiles from other resolutions if required data is already in cache. * If the data is not available in other resolutions, the tile will be generated from its thumbnail. */ public class DifferentResolutionsTileGenerator implements LoadingTilePlaceholderGenerator { private final static long MAX_THUMBNAIL_SIZE = 1024 * 1024 * 3; @Override public void getTilePlaceholder(byte[] dst, BufferedVirtualSlideImage tileSource, Tile tileToGenerate) { boolean generatedFromOtherResolution = tryToGenerateTileFromOtherResolutions(dst, tileSource, tileToGenerate); if(!generatedFromOtherResolution) { generateTileFromThumbnail(dst, tileSource, tileToGenerate); } } private boolean tryToGenerateTileFromOtherResolutions(byte[] dst, BufferedVirtualSlideImage tileSource, Tile tileToGenerate) { int highestResToCheck = Math.min(tileToGenerate.getImageIndex().getResolutionIndex() + 1, tileSource.getResolutionCount() - 1); int thumbnailResIndex = ImageUtil.getResolutionIndexWithSizeNotBiggerThan(tileSource, MAX_THUMBNAIL_SIZE); for(int currentRes = highestResToCheck; currentRes >= thumbnailResIndex; currentRes--) { Rectangle originalTileBounds = tileToGenerate.getBounds(tileSource); Rectangle tileBoundsInThisRes = scaleBoundsToMatchDifferentResolution(tileSource, originalTileBounds, tileToGenerate.getImageIndex().getResolutionIndex(), currentRes); ImageIndex imageIndexAtThisRes = new ImageIndex(currentRes, tileToGenerate.getImageIndex().getChannel(), tileToGenerate.getImageIndex().getZPlane(), tileToGenerate.getImageIndex().getTimePoint()); if(tileSource.isImageInCache(tileBoundsInThisRes, imageIndexAtThisRes)) { byte[] generatedTile = getScaledPixels(tileSource, tileBoundsInThisRes, imageIndexAtThisRes, originalTileBounds.getSize()); System.arraycopy(generatedTile, 0, dst, 0, generatedTile.length); return true; } } return false; } private void generateTileFromThumbnail(byte[] dst, VirtualSlideImage source, Tile tileToGenerate) { // TODO It should read data from some pregenerated thumbnail which is persistent in memory // The lowest resolution of some images is far too small. int thumbnailResIndex = ImageUtil.getResolutionIndexWithSizeNotBiggerThan(source, MAX_THUMBNAIL_SIZE); Rectangle tileBounds = tileToGenerate.getBounds(source); Rectangle tileBoundsInThumbnail = scaleBoundsToMatchDifferentResolution(source, tileBounds, tileToGenerate.getImageIndex().getResolutionIndex(), thumbnailResIndex); ImageIndex thumbnailImageIndex = new ImageIndex(thumbnailResIndex, tileToGenerate.getImageIndex().getChannel(), tileToGenerate.getImageIndex().getZPlane(), tileToGenerate.getImageIndex().getTimePoint()); byte[] generatedTile = getScaledPixels(source, tileBoundsInThumbnail, thumbnailImageIndex, tileBounds.getSize()); System.arraycopy(generatedTile, 0, dst, 0, generatedTile.length); } /** * Scales given bounds in original resolution to match the same region in different resolution. */ private Rectangle scaleBoundsToMatchDifferentResolution(VirtualSlideImage sourceImage, Rectangle originalBounds, int originalResIndex, int newResIndex) { double scaleX = sourceImage.getImageSize(newResIndex).getWidth() / sourceImage.getImageSize(originalResIndex).getWidth(); double scaleY = sourceImage.getImageSize(newResIndex).getHeight() / sourceImage.getImageSize(originalResIndex).getHeight(); int x = (int)(originalBounds.x * scaleX); int y = (int)(originalBounds.y * scaleY); int width = (int)Math.ceil(originalBounds.width * scaleX); int height = (int)Math.ceil(originalBounds.height * scaleY); return new Rectangle(x, y, width, height); } private byte[] getScaledPixels(VirtualSlideImage source, Rectangle originalPixelsBounds, ImageIndex imageIndex, Dimension newSize) { BufferedImage originalImage = new BufferedImage(originalPixelsBounds.width, originalPixelsBounds.height, (source.isRGB() ? BufferedImage.TYPE_3BYTE_BGR : BufferedImage.TYPE_BYTE_GRAY)); byte[] originalImageDataBuffer = ((DataBufferByte)originalImage.getRaster().getDataBuffer()).getData(); source.getPixels(originalImageDataBuffer, originalPixelsBounds, imageIndex); BufferedImage scaledImage = ImageUtil.scaleImage(originalImage, newSize, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); return ((DataBufferByte)scaledImage.getRaster().getDataBuffer()).getData(); } }