/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2003-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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.geotools.gui.swing.image;
// J2SE dependencies
import java.awt.geom.Rectangle2D;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderableImage;
// JAI dependencies
import javax.media.jai.JAI;
// Geotools dependencies
import org.geotools.gui.swing.ZoomPane;
/**
* A simple image viewer. This widget accepts either {@linkplain RenderedImage rendered} or
* {@linkplain RenderableImage renderable} image. Rendered image are display immediately,
* while renderable image will be rendered in a background thread when first requested.
* This widget may scale down images for faster rendering. This is convenient for image
* previews, but should not be used as a "real" renderer for full precision images.
*
* @since 2.3
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class ImagePane extends ZoomPane implements Runnable {
/**
* The default size for rendered image produced by a {@link RenderableImage}.
* This is also the maximum size for a {@link RenderedImage}; bigger image
* will be scaled down using JAI's "Scale" operation for faster rendering.
*/
private final int renderedSize;
/**
* The renderable image, or {@code null} if none. If non-null, then the {@link #run}
* method will transform this renderable image into a rendered one when first requested.
* Once the image is rendered, this field is set to {@code null}.
*/
private RenderableImage renderable;
/**
* The rendered image, or {@code null} if none. This image may be explicitly set
* by {@link #setImage(RenderedImage)}, or computed by {@link #run}.
*/
private RenderedImage rendered;
/**
* {@code true} if the {@link #run} method has been invoked for the current image.
* This field is used in order to avoid to start more than one thread for the same
* {@linkplain #renderable} image.
*/
private volatile boolean running;
/**
* Constructs an initially empty image pane with a default rendered image size.
*/
public ImagePane() {
this(512);
}
/**
* Constructs an initially empty image pane with the specified rendered image size.
* The {@code renderedSize} argument is the <em>maximum</em> width and height for
* {@linkplain RenderedImage rendered image}. Images greater than this value will be
* scaled down for faster rendering.
*/
public ImagePane(final int renderedSize) {
super(UNIFORM_SCALE | TRANSLATE_X | TRANSLATE_Y | ROTATE | RESET | DEFAULT_ZOOM);
setResetPolicy(true);
this.renderedSize = renderedSize;
}
/**
* Sets the source renderable image.
*/
public void setImage(final RenderableImage image) {
renderable = image;
rendered = null;
running = false;
reset();
repaint();
}
/**
* Sets the source rendered image.
*/
public void setImage(RenderedImage image) {
if (image != null) {
final float scale = Math.min(((float)renderedSize) / image.getWidth(),
((float)renderedSize) / image.getHeight());
if (scale < 1) {
final Float sc = new Float(scale);
image = JAI.create("Scale",
new ParameterBlock().addSource(image).add(sc).add(sc));
}
}
renderable = null;
rendered = image;
running = false;
reset();
repaint();
}
/**
* Reset the default zoom. This method overrides the default implementation in
* order to keep the <var>y</var> axis in its Java2D direction (<var>y</var>
* value increasing down), which is the usual direction of most image.
*/
public void reset() {
reset(getZoomableBounds(null), false);
}
/**
* Returns the image bounds, or {@code null} if none. This is used by
* {@link ZoomPane} in order to set the initial zoom.
*/
public Rectangle2D getArea() {
final RenderedImage rendered = this.rendered; // Protect from change in an other thread
if (rendered != null) {
return new Rectangle(rendered.getMinX(), rendered.getMinY(),
rendered.getWidth(), rendered.getHeight());
}
return null;
}
/**
* Paint the image. If the image was a {@link RenderableImage}, then a {@link RenderedImage}
* will be computed in a background thread when this method is first invoked.
*/
protected void paintComponent(final Graphics2D graphics) {
final RenderedImage rendered = this.rendered; // Protect from change in an other thread
if (rendered == null) {
if (renderable!=null && !running) {
running = true;
final Thread runner = new Thread(this, "Renderer");
runner.setPriority(Thread.NORM_PRIORITY-2);
runner.start();
}
} else {
graphics.drawRenderedImage(rendered, zoom);
}
}
/**
* Creates a {@linkplain RenderedImage rendered} view of the {@linkplain RenderableImage
* renderable} image and notifies {@link ZoomPane} when the result is ready. This method
* is run in a background thread and should not be invoked directly, unless the user wants
* to trig the {@link RenderedImage} creation immediately.
*/
public void run() {
running = true;
final RenderableImage producer = renderable; // Protect from change.
if (producer != null) {
final RenderedImage image = producer.createScaledRendering(renderedSize, 0, null);
EventQueue.invokeLater(new Runnable() {
public void run() {
if (producer == renderable) {
setImage(image);
}
}
});
}
}
}