minor improvements to new image support

restore isImageSupported code lost in merge
more cleanup in ImageUtils and MediaViewImagePanel
lazily instantiate static fileTypeDetector
better notification of errorsl loading images
special case to display iff images incorectly identifiead as audio/x-aiff
show image icon in result viewers for more files
This commit is contained in:
jmillman 2015-07-13 12:28:04 -04:00
parent dc65afc62b
commit 5e375dd557
5 changed files with 115 additions and 80 deletions

View File

@ -206,7 +206,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
}
}
}
return false;
}
@ -218,8 +218,8 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
* @return True if an image file that can be displayed
*/
private boolean isImageSupported(AbstractFile file) {
return ImageUtils.thumbnailSupported(file);
return ImageUtils.thumbnailSupported(file);
}
@Override

View File

@ -0,0 +1,28 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.label {
-fx-wrap-text:true;
-fx-text-fill: red;
-fx-font-size: 2em;
}
.bg {
-fx-background-color:black;
}

View File

@ -32,14 +32,11 @@ import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
@ -67,6 +64,10 @@ public class MediaViewImagePanel extends JPanel {
private ImageView fxImageView;
private BorderPane borderpane;
private final Label errorLabel = new Label("Could not load image file into media view.");
private final Label tooLargeLabel = new Label("Could not load image file into media view (too large).");
private final Label noReaderLabel = new Label("Image reader not found for file.");
/**
* mime types we shoul dbe able to display. if the mimetype is unknown we
* will fall back on extension (and jpg/png header
@ -79,7 +80,6 @@ public class MediaViewImagePanel extends JPanel {
static private final List<String> supportedExtensions = ImageUtils.getSupportedExtensions().stream()
.map("."::concat)
.collect(Collectors.toList());
/**
* Creates new form MediaViewImagePanel
@ -88,12 +88,15 @@ public class MediaViewImagePanel extends JPanel {
initComponents();
fxInited = org.sleuthkit.autopsy.core.Installer.isJavaFxInited();
if (fxInited) {
Platform.runLater(() -> { // build jfx ui (we could do this in FXML?)
Platform.runLater(() -> {
// build jfx ui (we could do this in FXML?)
fxImageView = new ImageView(); // will hold image
borderpane = new BorderPane(fxImageView); // centers and sizes imageview
borderpane.setBackground(new Background(new BackgroundFill(javafx.scene.paint.Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
borderpane.getStyleClass().add("bg");
fxPanel = new JFXPanel(); // bridge jfx-swing
Scene scene = new Scene(borderpane, javafx.scene.paint.Color.BLACK); //root of jfx tree
Scene scene = new Scene(borderpane); //root of jfx tree
scene.getStylesheets().add(MediaViewImagePanel.class.getResource("MediaViewImagePanel.css").toExternalForm());
fxPanel.setScene(scene);
//bind size of image to that of scene, while keeping proportions
@ -120,6 +123,7 @@ public class MediaViewImagePanel extends JPanel {
public void reset() {
Platform.runLater(() -> {
fxImageView.setImage(null);
borderpane.setCenter(null);
});
}
@ -144,41 +148,38 @@ public class MediaViewImagePanel extends JPanel {
@Override
public void run() {
if (!Case.isCaseOpen()) {
//handle in-between condition when case is being closed
//and an image was previously selected
/* handle in-between condition when case is being closed
* and an image was previously selected */
return;
}
final Image fxImage;
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
BufferedImage bufferedImage = ImageIO.read(inputStream);
if (bufferedImage == null) {
LOGGER.log(Level.WARNING, "Could image reader not found for file: {0}", file.getName()); //NON-NLS
return;
LOGGER.log(Level.WARNING, "Image reader not found for file: {0}", file.getName()); //NON-NLS
borderpane.setCenter(noReaderLabel);
} else {
Image fxImage = SwingFXUtils.toFXImage(bufferedImage, null);
if (fxImage.isError()) {
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), fxImage.getException()); //NON-NLS
borderpane.setCenter(errorLabel);
return;
} else {
fxImageView.setImage(fxImage);
borderpane.setCenter(fxImageView);
}
}
fxImage = SwingFXUtils.toFXImage(bufferedImage, null);
} catch (IllegalArgumentException | IOException ex) {
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), ex); //NON-NLS
return;
borderpane.setCenter(errorLabel);
} catch (OutOfMemoryError ex) {
LOGGER.log(Level.WARNING, "Could not load image file into media view (too large): " + file.getName(), ex); //NON-NLS
MessageNotifyUtil.Notify.warn(
NbBundle.getMessage(this.getClass(), "MediaViewImagePanel.imgFileTooLarge.msg", file.getName()),
ex.getMessage());
return;
borderpane.setCenter(tooLargeLabel);
}
if (fxImage.isError()) {
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), fxImage.getException()); //NON-NLS
return;
}
fxImageView.setImage(fxImage);
borderpane.setCenter(fxImageView);
SwingUtilities.invokeLater(() -> {
//show the panel after fully loaded
fxPanel.setVisible(true);

View File

@ -25,7 +25,7 @@ package org.sleuthkit.autopsy.coreutils;
import com.google.common.io.Files;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -49,6 +49,7 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector.FileTypeDetectorInitException;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ReadContentInputStream;
@ -63,20 +64,16 @@ public class ImageUtils {
private static final Logger LOGGER = Logger.getLogger(ImageUtils.class.getName());
/** save thumbnails to disk as this format */
private static final String FORMAT = "png";
private static final String FORMAT = "png"; //NON-NLS
public static final int ICON_SIZE_SMALL = 50;
public static final int ICON_SIZE_MEDIUM = 100;
public static final int ICON_SIZE_LARGE = 200;
private static final Image DEFAULT_ICON = new ImageIcon("/org/sleuthkit/autopsy/images/file-icon.png").getImage(); //NON-NLS
public static List<String> getSupportedExtensions() {
return Collections.unmodifiableList(SUPP_EXTENSIONS);
}
public static SortedSet<String> getSupportedMimeTypes() {
return Collections.unmodifiableSortedSet(SUPP_MIME_TYPES);
}
//initialized lazily
private static FileTypeDetector fileTypeDetector;
private static final List<String> SUPP_EXTENSIONS;
private static final TreeSet<String> SUPP_MIME_TYPES;
@ -90,11 +87,20 @@ public class ImageUtils {
SUPP_MIME_TYPES = new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes()));
SUPP_MIME_TYPES.addAll(Arrays.asList("image/x-ms-bmp", "application/x-123"));
SUPP_MIME_TYPES.removeIf("application/octet-stream"::equals);
}
private ImageUtils() {
}
public static List<String> getSupportedExtensions() {
return Collections.unmodifiableList(SUPP_EXTENSIONS);
}
public static SortedSet<String> getSupportedMimeTypes() {
return Collections.unmodifiableSortedSet(SUPP_MIME_TYPES);
}
/**
* Get the default Icon, which is the icon for a file.
*
@ -122,12 +128,12 @@ public class ImageUtils {
}
try {
String mimeType = new FileTypeDetector().getFileType(file);
String mimeType = getFileTypeDetector().getFileType(file);
if (Objects.nonNull(mimeType)) {
return SUPP_MIME_TYPES.contains(mimeType);
return SUPP_MIME_TYPES.contains(mimeType)
|| (mimeType.equalsIgnoreCase("audio/x-aiff") && "iff".equalsIgnoreCase(file.getNameExtension()));
}
} catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) {
LOGGER.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
if (!SUPP_MIME_TYPES.isEmpty()) {
AbstractFile.MimeMatchEnum mimeMatch = file.isMimeType(SUPP_MIME_TYPES);
@ -141,15 +147,29 @@ public class ImageUtils {
// if we have an extension, check it
final String extension = file.getNameExtension();
if (StringUtils.isNotBlank(extension)) {
if (SUPP_EXTENSIONS.contains(extension)) {
return true;
}
if (StringUtils.isNotBlank(extension) && SUPP_EXTENSIONS.contains(extension)) {
return true;
}
// if no extension or one that is not for an image, then read the content
return isJpegFileHeader(file) || isPngFileHeader(file);
}
/**
* lazily instantiates and returns a FileTypeDetector
*
* @return a FileTypeDetector
*
* @throws FileTypeDetectorInitException if a initializing the
* FileTypeDetector failed.
*/
synchronized private static FileTypeDetector getFileTypeDetector() throws FileTypeDetector.FileTypeDetectorInitException {
if (fileTypeDetector == null) {
fileTypeDetector = new FileTypeDetector();
}
return fileTypeDetector;
}
/**
* Get a thumbnail of a specified size. Generates the image if it is
* not already cached.
@ -281,21 +301,28 @@ public class ImageUtils {
/**
* Generate a thumbnail and save it to specified location.
*
* @param content File to generate icon for
* @param size the size of thumbnail to generate in pixels
* @param saveFile Location to save thumbnail to
* @param content File to generate icon for
* @param size the size of thumbnail to generate in pixels
* @param cacheFile Location to save thumbnail to
*
* @return Generated icon or a default icon if a thumbnail could not be
* made.
*/
private static Image generateAndSaveThumbnail(Content content, int size, File saveFile) {
BufferedImage thumbNail = generateThumbnail(content, size);
if (Objects.nonNull(thumbNail)) {
private static Image generateAndSaveThumbnail(Content content, int size, File cacheFile) {
BufferedImage thumbnail = generateThumbnail(content, size);
if (Objects.nonNull(thumbnail)) {
imageSaver.execute(() -> {
saveThumbnail(content, thumbNail, saveFile);
try {
Files.createParentDirs(cacheFile);
if (cacheFile.exists()) {
cacheFile.delete();
}
ImageIO.write(thumbnail, FORMAT, cacheFile);
} catch (IllegalArgumentException | IOException ex1) {
LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex1); //NON-NLS
}
});
return thumbNail;
return thumbnail;
} else {
return getDefaultIcon();
}
@ -313,8 +340,7 @@ public class ImageUtils {
@Nullable
private static BufferedImage generateThumbnail(Content content, int iconSize) {
try (InputStream inputStream = new ReadContentInputStream(content);) {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(content));) {
BufferedImage bi = ImageIO.read(inputStream);
if (bi == null) {
LOGGER.log(Level.WARNING, "No image reader for file: {0}", content.getName()); //NON-NLS
@ -323,7 +349,7 @@ public class ImageUtils {
try {
return ScalrWrapper.resizeFast(bi, iconSize);
} catch (IllegalArgumentException e) {
// if resizing does not work due to extremely small height/width ratio,
// if resizing does not work due to extreme aspect ratio,
// crop the image instead.
return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight()));
}
@ -335,24 +361,4 @@ public class ImageUtils {
return null;
}
}
/**
* save the generated thumbnail to disk in the cache folder with
* the obj_id as the name.
*
* @param file the file the given image is a thumbnail for
* @param thumbnail the thumbnail to save for the given DrawableFile
*/
static private void saveThumbnail(Content content, final RenderedImage thumbnail, File cacheFile) {
try {
Files.createParentDirs(cacheFile);
if (cacheFile.exists()) {
cacheFile.delete();
}
//convert back to swing to save
ImageIO.write(thumbnail, FORMAT, cacheFile);
} catch (IllegalArgumentException | IOException ex) {
LOGGER.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex); //NON-NLS
}
}
}

View File

@ -21,10 +21,10 @@ package org.sleuthkit.autopsy.datamodel;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Action;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.HashSearchAction;
@ -118,7 +118,7 @@ public class FileNode extends AbstractFsContentNode<AbstractFile> {
// Images
for (String s : FileTypeExtensions.getImageExtensions()) {
if (ext.equals(s)) {
if (ImageUtils.thumbnailSupported(file) || ext.equals(s)) {
return "org/sleuthkit/autopsy/images/image-file.png"; //NON-NLS
}
}