22 package org.sleuthkit.autopsy.coreutils;
24 import com.google.common.collect.ImmutableSortedSet;
25 import com.google.common.io.Files;
26 import java.awt.Image;
27 import java.awt.image.BufferedImage;
28 import java.io.BufferedInputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.nio.file.Paths;
33 import java.text.MessageFormat;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.Iterator;
38 import java.util.List;
39 import static java.util.Objects.nonNull;
40 import java.util.SortedSet;
41 import java.util.TreeSet;
42 import java.util.concurrent.ExecutionException;
43 import java.util.concurrent.Executor;
44 import java.util.concurrent.Executors;
45 import java.util.logging.Level;
46 import javafx.concurrent.Task;
47 import javafx.embed.swing.SwingFXUtils;
48 import javax.annotation.Nonnull;
49 import javax.annotation.Nullable;
50 import javax.imageio.IIOException;
51 import javax.imageio.ImageIO;
52 import javax.imageio.ImageReadParam;
53 import javax.imageio.ImageReader;
54 import javax.imageio.event.IIOReadProgressListener;
55 import javax.imageio.stream.ImageInputStream;
56 import org.apache.commons.lang3.ObjectUtils;
57 import org.apache.commons.lang3.StringUtils;
58 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
59 import org.opencv.core.Core;
60 import org.openide.util.NbBundle;
81 private static final String
FORMAT =
"png";
90 private static final SortedSet<String>
GIF_MIME_SET = ImmutableSortedSet.copyOf(
new String[]{
"image/gif"});
98 ImageIO.scanForPlugins();
99 BufferedImage tempImage;
101 tempImage = ImageIO.read(
ImageUtils.class.getResourceAsStream(
"/org/sleuthkit/autopsy/images/file-icon.png"));
102 }
catch (IOException ex) {
103 LOGGER.log(Level.SEVERE,
"Failed to load default icon.", ex);
106 DEFAULT_THUMBNAIL = tempImage;
109 boolean openCVLoadedTemp;
111 System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
112 if (System.getProperty(
"os.arch").equals(
"amd64") || System.getProperty(
"os.arch").equals(
"x86_64")) {
113 System.loadLibrary(
"opencv_ffmpeg248_64");
115 System.loadLibrary(
"opencv_ffmpeg248");
118 openCVLoadedTemp =
true;
119 }
catch (UnsatisfiedLinkError e) {
120 openCVLoadedTemp =
false;
121 LOGGER.log(Level.SEVERE,
"OpenCV Native code library failed to load", e);
126 openCVLoaded = openCVLoadedTemp;
127 SUPPORTED_IMAGE_EXTENSIONS = Arrays.asList(ImageIO.getReaderFileSuffixes());
128 SUPPORTED_IMAGE_MIME_TYPES =
new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes()));
133 SUPPORTED_IMAGE_MIME_TYPES.addAll(Arrays.asList(
136 "image/x-portable-graymap",
137 "image/x-portable-bitmap",
138 "application/x-123"));
139 SUPPORTED_IMAGE_MIME_TYPES.removeIf(
"application/octet-stream"::equals);
151 = Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder()
152 .namingPattern(
"thumbnail-saver-%d").build());
155 return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
159 return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES);
184 if (!(content instanceof AbstractFile)) {
187 AbstractFile file = (AbstractFile) content;
202 return isMediaThumbnailSupported(file,
"image/", SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS) ||
hasImageFileHeader(file);
213 public static boolean isGIF(AbstractFile file) {
214 return isMediaThumbnailSupported(file, null, GIF_MIME_SET, GIF_EXTENSION_LIST);
237 static boolean isMediaThumbnailSupported(AbstractFile file, String mimeTypePrefix,
final Collection<String> supportedMimeTypes,
final List<String> supportedExtension) {
238 if (
false == file.isFile() || file.getSize() <= 0) {
242 String extension = file.getNameExtension();
244 if (StringUtils.isNotBlank(extension) && supportedExtension.contains(extension)) {
249 if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) {
252 return supportedMimeTypes.contains(mimeType);
253 }
catch (FileTypeDetectorInitException | TskCoreException ex) {
254 LOGGER.log(Level.SEVERE,
"Error determining MIME type of " + getContentPathSafe(file), ex);
272 if (fileTypeDetector == null) {
288 public static BufferedImage
getThumbnail(Content content,
int iconSize) {
289 if (content instanceof AbstractFile) {
290 AbstractFile file = (AbstractFile) content;
295 return SwingFXUtils.fromFXImage(thumbnailTask.get(), null);
296 }
catch (InterruptedException | ExecutionException ex) {
297 LOGGER.log(Level.WARNING,
"Failed to get thumbnail for {0}: " + ex.toString(), getContentPathSafe(content));
335 return Paths.get(cacheDirectory,
"thumbnails", fileID +
".png").toFile();
336 }
catch (IllegalStateException e) {
337 LOGGER.log(Level.WARNING,
"Could not get cached thumbnail location. No case is open.");
362 if (file.getSize() < 100) {
367 byte[] fileHeaderBuffer =
readHeader(file, 2);
372 return (((fileHeaderBuffer[0] & 0xff) == 0xff) && ((fileHeaderBuffer[1] & 0xff) == 0xd8));
373 }
catch (TskCoreException ex) {
387 if (file.getSize() < 10) {
392 byte[] fileHeaderBuffer =
readHeader(file, 8);
397 return (((fileHeaderBuffer[1] & 0xff) == 0x50) && ((fileHeaderBuffer[2] & 0xff) == 0x4E)
398 && ((fileHeaderBuffer[3] & 0xff) == 0x47) && ((fileHeaderBuffer[4] & 0xff) == 0x0D)
399 && ((fileHeaderBuffer[5] & 0xff) == 0x0A) && ((fileHeaderBuffer[6] & 0xff) == 0x1A)
400 && ((fileHeaderBuffer[7] & 0xff) == 0x0A));
402 }
catch (TskCoreException ex) {
408 private static byte[]
readHeader(AbstractFile file,
int buffLength)
throws TskCoreException {
409 byte[] fileHeaderBuffer =
new byte[buffLength];
410 int bytesRead = file.read(fileHeaderBuffer, 0, buffLength);
412 if (bytesRead != buffLength) {
414 throw new TskCoreException(
"Could not read " + buffLength +
" bytes from " + file.getName());
416 return fileHeaderBuffer;
431 "ImageIO could not determine width of {0}: ",
432 imageReader -> imageReader.getWidth(0)
448 "ImageIO could not determine height of {0}: ",
449 imageReader -> imageReader.getHeight(0)
464 public T
extract(ImageReader reader)
throws IOException;
490 try (InputStream inputStream =
new BufferedInputStream(
new ReadContentInputStream(file));) {
491 try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
493 IIOException iioException =
new IIOException(
"Could not create ImageInputStream.");
494 LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
497 Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
499 if (readers.hasNext()) {
500 ImageReader reader = readers.next();
501 reader.setInput(input);
504 return propertyExtractor.extract(reader);
505 }
catch (IOException ex) {
506 LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file));
512 IIOException iioException =
new IIOException(
"No ImageReader found.");
513 LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
537 public static Task<javafx.scene.image.Image>
newGetThumbnailTask(AbstractFile file,
int iconSize,
boolean defaultOnFailure) {
552 @NbBundle.Messages({
"# {0} - file name",
553 "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
555 "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
558 updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
565 protected javafx.scene.image.Image
call() throws Exception {
573 if (cacheFile != null && cacheFile.exists()) {
575 BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
576 if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() ==
iconSize) {
577 return SwingFXUtils.toFXImage(cachedThumbnail, null);
579 }
catch (Exception ex) {
580 LOGGER.log(Level.WARNING,
"ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(),
ImageUtils.getContentPathSafe(file));
590 BufferedImage thumbnail = null;
593 updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
594 thumbnail =
VideoUtils.generateVideoThumbnail(file, iconSize);
596 if (null == thumbnail) {
597 if (defaultOnFailure) {
600 throw new IIOException(
"Failed to generate a thumbnail for " + getContentPathSafe(file));
607 BufferedImage bufferedImage = SwingFXUtils.fromFXImage(
readImage(), null);
608 if (null == bufferedImage) {
609 String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
610 LOGGER.log(Level.WARNING, msg);
611 throw new IIOException(msg);
613 updateProgress(-1, 1);
618 }
catch (IllegalArgumentException | OutOfMemoryError e) {
620 LOGGER.log(Level.WARNING,
"Cropping {0}, because it could not be scaled: " + e.toString(),
ImageUtils.getContentPathSafe(file));
622 final int height = bufferedImage.getHeight();
623 final int width = bufferedImage.getWidth();
624 if (iconSize < height || iconSize < width) {
625 final int cropHeight = Math.min(iconSize, height);
626 final int cropWidth = Math.min(iconSize, width);
629 }
catch (Exception cropException) {
630 LOGGER.log(Level.WARNING,
"Could not crop {0}: " + cropException.toString(),
ImageUtils.getContentPathSafe(file));
633 }
catch (Exception e) {
634 LOGGER.log(Level.WARNING,
"Could not scale {0}: " + e.toString(),
ImageUtils.getContentPathSafe(file));
643 updateProgress(-1, 1);
646 if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
650 return SwingFXUtils.toFXImage(thumbnail, null);
661 Files.createParentDirs(cacheFile);
662 if (cacheFile.exists()) {
665 ImageIO.write(thumbnail, FORMAT, cacheFile);
666 }
catch (IllegalArgumentException | IOException ex) {
667 LOGGER.log(Level.WARNING,
"Could not write thumbnail for {0}: " + ex.toString(),
ImageUtils.getContentPathSafe(file));
695 "ReadImageTask.mesageText=Reading image: {0}"})
700 updateMessage(Bundle.ReadImageTask_mesageText(file.getName()));
704 protected javafx.scene.image.Image
call() throws Exception {
712 static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
715 final AbstractFile file;
722 protected javafx.scene.image.Image
readImage() throws IOException {
725 javafx.scene.image.Image image =
new javafx.scene.image.Image(
new BufferedInputStream(
new ReadContentInputStream(file)));
726 if (image.isError() ==
false) {
744 ImageReadParam param = imageReader.getDefaultReadParam();
745 BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
746 param.setDestination(bufferedImage);
748 bufferedImage = imageReader.read(0, param);
749 }
catch (IOException iOException) {
750 LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT +
": " + iOException.toString(),
ImageUtils.getContentPathSafe(file));
757 return SwingFXUtils.toFXImage(bufferedImage, null);
764 updateProgress(percentageDone, 100);
766 reader.removeIIOReadProgressListener(
this);
776 javafx.scene.image.Image fxImage =
get();
777 if (fxImage == null) {
778 LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT,
ImageUtils.getContentPathSafe(file));
780 if (fxImage.isError()) {
782 LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT +
": " + ObjectUtils.toString(fxImage.getException()),
ImageUtils.getContentPathSafe(file));
785 }
catch (InterruptedException | ExecutionException ex) {
793 LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT +
": " + ObjectUtils.toString(getException()),
ImageUtils.getContentPathSafe(file));
798 updateProgress(100, 100);
838 static String getContentPathSafe(Content content) {
840 return content.getUniquePath();
841 }
catch (TskCoreException tskCoreException) {
842 String contentName = content.getName();
843 LOGGER.log(Level.SEVERE,
"Failed to get unique path for " + contentName, tskCoreException);
892 public static BufferedImage
getIcon(Content content,
int iconSize) {
static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT
static File getIconFile(Content content, int iconSize)
static boolean isGIF(AbstractFile file)
static final List< String > SUPPORTED_IMAGE_EXTENSIONS
javafx.scene.image.Image call()
static boolean isPngFileHeader(AbstractFile file)
static boolean thumbnailSupported(Content content)
static File getFile(long id)
void sequenceComplete(ImageReader source)
static final Logger LOGGER
void imageProgress(ImageReader reader, float percentageDone)
void readAborted(ImageReader source)
static Task< javafx.scene.image.Image > newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
final boolean defaultOnFailure
void imageStarted(ImageReader source, int imageIndex)
static final int ICON_SIZE_SMALL
static synchronized BufferedImage resizeFast(BufferedImage input, int size)
static FileTypeDetector fileTypeDetector
static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION
static final SortedSet< String > GIF_MIME_SET
static Task< javafx.scene.image.Image > newReadImageTask(AbstractFile file)
void sequenceStarted(ImageReader source, int minIndex)
static boolean isJpegFileHeader(AbstractFile file)
javafx.scene.image.Image readImage()
static final Executor imageSaver
void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex)
static final int ICON_SIZE_MEDIUM
static final boolean openCVLoaded
static Image getDefaultIcon()
GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
static int getImageHeight(AbstractFile file)
void thumbnailProgress(ImageReader source, float percentageDone)
static List< String > getSupportedImageExtensions()
String getCacheDirectory()
void thumbnailComplete(ImageReader source)
static Image getDefaultThumbnail()
static final BufferedImage DEFAULT_THUMBNAIL
javafx.scene.image.Image call()
static File getCachedThumbnailFile(Content content, int iconSize)
static boolean hasImageFileHeader(AbstractFile file)
static byte[] readHeader(AbstractFile file, int buffLength)
String detect(AbstractFile file)
static File getCachedThumbnailLocation(long fileID)
static Case getCurrentCase()
synchronized static Logger getLogger(String name)
static int getImageWidth(AbstractFile file)
static< T > T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor< T > propertyExtractor)
static boolean isImageThumbnailSupported(AbstractFile file)
static BufferedImage getThumbnail(Content content, int iconSize)
static final int ICON_SIZE_LARGE
static final List< String > GIF_EXTENSION_LIST
static final String FORMAT
synchronized static FileTypeDetector getFileTypeDetector()
void imageComplete(ImageReader source)
void saveThumbnail(BufferedImage thumbnail)
static BufferedImage getIcon(Content content, int iconSize)
static boolean isVideoThumbnailSupported(AbstractFile file)
static final SortedSet< String > SUPPORTED_IMAGE_MIME_TYPES
static synchronized BufferedImage cropImage(BufferedImage input, int width, int height)
static SortedSet< String > getSupportedImageMimeTypes()