/* * #%L * SCIFIO library for reading and converting scientific file formats. * %% * Copyright (C) 2011 - 2020 SCIFIO developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package io.scif.gui; import io.scif.FormatException; import io.scif.ImageMetadata; import io.scif.Plane; import io.scif.Reader; import io.scif.services.InitializeService; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.util.List; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import net.imagej.axis.CalibratedAxis; import org.scijava.Context; import org.scijava.io.location.FileLocation; import org.scijava.log.LogService; import org.scijava.plugin.Parameter; /** * PreviewPane is a panel for use as a JFileChooser accessory, displaying a * thumbnail for the selected image, loaded in a separate thread. */ public class PreviewPane extends JPanel implements PropertyChangeListener, Runnable { // -- Parameters -- @Parameter LogService logService; @Parameter InitializeService initializeService; // -- Fields -- /** Reader for use when loading thumbnails. */ private Reader reader; /** Current ID to load. */ private String loadId; /** Last ID loaded. */ private String lastId; /** Thumbnail loading thread. */ private Thread loader; /** Flag indicating whether loader thread should keep running. */ private boolean loaderAlive; /** Method for syncing the view to the model. */ private Runnable refresher; // -- Fields - view -- /** Labels containing thumbnail and dimensional information. */ private final JLabel iconLabel, formatLabel, resLabel, zctLabel, typeLabel; // -- Fields - model -- private ImageIcon icon; private String iconText, formatText, resText, npText, typeText; private String iconTip, formatTip, resTip, zctTip, typeTip; // -- Constructor -- /** Constructs a preview pane for the given file chooser. */ public PreviewPane(final Context context, final JFileChooser jc) { super(); context.inject(this); // create view setBorder(new EmptyBorder(0, 10, 0, 10)); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); iconLabel = new JLabel(); iconLabel.setMinimumSize(new java.awt.Dimension(128, -1)); iconLabel.setAlignmentX(Component.CENTER_ALIGNMENT); add(iconLabel); add(Box.createVerticalStrut(7)); formatLabel = new JLabel(); formatLabel.setAlignmentX(Component.CENTER_ALIGNMENT); add(formatLabel); add(Box.createVerticalStrut(5)); resLabel = new JLabel(); resLabel.setAlignmentX(Component.CENTER_ALIGNMENT); add(resLabel); zctLabel = new JLabel(); zctLabel.setAlignmentX(Component.CENTER_ALIGNMENT); add(zctLabel); typeLabel = new JLabel(); typeLabel.setAlignmentX(Component.CENTER_ALIGNMENT); add(typeLabel); // smaller font for most labels Font font = formatLabel.getFont(); font = font.deriveFont(font.getSize2D() - 3); formatLabel.setFont(font); resLabel.setFont(font); zctLabel.setFont(font); typeLabel.setFont(font); // populate model icon = null; iconText = formatText = resText = npText = typeText = ""; iconTip = formatTip = resTip = zctTip = typeTip = null; if (jc != null) { jc.setAccessory(this); jc.addPropertyChangeListener(this); refresher = new Runnable() { @Override public void run() { iconLabel.setIcon(icon); iconLabel.setText(iconText); iconLabel.setToolTipText(iconTip); formatLabel.setText(formatText); formatLabel.setToolTipText(formatTip); resLabel.setText(resText); resLabel.setToolTipText(resTip); zctLabel.setText(npText); zctLabel.setToolTipText(zctTip); typeLabel.setText(typeText); typeLabel.setToolTipText(typeTip); } }; // start separate loader thread loaderAlive = true; loader = new Thread(this, "Preview"); loader.start(); } } // -- Component API methods -- /* @see java.awt.Component#getPreferredSize() */ @Override public Dimension getPreferredSize() { final Dimension prefSize = super.getPreferredSize(); return new Dimension(148, prefSize.height); } // -- PropertyChangeListener API methods -- /** * Property change event, to listen for when a new file is selected, or the * file chooser closes. */ @Override public void propertyChange(final PropertyChangeEvent e) { final String prop = e.getPropertyName(); if (prop.equals("JFileChooserDialogIsClosingProperty")) { // notify loader thread that it should stop loaderAlive = false; } if (!prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) return; File f = (File) e.getNewValue(); if (f != null && (f.isDirectory() || !f.exists())) f = null; loadId = f == null ? null : f.getAbsolutePath(); } // -- Runnable API methods -- /** Thumbnail loading routine. */ @Override public void run() { while (loaderAlive) { try { Thread.sleep(100); } catch (final InterruptedException exc) { logService.info("", exc); } try { // catch-all for unanticipated exceptions final String id = loadId; if (id == lastId) continue; lastId = id; icon = null; iconText = id == null ? "" : "Reading..."; formatText = resText = npText = typeText = ""; iconTip = id; formatTip = resTip = zctTip = typeTip = ""; if (id == null) { SwingUtilities.invokeLater(refresher); continue; } try { reader = initializeService.initializeReader(new FileLocation(id)); reader.setNormalized(true); } catch (final FormatException exc) { logService.debug("Failed to initialize " + id, exc); final boolean badFormat = exc.getMessage().startsWith( "Unknown file format"); iconText = "Unsupported " + (badFormat ? "format" : "file"); formatText = resText = ""; SwingUtilities.invokeLater(refresher); lastId = null; continue; } catch (final IOException exc) { logService.debug("Failed to initialize " + id, exc); iconText = "Unsupported file"; formatText = resText = ""; SwingUtilities.invokeLater(refresher); lastId = null; continue; } if (id != loadId) { SwingUtilities.invokeLater(refresher); continue; } icon = new ImageIcon(makeImage("Loading...")); iconText = ""; final String format = reader.getFormat().getFormatName(); formatText = format; formatTip = format; final ImageMetadata iMeta = reader.getMetadata().get(0); resText = getText(iMeta, iMeta.getAxesPlanar()); npText = getText(iMeta, iMeta.getAxesNonPlanar()); SwingUtilities.invokeLater(refresher); // open middle image thumbnail final long planeIndex = iMeta.getPlaneCount() / 2; Plane thumbPlane = null; try { thumbPlane = reader.openPlane(0, planeIndex); } catch (FormatException | IOException exc) { logService.debug("Failed to read thumbnail #" + planeIndex + " from " + id, exc); } final BufferedImage thumb = AWTImageTools.openThumbImage(thumbPlane, reader, 0, iMeta.getAxesLengthsPlanar(), (int) iMeta.getThumbSizeX(), (int) iMeta.getThumbSizeY(), false); icon = new ImageIcon(thumb == null ? makeImage("Failed") : thumb); iconText = ""; SwingUtilities.invokeLater(refresher); } catch (final Exception exc) { logService.info("", exc); icon = null; iconText = "Thumbnail failure"; formatText = resText = npText = typeText = ""; iconTip = loadId; formatTip = resTip = zctTip = typeTip = ""; SwingUtilities.invokeLater(refresher); } } } private String getText(final ImageMetadata meta, final List<CalibratedAxis> axes) { String text = ""; for (final CalibratedAxis axis : axes) { if (text.length() > 0) text += " x "; text += meta.getAxisLength(axis) + " " + axis.type().getLabel(); } return text; } // -- PreviewPane API methods -- /** Closes the underlying image reader. */ public void close() throws IOException { if (reader != null) { reader.close(); } } // -- Helper methods -- /** * Creates a blank image with the given message painted on top (e.g., a * loading or error message), matching the size of the active reader's * thumbnails. */ private BufferedImage makeImage(final String message) { final ImageMetadata iMeta = reader.getMetadata().get(0); int w = (int) iMeta.getThumbSizeX(), h = (int) iMeta.getThumbSizeY(); if (w < 128) w = 128; if (h < 32) h = 32; final BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); final Graphics2D g = image.createGraphics(); final Rectangle2D.Float r = (Rectangle2D.Float) g.getFont().getStringBounds( message, g.getFontRenderContext()); g.drawString(message, (w - r.width) / 2, (h - r.height) / 2 + r.height); g.dispose(); return image; } }