All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.shredzone.jshred.swing.JImageViewer Maven / Gradle / Ivy

/**
 * jshred - Shred's Toolbox
 *
 * Copyright (C) 2009 Richard "Shred" Körber
 *   http://jshred.shredzone.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License / GNU Lesser
 * General Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * 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.
 *
 */
package net.shredzone.jshred.swing;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JScrollPane;

/**
 * An {@link JImageViewer} renders an image in the centre of the component. This image can
 * be scaled, and a transparency checkboard can also be drawn behind it.
 * 

* As an extra feature, the image can be dragged with the mouse if the viewer is in a * JScrollPane. *

* This class also implements the {@link Printable} interface since R13. * * @author Richard "Shred" Körber * @since R9 */ public class JImageViewer extends JComponent implements Printable { private static final long serialVersionUID = 3690198762949851445L; public enum Quality { /** * The default quality. Usually this is about the quality of QUALITY_FAST, but may * depend on the client's system and might change in future. */ DEFAULT, /** * This quality is very fast, but will give a rather poor scaling result. */ FAST, /** * This quality is reasonable in speed and quality. */ SMOOTH, /** * This quality gives best scaling results, but it is rather slow. */ BEST }; public enum Autoscale { /** * No autoscaling. This is the default. Use this mode if you want to embed the * JImageViewer into a JScrollPane. */ OFF, /** * Autoscaling is used. The image will only be scaled down to the size of the * component, keeping its aspect ratio. If the component is larger than the image * though, it will not be magnified. */ REDUCE, /** * Autoscaling is used. The image will be scaled to always fill as much as * possible of the component, keeping its aspect ratio. */ FULL } private static final int CBSIZE = 10; // Checkboard size in pixel private float zoom = 1.0f; private boolean checkboard = false; private Color cbcolor = null; private Autoscale autoscale = Autoscale.OFF; private Image image = null; private Quality quality = Quality.DEFAULT; private transient int rectX, rectY, mouseX, mouseY; private transient Cursor oldCursor; /** * Creates an empty {@link JImageViewer}. */ public JImageViewer() { init(); } /** * Creates a {@link JImageViewer} showing the given {@link Image}. * * @param image * {@link Image} to be shown. */ public JImageViewer(Image image) { init(); setImage(image); } /** * Creates a {@link JImageViewer} showing the given {@link ImageIcon}. * * @param icon * {@link ImageIcon} to be shown. */ public JImageViewer(ImageIcon icon) { init(); setImage(icon); } /** * Creates a {@link JImageViewer} showing an image that is read from the given * {@link InputStream}. * * @param is * {@link InputStream} to read the image data from. * @throws IOException * if the stream could not be read. */ public JImageViewer(InputStream is) throws IOException { init(); setImage(is); } /** * Creates a {@link JImageViewer} showing an image that is read from the given * {@link URL}. * * @param url * {@link URL} to read the image data from. */ public JImageViewer(URL url) { init(); setImage(url); } private void init() { MouseListener lMouse = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { final Rectangle rv = getVisibleRect(); // --- Remember the rectangle when the mouse was pressed --- rectX = rv.x; rectY = rv.y; // --- Remember the mouse position relative to it --- mouseX = e.getX() - rv.x; mouseY = e.getY() - rv.y; // --- Set the cursor --- oldCursor = getCursor(); if (rv.width < getWidth() || rv.height < getHeight()) { setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); } } @Override public void mouseReleased(MouseEvent e) { // --- Restore the cursor --- setCursor(oldCursor); } }; MouseMotionListener lMotion = new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent e) { Rectangle rv = new Rectangle(getVisibleRect()); // --- Compute the mouse delta movement --- // This are the number of pixels the mouse has been moved // on the viewport. int dX = mouseX - (e.getX() - rv.x); int dY = mouseY - (e.getY() - rv.y); // --- Compute the new rectangle --- // This is the position of the rectangle when the mouse was // clicked, added by the delta mouse move since then. rv.x = rectX + dX; rv.y = rectY + dY; // --- Make visible --- ((JComponent) e.getSource()).scrollRectToVisible(rv); } }; addMouseListener(lMouse); addMouseMotionListener(lMotion); } /** * Sets a new {@link Image} to be shown. * * @param img * New {@link Image} to be shown. */ public void setImage(Image img) { firePropertyChange("image", this.image, img); this.image = img; revalidate(); repaint(); } /** * Sets a new {@link ImageIcon} to be shown. This is a convenience method that will * just invoke {@link ImageIcon#getImage()}. * * @param icon * New {@link ImageIcon} to be shown. */ public void setImage(ImageIcon icon) { setImage(icon.getImage()); } /** * Sets a new image that is to be read from the {@link InputStream}. No fancy image * formats are supported, just the one that are supported by {@link ImageIO}. * * @param in * {@link InputStream} providing the image data. * @throws IOException * if the image could not be read. */ public void setImage(InputStream in) throws IOException { setImage(ImageIO.read(in)); } /** * Sets a new image that is to be read from the {@link URL}. No fancy image formats * are supported, just the one that are supported by {@link ImageIO}. * * @param url * {@link URL} to read the image from. */ public void setImage(URL url) { setImage(Toolkit.getDefaultToolkit().createImage(url)); } /** * Gets the {@link Image} that is currently set. * * @return Current {@link Image}, or {@code null} if none was set. */ public Image getImage() { return image; } /** * Draws the transparency checkboard. The checkboard is drawn behind the image and can * be seen through the transparent parts of it. *

* By default, no transparency checkboard is shown since it slows down the performance * and might confuse the user. * * @param checkboard * {@code true}: show transparency checkboard. */ public void setCheckboard(boolean checkboard) { firePropertyChange("checkboard", this.checkboard, checkboard); this.checkboard = checkboard; repaint(); } /** * Checks if the transparency checkboard is to be drawn. * * @return {@code true}: show transparency checkboard. */ public boolean isCheckboard() { return checkboard; } /** * Sets the transparency checkboard color. By default (or if {@code null} is passed in * here), the background color's {@link Color#brighter()} is used for the bright * fields. For the dark fields, {@link #getBackground()} is always used if this * component is opaque. * * @param color * Checkboard {@link Color}, {@code null} means default color. */ public void setCheckboardColor(Color color) { firePropertyChange("checkboardColor", this.cbcolor, color); this.cbcolor = color; repaint(); } /** * Gets the transparency checkboard color. * * @return Checkboard {@link Color}, or {@code null} if the default color is * used. */ public Color getCheckboardColor() { return cbcolor; } /** * Sets the autoscale mode. The default is {@link Autoscale#OFF}, so you can embed a * {@link JImageViewer} into a {@link JScrollPane}. * * @param autoscale * The new {@link Autoscale} mode. */ public void setAutoscale(Autoscale autoscale) { if (this.autoscale != autoscale) { firePropertyChange("autoscale", this.autoscale, autoscale); this.autoscale = autoscale; revalidate(); repaint(); } } /** * Gets the current autoscale mode. * * @return Current autoscale mode. */ public Autoscale getAutoscale() { return autoscale; } /** * Sets the zoom factor for the shown image. A factor of 1.0f means that the image is * shown in the original size. A factor <1.0f will reduce the image, while a factor * >1.0f will magnify it. *

* NOTE that {@link Autoscale#OFF} must be set in order to use zooming. This * might change in future releases though. * * @param zoom * Zoom factor. Default is 1.0f. */ public void setZoomFactor(float zoom) { if (this.zoom != zoom) { firePropertyChange("zoom", new Float(this.zoom), new Float(zoom)); this.zoom = zoom; revalidate(); repaint(); } } /** * Gets the current zoom factor. * * @return Current zoom factor. */ public float getZoomFactor() { return zoom; } /** * Sets the scaling quality. * * @param quality * New scaling quality. */ public void setQuality(Quality quality) { firePropertyChange("quality", this.quality, quality); this.quality = quality; repaint(); } /** * Gets the current scaling quality. * * @return Current scaling quality. */ public Quality getQuality() { return quality; } /** * Gets the final {@link Dimension} of the image after proper scaling was applied. * * @return {@link Dimension} of the image to be drawn. The returned {@link Dimension} * object is a copy that can be manipulated by the caller. */ protected Dimension getScaledDimension() { // --- Return null if there is no image --- if (image == null) return null; // --- Get the default dimensions --- Dimension dim = new Dimension(image.getWidth(this), image.getHeight(this)); if (autoscale != Autoscale.OFF) { // --- Automatically scale the image --- final Dimension imgDim = new Dimension(image.getWidth(this), image.getHeight(this)); final Dimension cmpDim = new Dimension(getSize()); final Insets insets = getInsets(); cmpDim.width -= insets.left + insets.right; cmpDim.height -= insets.top + insets.bottom; if (autoscale == Autoscale.REDUCE) { dim = SwingUtils.scaleAspect(imgDim, cmpDim); } else { dim = SwingUtils.scaleAspectMax(imgDim, cmpDim); } } else if (zoom != 1.0f) { // --- Scale the picture according to the zoom --- dim.width = Math.round(dim.width * zoom); dim.height = Math.round(dim.height * zoom); } return dim; } @Override public Dimension getMinimumSize() { if (image != null && autoscale == Autoscale.OFF) { Dimension dim = getScaledDimension(); Insets insets = getInsets(); dim.width += insets.left + insets.right; dim.height += insets.top + insets.bottom; return dim; } else { return super.getMinimumSize(); } } @Override public Dimension getPreferredSize() { if (image != null) { return getMinimumSize(); } else { return super.getPreferredSize(); } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (image != null) { final Graphics2D g2d = (Graphics2D) g.create(); final Dimension dim = getScaledDimension(); final Insets insets = getInsets(); // --- Translate to the centre --- g2d.translate( ((getWidth() - insets.left - insets.right - dim.width) / 2) + insets.left, ((getHeight() - insets.top - insets.bottom - dim.height) / 2) + insets.top); g2d.clipRect(0, 0, dim.width, dim.height); // --- Draw the cbcolor --- if (checkboard) { g2d.setColor(cbcolor != null ? cbcolor : getBackground().brighter()); for (int x = 0; x <= (dim.width / CBSIZE); x++) { for (int y = 0; y <= (dim.height / CBSIZE); y++) { if (x % 2 == y % 2) { g2d.fillRect(x * CBSIZE, y * CBSIZE, CBSIZE - 1, CBSIZE - 1); } } } } // --- Set scaling quality --- setQuality(g2d, quality); // --- Draw the image --- g2d.drawImage(image, 0, 0, dim.width, dim.height, this); // --- We're done --- g2d.dispose(); } } @Override public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) { final Graphics2D g2d = (Graphics2D) graphics; if (pageIndex != 0) { return Printable.NO_SUCH_PAGE; } // --- Do nothing if there is no image --- if (image == null) { return Printable.PAGE_EXISTS; } int imgW = image.getWidth(this); int imgH = image.getHeight(this); if (imgW == 0 || imgH == 0) { return Printable.PAGE_EXISTS; } // --- Set Scaling and Clipping --- double scaleW = pageFormat.getImageableWidth() / imgW; double scaleH = pageFormat.getImageableHeight() / imgH; double scale = Math.min(scaleW, scaleH); g2d.scale(scale, scale); g2d.setClip( (int) (pageFormat.getImageableX() / scale), (int) (pageFormat.getImageableY() / scale), (int) (pageFormat.getImageableWidth() / scale), (int) (pageFormat.getImageableHeight() / scale)); // --- Translate --- g2d.translate(g2d.getClipBounds().getX(), g2d.getClipBounds().getY()); // --- Set scaling quality --- setQuality(g2d, quality); // --- Draw the image --- g2d.drawImage(image, 0, 0, this); return Printable.PAGE_EXISTS; } /** * Sets the rendering hints according to the quality. * * @param g2d * {@link Graphics2D} to set the rendering hints. * @param quality * {@link Quality} desired. * @since R13 */ protected void setQuality(Graphics2D g2d, Quality quality) { switch (quality) { case FAST: g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)); g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED)); g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED)); break; case SMOOTH: g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)); g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED)); g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)); break; case BEST: g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC)); g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)); g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)); g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); break; default: throw new IllegalArgumentException("Unknown quality " + quality.name()); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy