/*
* 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.ui.imageviewing.view;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import virtualslideviewer.ui.imageviewing.ImagePresentationModel;
import virtualslideviewer.ui.imageviewing.ImagePresentationModel.Listener;
import virtualslideviewer.util.ImageUtil;
/**
* A JPanel which handles rendering of a virtual slide image.
*/
public class ImageRenderingPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private final ImagePresentationModel mUIModel;
private BufferedImage mCachedImage = new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR);
/**
* @param presentationModel A presentation model with panel's logic.
*/
public ImageRenderingPanel(ImagePresentationModel presentationModel)
{
if(presentationModel == null)
throw new IllegalArgumentException("presentationModel cannot be null.");
mUIModel = presentationModel;
mUIModel.addListener(new Listener()
{
@Override
public void onVisibleImageContentUpdate() { SwingUtilities.invokeLater(() -> repaint()); }
@Override
public void onImageChange() { SwingUtilities.invokeLater(() -> repaint()); }
});
MouseHandler handler = new MouseHandler();
super.addMouseListener(handler);
super.addMouseMotionListener(handler);
super.addMouseWheelListener(handler);
super.addComponentListener(new ResizeHandler());
}
@Override
protected void paintComponent(Graphics graphics)
{
super.paintComponent(graphics);
if(!mUIModel.isImageLoaded())
return;
renderImage((Graphics2D)graphics, loadImage());
}
private void renderImage(Graphics2D graphics, Image image)
{
Dimension imageSize = mUIModel.getVisibleImageRegionSize();
Point imagePos = ImageUtil.getCenteredPosition(imageSize, super.getSize());
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics.drawImage(image, imagePos.x, imagePos.y, imageSize.width, imageSize.height, null);
}
private Image loadImage()
{
reallocateCachedImageIfNeeded(mUIModel.getImageDataSize(), mUIModel.isImageRGB());
ImageUtil.loadDataIntoBufferedImage(mCachedImage, (dst) -> mUIModel.loadImageDataInto(dst));
return mCachedImage;
}
/**
* Reallocates cached buffered image if is needed.
*
* The image is cached in order to reduce the amount of unneeded allocations and gargabe collection runs.
*/
private void reallocateCachedImageIfNeeded(Dimension imageSize, boolean isRGB)
{
if(hasDifferentSize(mCachedImage, imageSize) || hasInvalidPixelType(mCachedImage, isRGB))
{
mCachedImage = new BufferedImage(imageSize.width, imageSize.height,
isRGB ? BufferedImage.TYPE_3BYTE_BGR : BufferedImage.TYPE_BYTE_GRAY);
}
}
private boolean hasDifferentSize(BufferedImage image, Dimension size)
{
return image.getWidth() != size.width || image.getHeight() != size.height;
}
private boolean hasInvalidPixelType(BufferedImage image, boolean shouldBeRGB)
{
boolean isRGB = (image.getType() != BufferedImage.TYPE_BYTE_GRAY);
return shouldBeRGB != isRGB;
}
private class ResizeHandler extends ComponentAdapter
{
@Override
public void componentResized(ComponentEvent e)
{
mUIModel.setViewportSize(ImageRenderingPanel.super.getSize());
}
}
private class MouseHandler extends MouseAdapter
{
private Point mLastMousePosition;
@Override
public void mousePressed(MouseEvent e)
{
mLastMousePosition = e.getPoint();
}
@Override
public void mouseDragged(MouseEvent e)
{
if(!SwingUtilities.isLeftMouseButton(e))
return;
mUIModel.pan(mLastMousePosition.x - e.getX(), mLastMousePosition.y - e.getY());
mLastMousePosition = e.getPoint();
}
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
mUIModel.zoomAt(-e.getWheelRotation(), e.getPoint());
}
}
}