/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2009-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* 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.geotoolkit.gui.swing.image;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import javax.imageio.IIOException;
import javax.imageio.ImageReader;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.event.IIOReadProgressListener;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.EventQueue;
import java.awt.Dimension;
import javax.swing.JList;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JFileChooser;
import javax.swing.SwingWorker;
import javax.swing.DefaultListModel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import org.geotoolkit.nio.IOUtilities;
import org.opengis.coverage.grid.RectifiedGrid;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.geotoolkit.image.io.NamedImageStore;
import org.geotoolkit.image.io.metadata.MetadataHelper;
import org.geotoolkit.image.io.metadata.SampleDimension;
import org.geotoolkit.image.io.metadata.SpatialMetadata;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.internal.image.io.Formats;
import org.geotoolkit.internal.swing.SwingUtilities;
import org.geotoolkit.internal.swing.ExceptionMonitor;
import org.geotoolkit.resources.Vocabulary;
import org.geotoolkit.lang.Debug;
/**
* A panel showing the properties of an image <u>file</u>. This is different than
* {@link ImageProperties}, which shows the properties of a {@code RenderedImage}.
* The panel contains the following tabs:
* <p>
* <ul>
* <li>A summary with informations about the {@linkplain java.awt.image.ColorModel color model},
* {@linkplain java.awt.image.SampleModel sample model}, image size, tile size, <i>etc.</i></li>
* <li>The image metadata, as provided by {@link IIOMetadataPanel}.</li>
* <li>An overview of the image, as provided by {@link ImagePane}.</li>
* </ul>
* <p>
* All {@code setImage} methods defined in this class may be slow because they involve I/O
* operations. It is recommended to invoke them from an other thread than the Swing thread.
*
* {@section Using this component together with a FileChooser}
* This component can be registered to a {@link JFileChooser} for listening to change events.
* When the file selection change, the {@link #propertyChange(PropertyChangeEvent)} method is
* automatically invoked. The default implementation invokes in turn {@link #setImage(File)} in
* a background thread. This allows this {@code ImageFileProperties} to be updated automatically
* when the user selection changed. Example:
*
* {@preformat java
* ImageFileChooser chooser = new ImageFileChooser("png");
* ImageFileProperties properties = new ImageFileProperties();
* chooser.addPropertyChangeListener(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY, properties);
* //
* // Add the FileChooser and ImageFileProperties to some panel with the layout constraints
* // of your choice. The example below uses BorderLayout in such a way that, when resizing
* // the panel, only the properties pane is resized.
* //
* JPanel panel = new JPanel(new BorderLayout());
* panel.add(this, BorderLayout.WEST);
* panel.add(properties, BorderLayout.CENTER);
* }
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.14
*
* @see ImageProperties
* @see ImageFileChooser
*
* @since 3.05
* @module
*/
@SuppressWarnings("serial")
public class ImageFileProperties extends ImageProperties implements PropertyChangeListener {
/**
* The preferred size of thumbnail.
*/
private Dimension preferredThumbnailSize = new Dimension(256, 256);
/**
* The panel for image I/O metadata.
*/
private final IIOMetadataPanel metadata;
/**
* The warnings.
*/
private final DefaultListModel<Object> warnings;
/**
* The index of the warning tab. Used in order to change the enabled
* or disabled status of that tab.
*/
private final int warningsTab;
/**
* If a worker is currently running, that worker. This is used by {@link #propertyChange}
* (invoked when a new file is selected in the file chooser) for canceling a running action
* before to start a new one. This is also used as progress and warning listeners to be
* registered to the image reader.
* <p>
* This field shall be read and set in the Swing thread only.
*/
private transient Worker worker;
/**
* Creates a new instance of {@code ImageFileProperties} with no image.
* One of {@link #setImage(File) setImage(...)} methods must be
* invoked in order to set the properties source.
*/
public ImageFileProperties() {
this(new IIOMetadataPanel());
}
/**
* Creates a new instance of {@code ImageFileProperties} initialized to the given file.
*
* @param file The image file.
* @throws IOException If the file is not found, or no suitable image reader is found for the
* given file, or if an error occurred while reading the metadata or the thumbnails.
*/
public ImageFileProperties(final File file) throws IOException {
this();
setImage(file);
}
/**
* Creates a new instance using the given metadata panel.
*/
private ImageFileProperties(final IIOMetadataPanel metadata) {
super(metadata);
this.metadata = metadata;
warningsTab = tabs.getTabCount();
warnings = new DefaultListModel<>();
final Vocabulary resources = Vocabulary.getResources(getLocale());
final JComponent list = new JScrollPane(new JList<>(warnings));
list.setOpaque(false);
tabs.addTab(resources.getString(Vocabulary.Keys.Warning), list);
tabs.setEnabledAt(warningsTab, false);
}
/**
* Processes the messages sent by the background process. This method shall be invoked
* in the Swing thread only. The method behavior depends on the object type:
* <p>
* <ul>
* <li>{@link Boolean#TRUE} means that the reading process started.</li>
* <li>{@link Boolean#FALSE} means that the reading process finished or has been canceled.</li>
* <li>{@link File} gives the filename to write in the label above the progress.</li>
* <li>{@link Integer} are progress as a percentage between 0 and 100.</li>
* <li>{@link Strings} are warnings.</li>
* </ul>
*/
final void processBackgroundMessages(final Worker caller, final List<?> chunks) {
if (caller != worker) {
return;
}
final ImagePane viewer = this.viewer;
boolean hasWarnings = false;
for (final Object chunk : chunks) {
if (chunk instanceof Worker) {
String label = IOUtilities.filename(((Worker) chunk).input);
label = Vocabulary.getResources(getLocale()).getString(Vocabulary.Keys.Loading_1, label);
viewer.setProgressLabel(label);
viewer.setProgress(0);
} else if (chunk instanceof Boolean) {
viewer.setProgressVisible((Boolean) chunk);
} else if (chunk instanceof Integer) {
viewer.setProgress((Integer) chunk);
} else {
warnings.addElement(chunk);
if (!hasWarnings) {
tabs.setEnabledAt(warningsTab, true);
hasWarnings = true;
}
}
}
}
/**
* Clears the panels except the warnings one. We do not clear the warnings panel because
* it is the result of the reading process we just finished and we want to show them.
*/
@Override
final void clear() {
super.clear();
metadata.clear();
}
/**
* Returns {@code true} if the running task has been cancelled.
*
* @param worker The worker which was set when the task has begun.
*/
static boolean isCancelled(final Worker worker) {
return worker != null && worker.isCancelled();
}
/**
* Sets the specified {@linkplain ImageReader image reader} as the source of metadata
* and thumbnails, reading the information at the given image index. The information
* are extracted immediately and no reference to the given image reader is retained.
*
* {@section Resources management}
* This method does <strong>not</strong> close the
* {@linkplain javax.imageio.stream.ImageInputStream Image Input Stream} (if any).
* It is caller responsibility to dispose reader resources (including closing the
* input stream) after this method call, if desired.
*
* {@section Thread safety}
* This method can be invoked from any thread. Actually it is recommended to invoke
* it from an other thread than the <cite>Swing</cite> one.
*
* @param reader The image reader from which to read the informations.
* @param imageIndex The index of the image to read (usually 0).
* @throws IOException If an error occurred while reading the metadata or the thumbnails.
*
* @since 3.07
*/
public void setImage(final ImageReader reader, final int imageIndex) throws IOException {
final AtomicReference<Worker> ref = new AtomicReference<>();
SwingUtilities.invokeAndWait(new Runnable() {
@Override public void run() {
ref.set(worker);
warnings.clear();
tabs.setEnabledAt(warningsTab, false);
}
});
final Worker worker = ref.get();
/*
* Read only the metadata, and refresh the information panel as soon as those metadata
* are available. Note that the list of metadata may contains null elements, which are
* necessary in order to have metadata for image 'i' stored in the list at index 'i'.
*/
final List<String> imageNames = (reader instanceof NamedImageStore) ?
((NamedImageStore) reader).getImageNames() : null;
final IIOMetadata streamMetadata = reader.getStreamMetadata();
final List<IIOMetadata> imageMetadata = new ArrayList<>();
for (int i=0; i<imageIndex; i++) {
if (isCancelled(worker)) return;
imageMetadata.add(reader.getImageMetadata(i));
}
if (isCancelled(worker)) return;
final Info info = new Info(reader, imageIndex);
imageMetadata.add(info.metadata);
/*
* Update the Swing widget in the Swing thread. Note that we need to take a snapshot
* of the list content - do not pass the list directly, because it is not thread safe.
*/
final IIOMetadata[] snapshot = imageMetadata.toArray(new IIOMetadata[imageMetadata.size()]);
EventQueue.invokeLater(new Runnable() {
@Override public void run() {
if (!isCancelled(worker)) {
info.show(ImageFileProperties.this);
metadata.imageNames = imageNames;
try {
metadata.setMetadata(streamMetadata, snapshot);
} finally {
metadata.imageNames = null;
}
}
}
});
/*
* Read the thumbnail or the image now.
*/
if (isCancelled(worker)) return;
final BufferedImage thumbnail = info.readThumbnail(reader, imageIndex, preferredThumbnailSize);
EventQueue.invokeLater(new Runnable() {
@Override public void run() {
if (!isCancelled(worker)) {
viewer.setImage(thumbnail);
}
}
});
/*
* Read metadata for the remaining images, if any. If the number of image is unknown,
* we will read every image until we get an IndexOutOfBoundsException. Otherwise such
* exception will be considered as an error.
*/
int index = imageIndex;
boolean hasMore = false;
final int numImages = reader.getNumImages(false);
while (++index < numImages || numImages < 0) {
if (isCancelled(worker)) return;
final IIOMetadata md;
try {
md = reader.getImageMetadata(index);
} catch (IndexOutOfBoundsException e) {
if (numImages >= 0) {
// This exception is unexpected if the number of images was known.
throw new IIOException(e.getLocalizedMessage(), e);
}
break;
}
imageMetadata.add(md);
hasMore |= (md != null);
}
/*
* Set metadata for the remaining images, if any. We need to set the previous
* content of the list to 'null' in order to avoid adding the same metadata twice.
*/
if (hasMore) {
for (int i=0; i<=imageIndex; i++) {
imageMetadata.set(i, null);
}
final IIOMetadata[] md = imageMetadata.toArray(new IIOMetadata[imageMetadata.size()]);
EventQueue.invokeLater(new Runnable() {
@Override public void run() {
if (!isCancelled(worker)) {
metadata.imageNames = imageNames;
try {
metadata.addMetadata(null, md);
} finally {
metadata.imageNames = null;
}
}
}
});
}
}
/**
* Sets the specified {@linkplain ImageReader image reader} as the source of metadata
* and thumbnails. The information are extracted immediately and no reference to the
* given image reader is retained.
* <p>
* The default implementation delegates to {@link #setImage(ImageReader, int)} with
* an <cite>image index</cite> of 0. Subclasses should override this method if they
* want to choose automatically an image index depending on the format and the input.
*
* {@section Thread safety}
* This method can be invoked from any thread. Actually it is recommended to invoke
* it from an other thread than the <cite>Swing</cite> one.
*
* @param reader The image reader from which to read the informations.
* @throws IOException If an error occurred while reading the metadata or the thumbnails.
*/
public void setImage(final ImageReader reader) throws IOException {
setImage(reader, 0);
}
/**
* Sets the specified {@linkplain File file} as the source of metadata and thumbnails.
* This is the method which is invoked in a background thread when a {@link JFileChooser}
* is associated to this widget as described in the <a href="#overview">class javadoc</a>.
* The default implementation delegates to {@link #setImageInput(Object)}, but subclasses
* can override if they want to perform additional processing.
*
* {@section Thread safety}
* This method can be invoked from any thread. Actually it is recommended to invoke
* it from an other thread than the <cite>Swing</cite> one.
*
* @param file The image file.
* @throws IOException If the file is not found, or no suitable image reader is found for the
* given file, or if an error occurred while reading the metadata or the thumbnails.
*/
public void setImage(final File file) throws IOException {
setImageInput(file);
}
/**
* Sets the specified image input as the source of metadata and thumbnails. This method gets
* or creates an {@link ImageReader} for the given input as below:
* <p>
* <u>
* <li>If the given input is an instance of {@link ImageReader}, then it is used
* directly. Note that this method does <strong>not</strong> close the image
* input stream after usage, as specified by {@link #setImage(ImageReader, int)}.</li>
* <li>Otherwise this method creates a temporary image readerĀ for the given input,
* passes it to {@link #setImage(ImageReader)}, then disposes the resources
* (reader and input stream).</li>
* </u>
* <p>
* Then, this method delegates to {@link #setImage(ImageReader)}.
*
* {@section Thread safety}
* This method can be invoked from any thread. Actually it is recommended to invoke
* it from an other thread than the <cite>Swing</cite> one.
*
* @param input The image input.
* @throws IOException If the file is not found, or no suitable image reader is found for the
* given input, or if an error occurred while reading the metadata or the thumbnails.
*/
public void setImageInput(final Object input) throws IOException {
final ReadInvoker invoker = new ReadInvoker();
SwingUtilities.invokeAndWait(invoker);
if (input instanceof ImageReader) {
invoker.read((ImageReader) input);
} else {
Formats.selectImageReader(input, invoker.locale, invoker);
}
}
/**
* Reads the metadata and thumbnails from the given image input in a background thread.
* This method delegates to the {@link #setImage(File)} method if the given input is a
* file, or to {@link #setImageInput(Object)} otherwise, from a background thread. If
* the operation fails, then the {@link IOException} is painted in the quicklook area.
* <p>
* <b>NOTE:</b> This method must be invoked from the <cite>Swing</cite> thread.
*
* @param input The image input, or {@code null} if none.
*
* @since 3.12
*/
public void setImageLater(final Object input) {
Worker w = worker;
if (w != null) {
worker = null;
w.cancel();
}
if (input == null) {
setImage((RenderedImage) null);
} else {
w = new Worker(input);
worker = w; // Must be before execute.
w.execute();
}
}
/**
* The task to be run by {@link ImageFileProperties#setImageInput(Object) in the caller
* (preferably a background) thread. The {@link Runnable} part is to be run in the Swing
* thread. The {@link Formats.ReadCall} part is to be run in the caller thread.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.14
*
* @since 3.05
* @module
*/
private final class ReadInvoker implements Runnable, Formats.ReadCall {
/**
* A copy of the {@link ImageFileProperties#worker} field,
* used in order to register listeners to the image reader.
*/
private Worker listener;
/**
* The locale of the enclosing widget.
*/
Locale locale;
/**
* Executed from the Swing thread in order to initialize {@link #listener} to the
* value of {@link ImageFileProperties#worker}. Note that this is run from a thread
* different than everything else declared in the {@code setImageInput} method body.
*/
@Override
public void run() {
listener = worker;
locale = getLocale();
}
/**
* Reads the image. This is run from the caller (preferably a background) thread.
* Note that if this method is called by {@link Formats#selectImageReader}, then
* it will be disposed automatically after the call. Otherwise (if this method is
* invoked directly), it is caller responsibility to dispose the reader.
*/
@Override
public void read(final ImageReader reader) throws IOException {
final Worker listener = this.listener;
if (listener != null) {
reader.addIIOReadWarningListener (listener);
reader.addIIOReadProgressListener(listener);
}
try {
setImage(reader);
} finally {
if (listener != null) {
reader.removeIIOReadProgressListener(listener);
reader.removeIIOReadWarningListener (listener);
}
}
}
/**
* Invoked when a recoverable error (not during image read) occurred.
*/
@Override
public void recoverableException(final Throwable error) {
Logging.recoverableException(null, ImageFileProperties.class, "setImageInput", error);
}
}
/**
* Returns the preferred size of the {@linkplain ImageReader#readThumbnail(int,int) thumbnail}.
* If a file contains more than one thumbnail, then the one having the closest size to this
* value will be selected.
*
* @return The preferred thumbnail size.
*/
public Dimension getPreferredThumbnailSize() {
return (Dimension) preferredThumbnailSize.clone();
}
/**
* Sets the preferred size of the {@linkplain ImageReader#readThumbnail(int,int) thumbnail}.
*
* @param size The new preferred thumbnail size.
*/
public void setPreferredThumbnailSize(final Dimension size) {
final Dimension old = preferredThumbnailSize;
if (!size.equals(old)) {
preferredThumbnailSize = new Dimension(size);
firePropertyChange("preferredThumbnailSize", old, size);
}
}
/**
* Invoked when the state of a {@link JFileChooser} (or any other component at caller choice)
* changed. If the event {@linkplain PropertyChangeEvent#getPropertyName() property name} is
* {@value javax.swing.JFileChooser#SELECTED_FILE_CHANGED_PROPERTY}, then this method invokes
* {@link #setImage(File)} in a background thread.
* <p>
* This method is invoked automatically when this {@code ImageFileProperties} is registered
* to a {@code JFileChooser} as an {@link PropertyChangeListener}. It can be invoked from
* the <cite>Swing</cite> thread only.
*
* @param event The property change event.
*/
@Override
public void propertyChange(final PropertyChangeEvent event) {
if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(event.getPropertyName())) {
final Object input = event.getNewValue();
if (input instanceof File) {
final File file = (File) input;
if (file.isFile()) {
setImageLater(file);
}
}
}
}
/**
* The worker thread which will fetch image properties in background.
* In the operation fails, the {@link IOException} is painted in the
* quicklook area.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.05
*
* @since 3.05
* @module
*/
private final class Worker extends SwingWorker<Object,Object>
implements IIOReadProgressListener, IIOReadWarningListener
{
/**
* The file to read.
*/
final Object input;
/**
* The image reader. Used only for cancelation.
*/
private volatile ImageReader reader;
/**
* Creates a new worker thread for reading the given image.
*/
Worker(final Object input) {
this.input = input;
}
/**
* Loads the image in a background thread, then refreshes
* the {@link ImageFileProperties} in the Swing thread.
*/
@Override
protected Object doInBackground() throws IOException {
publish(this); // A marker value for showing the input name in the widget.
if (input instanceof File) {
setImage((File) input);
} else {
setImageInput(input);
}
return null;
}
@Override public void sequenceStarted (ImageReader r, int image) {}
@Override public void imageStarted (ImageReader r, int image) {publish(Boolean.TRUE); reader=r;}
@Override public void thumbnailStarted (ImageReader r, int i, int t) {publish(Boolean.TRUE); reader=r;}
@Override public void imageProgress (ImageReader r, float percent) {publish(Math.round(percent));}
@Override public void thumbnailProgress(ImageReader r, float percent) {publish(Math.round(percent));}
@Override public void warningOccurred (ImageReader r, String warning) {publish(warning);}
@Override public void readAborted (ImageReader r) {publish(Boolean.FALSE);}
@Override public void thumbnailComplete(ImageReader r) {publish(Boolean.FALSE);}
@Override public void imageComplete (ImageReader r) {publish(Boolean.FALSE);}
@Override public void sequenceComplete (ImageReader r) {}
/**
* Invoked from the Swing thread for processing the progress or the warnings.
*/
@Override
protected void process(final List<Object> chunks) {
processBackgroundMessages(this, chunks);
}
/**
* Cancels the current reading operation.
* This method can be invoked from any thread.
*/
final void cancel() {
final ImageReader reader = this.reader;
if (reader != null) {
reader.abort();
}
cancel(true);
}
/**
* Invoked in the Swing thread when the task is completed for
* cleaning the {@link ImageFileProperties#worker} reference.
*/
@Override
protected void done() {
if (worker == this) try {
get();
} catch (final InterruptedException | ExecutionException e) {
Throwable cause = e;
if (e instanceof ExecutionException) {
cause = e.getCause();
if (cause == null) {
cause = e;
}
}
setImage((RenderedImage) null);
processBackgroundMessages(this, Collections.singletonList(e.getLocalizedMessage()));
viewer.setError(cause);
} finally {
worker = null; // Must be last.
}
}
}
/**
* Informations about an image. An instance of this structure is created for each
* image in a stream.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.09
*
* @see ImageProperties
*
* @since 3.05
* @module
*/
private static final class Info {
/**
* The provider of the image reader. This information is not really image-specific,
* but it is cheap to keep a reference here anyway since the same instance will be
* shared by many {@code Info} structures.
*/
private final ImageReaderSpi provider;
/**
* The image size and tile size.
*/
private final int width, height, tileWidth, tileHeight;
/**
* The color and sample models.
*/
private final ImageTypeSpecifier type;
/**
* The image metadata, or {@code null} if none.
*/
final IIOMetadata metadata;
/**
* The coordinate reference system, or {@code null} if none.
*/
private CoordinateReferenceSystem crs;
/**
* The cell size as a string, or {@code null} if none.
*/
private String cellSize;
/**
* The range of valid geophysics values, or {@code null} if none.
*/
private NumberRange<?> valueRange;
/**
* Fetches the informations from the given image reader for the image at the given index.
* The thumbnail is not read yet; an explicit call to {@code readThumbnail} will be needed.
*/
Info(final ImageReader reader, final int index) throws IOException {
provider = reader.getOriginatingProvider();
width = reader.getWidth(index);
height = reader.getHeight(index);
tileWidth = reader.getTileWidth(index);
tileHeight = reader.getTileHeight(index);
type = reader.getRawImageType(index);
metadata = reader.getImageMetadata(index);
if (metadata instanceof SpatialMetadata) {
final SpatialMetadata sm = (SpatialMetadata) metadata;
sm.setReadOnly(true);
crs = sm.getInstanceForType(CoordinateReferenceSystem.class);
final RectifiedGrid rg = sm.getInstanceForType(RectifiedGrid.class);
final MetadataHelper helper = new MetadataHelper(sm);
if (rg != null) {
cellSize = helper.formatCellDimension(rg,
(crs != null) ? crs.getCoordinateSystem() : null);
}
final SampleDimension sd = sm.getInstanceForType(SampleDimension.class);
if (sd != null) {
valueRange = helper.getValidValues(sd);
}
}
}
/**
* Reads the thumbnail, or the full image if there is no thumbnail. The reader and index
* given to this method shall be the same than the ones given to the constructor.
*/
@SuppressWarnings("fallthrough")
final BufferedImage readThumbnail(final ImageReader reader, final int index,
final Dimension preferredThumbnailSize) throws IOException
{
BufferedImage thumbnail;
final int n = reader.getNumThumbnails(index);
int ti = 0;
switch (n) {
/*
* Search for the best thumbnail. Note that for any (n >= 2) cases, fetching the
* thumbnail width and height may be inefficient if the ImageReader in use didn't
* overridden the default getThumbnail[Width|Height] implementations. This is the
* raison why we skip the search if n=1.
*/
default: {
long best = Integer.MAX_VALUE;
for (int i=0; i<n; i++) {
final long dx = reader.getThumbnailWidth (index, i) - preferredThumbnailSize.width;
final long dy = reader.getThumbnailHeight(index, i) - preferredThumbnailSize.height;
long distance = dx*dx + dy*dy;
if (distance < best) {
best = distance;
ti = i;
}
}
// Fall through
}
case 1: {
thumbnail = reader.readThumbnail(index, ti);
break;
}
case 0: {
/*
* No thumbnail: read the image with a subsampling. Note that it may be a slow
* operation, but we are running this constructor in a background thread anyway.
*/
final int xSubsampling = Math.max(1, width / preferredThumbnailSize.width);
final int ySubsampling = Math.max(1, height / preferredThumbnailSize.height);
final ImageReadParam param = reader.getDefaultReadParam();
param.setSourceSubsampling(xSubsampling, ySubsampling, 0, 0);
thumbnail = reader.read(index, param);
break;
}
}
return thumbnail;
}
/**
* Shows the content of this {@code Info} object in the given properties pane.
*/
final void show(final ImageFileProperties properties) {
properties.setOperationDescription(provider);
properties.setImageDescription(
(type != null) ? type.getColorModel() : null,
(type != null) ? type.getSampleModel() : null,
width, height, tileWidth, tileHeight,
(width + tileWidth -1) / tileWidth,
(height + tileHeight-1) / tileHeight);
properties.setGeospatialDescription(crs, cellSize, valueRange);
if (!(metadata instanceof SpatialMetadata)) {
properties.setGeospatialDescription(false);
}
}
}
/**
* Shows the properties for the specified image file in a frame.
* This convenience method is mostly a helper for debugging purpose.
*
* @param image The image to display in a frame.
*/
@Debug
public static void show(final File image) {
JComponent c;
try {
c = new ImageFileProperties(image);
} catch (IOException e) {
ExceptionMonitor.show(null, e);
return;
}
SwingUtilities.show(c, image.getName());
}
}