From 5e375dd557d3838594f8d11e557db641e7e64df1 Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 13 Jul 2015 12:28:04 -0400 Subject: [PATCH] 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 --- .../DataContentViewerMedia.java | 4 +- .../corecomponents/MediaViewImagePanel.css | 28 +++++ .../corecomponents/MediaViewImagePanel.java | 55 +++++----- .../autopsy/coreutils/ImageUtils.java | 102 +++++++++--------- .../sleuthkit/autopsy/datamodel/FileNode.java | 6 +- 5 files changed, 115 insertions(+), 80 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.css diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index 0090cb47db..08fc169d49 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -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 diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.css b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.css new file mode 100644 index 0000000000..8cd592b28b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.css @@ -0,0 +1,28 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit 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; +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java index 792621e8e2..36e3bf9812 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -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 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); diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 78aee5b3a9..00ff448a76 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -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 getSupportedExtensions() { - return Collections.unmodifiableList(SUPP_EXTENSIONS); - } - - public static SortedSet getSupportedMimeTypes() { - return Collections.unmodifiableSortedSet(SUPP_MIME_TYPES); - } + //initialized lazily + private static FileTypeDetector fileTypeDetector; private static final List SUPP_EXTENSIONS; private static final TreeSet 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 getSupportedExtensions() { + return Collections.unmodifiableList(SUPP_EXTENSIONS); + } + + public static SortedSet 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 - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index ef7d82e4b8..47f2c30940 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -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 { // 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 } }