diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 941a3f1609..fc418a005f 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -35,7 +35,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; -import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.SortedSet; import java.util.TreeSet; @@ -347,22 +346,14 @@ public class ImageUtils { public static BufferedImage getThumbnail(Content content, int iconSize) { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; - // If a thumbnail file is already saved locally - File cacheFile = getCachedThumbnailLocation(content.getId()); - if (cacheFile.exists()) { - try { - BufferedImage thumbnail = ImageIO.read(cacheFile); - if (isNull(thumbnail) || thumbnail.getWidth() != iconSize) { - return generateAndSaveThumbnail(file, iconSize, cacheFile); - } else { - return thumbnail; - } - } catch (Exception ex) { - LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: {1}", new Object[]{content.getName(), ex.getLocalizedMessage()}); //NON-NLS //NOI18N - return generateAndSaveThumbnail(file, iconSize, cacheFile); - } - } else { - return generateAndSaveThumbnail(file, iconSize, cacheFile); + + Task thumbnailTask = newGetThumbnailTask(file, iconSize, true); + thumbnailTask.run(); + try { + return SwingFXUtils.fromFXImage(thumbnailTask.get(), null); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.WARNING, "Failed to get thumbnail for {0}: " + ex.toString(), getContentPathSafe(content)); + return DEFAULT_THUMBNAIL; } } else { return DEFAULT_THUMBNAIL; @@ -513,87 +504,6 @@ public class ImageUtils { return fileHeaderBuffer; } - /** - * Generate an icon and save it to specified location. - * - * @param file File to generate icon for - * @param iconSize size in pixels of the thumbnail - * @param cacheFile Location to save thumbnail to - * - * @return Generated icon or null on error - */ - private static BufferedImage generateAndSaveThumbnail(AbstractFile file, int iconSize, File cacheFile) { - BufferedImage thumbnail = null; - try { - if (VideoUtils.isVideoThumbnailSupported(file)) { - if (openCVLoaded) { - thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize); - } else { - return DEFAULT_THUMBNAIL; - } - } else { - thumbnail = generateImageThumbnail(file, iconSize); - } - - if (thumbnail == null) { - return DEFAULT_THUMBNAIL; - - } else { - BufferedImage toSave = thumbnail; - imageSaver.execute(() -> { - try { - Files.createParentDirs(cacheFile); - if (cacheFile.exists()) { - cacheFile.delete(); - } - ImageIO.write(toSave, FORMAT, cacheFile); - } catch (IllegalArgumentException | IOException ex1) { - LOGGER.log(Level.WARNING, COULD_NOT_WRITE_CACHE_THUMBNAIL + file, ex1); - } - }); - } - } catch (NullPointerException ex) { - LOGGER.log(Level.WARNING, COULD_NOT_WRITE_CACHE_THUMBNAIL + file, ex); - } - return thumbnail; - } - - /** - * Generate and return a scaled image - * - * @param content - * @param iconSize - * - * @return a Thumbnail of the given content at the given size, or null if - * there was a problem. - */ - @Nullable - private static BufferedImage generateImageThumbnail(AbstractFile content, int iconSize) { - - try { - final ReadImageTask readImageTask = new ReadImageTask(content); - - readImageTask.run(); - BufferedImage bi = SwingFXUtils.fromFXImage(readImageTask.get(), null); - - if (bi == null) { - return null; - } - try { - return ScalrWrapper.resizeFast(bi, iconSize); - } catch (IllegalArgumentException e) { - // 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())); - } - } catch (OutOfMemoryError e) { - LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName() + ": " + e.toString()); //NON-NLS //NOI18N - } catch (Exception e) { - LOGGER.log(Level.WARNING, "ImageIO could not load image " + content.getName() + ": " + e.toString()); //NON-NLS //NOI18N - } - return null; - } - /** * Get the width of the given image, in pixels. * @@ -711,8 +621,8 @@ public class ImageUtils { * * @return a new Task that returns a thumbnail as its result. */ - public static Task newGetThumbnailTask(AbstractFile file, int iconSize) { - return new GetThumbnailTask(file, iconSize); + public static Task newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) { + return new GetThumbnailTask(file, iconSize, defaultOnFailure); } /** @@ -724,15 +634,17 @@ public class ImageUtils { private final int iconSize; private final File cacheFile; + private final boolean defaultOnFailure; @NbBundle.Messages({"# {0} - file name", "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}", "# {0} - file name", "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"}) - private GetThumbnailTask(AbstractFile file, int iconSize) { + private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) { super(file); updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName())); this.iconSize = iconSize; - cacheFile = getCachedThumbnailLocation(file.getId()); + this.defaultOnFailure = defaultOnFailure; + this.cacheFile = getCachedThumbnailLocation(file.getId()); } @Override @@ -755,8 +667,10 @@ public class ImageUtils { if (openCVLoaded) { updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName())); thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize); - } else { + } else if (defaultOnFailure) { thumbnail = DEFAULT_THUMBNAIL; + } else { + throw new IIOException("Failed to read image for thumbnail generation."); } } else { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java index a6a116102f..4392f59e2d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java @@ -191,7 +191,7 @@ public enum ThumbnailCache { } }; } - final Task newGetThumbnailTask = ImageUtils.newGetThumbnailTask(file.getAbstractFile(), MAX_THUMBNAIL_SIZE); + final Task newGetThumbnailTask = ImageUtils.newGetThumbnailTask(file.getAbstractFile(), MAX_THUMBNAIL_SIZE, false); newGetThumbnailTask.stateProperty().addListener((Observable observable) -> { switch (newGetThumbnailTask.getState()) { case SUCCEEDED: diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 0f8db63ae3..fb08256f83 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; +import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -281,7 +282,30 @@ public abstract class DrawableFile extends AbstractFile } } - public abstract Task getReadFullSizeImageTask(); + public Task getReadFullSizeImageTask() { + Image image = (imageRef != null) ? imageRef.get() : null; + if (image == null || image.isError()) { + Task readImageTask = getReadFullSizeImageTaskHelper(); + readImageTask.stateProperty().addListener(stateProperty -> { + switch (readImageTask.getState()) { + case SUCCEEDED: + try { + imageRef = new SoftReference<>(readImageTask.get()); + } catch (InterruptedException | ExecutionException exception) { + LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe()); + } + break; + } + }); + return readImageTask; + } else { + return TaskUtils.taskFrom(() -> image); + } + } + + abstract String getMessageTemplate(Exception exception); + + abstract Task getReadFullSizeImageTaskHelper(); public void setAnalyzed(Boolean analyzed) { this.analyzed.set(analyzed); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java index a6c34647b7..4249fe3c24 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/ImageFile.java @@ -19,9 +19,6 @@ package org.sleuthkit.autopsy.imagegallery.datamodel; import java.io.IOException; -import java.lang.ref.SoftReference; -import java.util.concurrent.ExecutionException; -import javafx.beans.Observable; import javafx.concurrent.Task; import javafx.scene.image.Image; import javax.imageio.ImageIO; @@ -48,33 +45,13 @@ public class ImageFile extends DrawableFile { } @Override - public Task getReadFullSizeImageTask() { - Image image = (imageRef != null) ? imageRef.get() : null; - if (image == null || image.isError()) { - final Task newReadImageTask = ImageUtils.newReadImageTask(this.getAbstractFile()); - newReadImageTask.stateProperty().addListener((Observable observable) -> { - switch (newReadImageTask.getState()) { - case CANCELLED: - break; - case FAILED: - break; - case SUCCEEDED: - try { - imageRef = new SoftReference<>(newReadImageTask.get()); - } catch (InterruptedException | ExecutionException interruptedException) { - } - break; - } - }); - return newReadImageTask; - } else { - return new Task() { - @Override - protected Image call() throws Exception { - return image; - } - }; - } + String getMessageTemplate(final Exception exception) { + return "Failed to read image {0}: " + exception.toString(); + } + + @Override + Task getReadFullSizeImageTaskHelper() { + return ImageUtils.newReadImageTask(this.getAbstractFile()); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java index 4ba866144c..20724ef0bc 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java @@ -19,27 +19,26 @@ package org.sleuthkit.autopsy.imagegallery.datamodel; import com.google.common.io.Files; -import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.lang.ref.SoftReference; import java.nio.file.Paths; -import java.util.concurrent.ExecutionException; -import javafx.beans.Observable; import javafx.concurrent.Task; -import javafx.embed.swing.SwingFXUtils; import javafx.scene.image.Image; import javafx.scene.media.Media; import javafx.scene.media.MediaException; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.sleuthkit.autopsy.coreutils.ImageUtils; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.VideoUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; public class VideoFile extends DrawableFile { + private static final Logger LOGGER = Logger.getLogger(VideoFile.class.getName()); + private static final Image VIDEO_ICON = new Image("org/sleuthkit/autopsy/imagegallery/images/Clapperboard.png"); VideoFile(T file, Boolean analyzed) { @@ -50,44 +49,16 @@ public class VideoFile extends DrawableFile { return VIDEO_ICON; } + + @Override - public Task getReadFullSizeImageTask() { - Image image = (imageRef != null) ? imageRef.get() : null; - if (image == null || image.isError()) { - Task newReadImageTask = new Task() { + String getMessageTemplate(final Exception exception) { + return "Failed to get image preview for video {0}: " + exception.toString(); + } - @Override - protected Image call() throws Exception { - final BufferedImage bufferedImage = ImageUtils.getThumbnail(getAbstractFile(), 1024); - return (bufferedImage == ImageUtils.getDefaultThumbnail()) - ? null - : SwingFXUtils.toFXImage(bufferedImage, null); - } - }; - - newReadImageTask.stateProperty().addListener((Observable observable) -> { - switch (newReadImageTask.getState()) { - case CANCELLED: - break; - case FAILED: - break; - case SUCCEEDED: - try { - imageRef = new SoftReference<>(newReadImageTask.get()); - } catch (InterruptedException | ExecutionException interruptedException) { - } - break; - } - }); - return newReadImageTask; - } else { - return new Task() { - @Override - protected Image call() throws Exception { - return image; - } - }; - } + @Override + Task getReadFullSizeImageTaskHelper() { + return ImageUtils.newGetThumbnailTask(getAbstractFile(), 1024, false); } private SoftReference mediaRef; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java new file mode 100644 index 0000000000..7b648b5db7 --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java @@ -0,0 +1,40 @@ +/* + * 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. + */ +package org.sleuthkit.autopsy.imagegallery.utils; + +import java.util.concurrent.Callable; +import javafx.concurrent.Task; + +/** + * + */ +public class TaskUtils { + + public static Task taskFrom(Callable callable) { + return new Task() { + @Override + protected T call() throws Exception { + return callable.call(); + } + }; + } + + private TaskUtils() { + } +}