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.ArrayList;
 
   35 import java.util.Arrays;
 
   36 import java.util.Collection;
 
   37 import java.util.Collections;
 
   38 import java.util.EnumSet;
 
   39 import java.util.Iterator;
 
   40 import java.util.List;
 
   41 import static java.util.Objects.nonNull;
 
   42 import java.util.SortedSet;
 
   43 import java.util.TreeSet;
 
   44 import java.util.concurrent.ConcurrentHashMap;
 
   45 import java.util.concurrent.ExecutionException;
 
   46 import java.util.concurrent.Executor;
 
   47 import java.util.concurrent.Executors;
 
   48 import java.util.logging.Level;
 
   49 import javafx.concurrent.Task;
 
   50 import javafx.embed.swing.SwingFXUtils;
 
   51 import javax.annotation.Nonnull;
 
   52 import javax.annotation.Nullable;
 
   53 import javax.imageio.IIOException;
 
   54 import javax.imageio.ImageIO;
 
   55 import javax.imageio.ImageReadParam;
 
   56 import javax.imageio.ImageReader;
 
   57 import javax.imageio.event.IIOReadProgressListener;
 
   58 import javax.imageio.stream.ImageInputStream;
 
   59 import org.apache.commons.lang3.ObjectUtils;
 
   60 import org.apache.commons.lang3.StringUtils;
 
   61 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 
   62 import org.openide.util.NbBundle;
 
   63 import org.openide.util.NbBundle.Messages;
 
   86     private static final String 
FORMAT = 
"png"; 
 
   95     private static final SortedSet<String> 
GIF_MIME_SET = ImmutableSortedSet.copyOf(
new String[]{
"image/gif"});
 
  109     @Messages({
"ImageUtils.ffmpegLoadedError.title=OpenCV FFMpeg",
 
  110         "ImageUtils.ffmpegLoadedError.msg=OpenCV FFMpeg library failed to load, see log for more details"})
 
  111     private static final ConcurrentHashMap<Long, File> 
cacheFileMap = 
new ConcurrentHashMap<>();
 
  114         ImageIO.scanForPlugins();
 
  115         BufferedImage tempImage;
 
  117             tempImage = ImageIO.read(
ImageUtils.class.getResourceAsStream(
"/org/sleuthkit/autopsy/images/file-icon.png"));
 
  118         } 
catch (IOException ex) {
 
  119             LOGGER.log(Level.SEVERE, 
"Failed to load default icon.", ex); 
 
  122         DEFAULT_THUMBNAIL = tempImage;
 
  123         boolean tempFfmpegLoaded = 
false;
 
  126                 if (System.getProperty(
"os.arch").equals(
"amd64") || System.getProperty(
"os.arch").equals(
"x86_64")) { 
 
  127                     System.loadLibrary(
"opencv_ffmpeg248_64"); 
 
  129                     System.loadLibrary(
"opencv_ffmpeg248"); 
 
  131                 tempFfmpegLoaded = 
true;
 
  132             } 
catch (UnsatisfiedLinkError e) {
 
  133                 tempFfmpegLoaded = 
false;
 
  134                 LOGGER.log(Level.SEVERE, Bundle.ImageUtils_ffmpegLoadedError_msg(), e); 
 
  138         FFMPEG_LOADED = tempFfmpegLoaded;
 
  140         SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes()));
 
  141         SUPPORTED_IMAGE_EXTENSIONS.add(
"tec"); 
 
  142         SUPPORTED_IMAGE_EXTENSIONS.removeIf(
"db"::equals); 
 
  144         SUPPORTED_IMAGE_MIME_TYPES = 
new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes()));
 
  149         SUPPORTED_IMAGE_MIME_TYPES.addAll(Arrays.asList(
 
  152                 "image/x-portable-graymap", 
 
  153                 "image/x-portable-bitmap", 
 
  154                 "application/x-123")); 
 
  155         SUPPORTED_IMAGE_MIME_TYPES.removeIf(
"application/octet-stream"::equals); 
 
  170             = Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder()
 
  171                     .namingPattern(
"thumbnail-saver-%d").build()); 
 
  174         return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
 
  178         return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES);
 
  203         if (!(content instanceof AbstractFile)) {
 
  206         AbstractFile file = (AbstractFile) content;
 
  216         if (isSupportedMediaExtension(file, supportedExtensions)) {
 
  233         return isMediaThumbnailSupported(file, 
"image/", SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS) || 
hasImageFileHeader(file);
 
  244     public static boolean isGIF(AbstractFile file) {
 
  245         return isMediaThumbnailSupported(file, null, GIF_MIME_SET, GIF_EXTENSION_LIST);
 
  268     static boolean isMediaThumbnailSupported(AbstractFile file, String mimeTypePrefix, 
final Collection<String> supportedMimeTypes, 
final List<String> supportedExtension) {
 
  269         if (
false == file.isFile() || file.getSize() <= 0) {
 
  273         if (isSupportedMediaExtension(file, supportedExtension)) {
 
  278                 if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) {
 
  281                 return supportedMimeTypes.contains(mimeType);
 
  282             } 
catch (FileTypeDetectorInitException ex) {
 
  283                 LOGGER.log(Level.SEVERE, 
"Error determining MIME type of " + getContentPathSafe(file), ex);
 
  298     static boolean isSupportedMediaExtension(
final AbstractFile file, 
final List<String> supportedExtensions) {
 
  299         String extension = file.getNameExtension();
 
  301         return (StringUtils.isNotBlank(extension) && supportedExtensions.contains(extension));
 
  316         if (fileTypeDetector == null) {
 
  332     public static BufferedImage 
getThumbnail(Content content, 
int iconSize) {
 
  333         if (content instanceof AbstractFile) {
 
  334             AbstractFile file = (AbstractFile) content;
 
  341                     if (Thread.interrupted()) {
 
  344                     final BufferedImage image = ImageIO.read(bufferedReadContentStream);
 
  346                         if (Thread.interrupted()) {
 
  351                 } 
catch (IOException iOException) {
 
  352                     LOGGER.log(Level.WARNING, 
"Failed to get thumbnail for " + getContentPathSafe(content), iOException); 
 
  358             if (Thread.interrupted()) {
 
  363                 return SwingFXUtils.fromFXImage(thumbnailTask.get(), null);
 
  364             } 
catch (InterruptedException | ExecutionException ex) {
 
  365                 LOGGER.log(Level.WARNING, 
"Failed to get thumbnail for " + getContentPathSafe(content), ex); 
 
  381         return new BufferedInputStream(
new ReadContentInputStream(file));
 
  414                 return Paths.get(cacheDirectory, 
"thumbnails", fileID + 
".png").toFile(); 
 
  416                 LOGGER.log(Level.INFO, 
"Could not get cached thumbnail location.  No case is open."); 
 
  442         if (file.getSize() < 100) {
 
  447             byte[] fileHeaderBuffer = 
readHeader(file, 2);
 
  452             return (((fileHeaderBuffer[0] & 0xff) == 0xff) && ((fileHeaderBuffer[1] & 0xff) == 0xd8));
 
  453         } 
catch (TskCoreException ex) {
 
  469         byte[] fileHeaderBuffer;
 
  472             length = file.getSize();
 
  473             if (length % 2 != 0) {
 
  476             if (length >= 1024) {
 
  479             fileHeaderBuffer = 
readHeader(file, (
int) length); 
 
  480         } 
catch (TskCoreException ex) {
 
  485         if (fileHeaderBuffer != null) {
 
  486             for (
int index = 0; index < length; index += 2) {
 
  488                 if ((fileHeaderBuffer[index] == (byte) 0xFF) && (fileHeaderBuffer[index + 1] == (byte) 0xD8)) {
 
  506         if (file.getSize() < 10) {
 
  511             byte[] fileHeaderBuffer = 
readHeader(file, 8);
 
  516             return (((fileHeaderBuffer[1] & 0xff) == 0x50) && ((fileHeaderBuffer[2] & 0xff) == 0x4E)
 
  517                     && ((fileHeaderBuffer[3] & 0xff) == 0x47) && ((fileHeaderBuffer[4] & 0xff) == 0x0D)
 
  518                     && ((fileHeaderBuffer[5] & 0xff) == 0x0A) && ((fileHeaderBuffer[6] & 0xff) == 0x1A)
 
  519                     && ((fileHeaderBuffer[7] & 0xff) == 0x0A));
 
  521         } 
catch (TskCoreException ex) {
 
  527     private static byte[] 
readHeader(AbstractFile file, 
int buffLength) 
throws TskCoreException {
 
  528         byte[] fileHeaderBuffer = 
new byte[buffLength];
 
  529         int bytesRead = file.read(fileHeaderBuffer, 0, buffLength);
 
  531         if (bytesRead != buffLength) {
 
  533             throw new TskCoreException(
"Could not read " + buffLength + 
" bytes from " + file.getName());
 
  535         return fileHeaderBuffer;
 
  550                 "ImageIO could not determine width of {0}: ", 
 
  551                 imageReader -> imageReader.getWidth(0)
 
  567                 "ImageIO could not determine height of {0}: ", 
 
  568                 imageReader -> imageReader.getHeight(0)
 
  584         public T 
extract(ImageReader reader) 
throws IOException;
 
  610                 ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
 
  612                 IIOException iioException = 
new IIOException(
"Could not create ImageInputStream.");
 
  613                 LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
 
  616             Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
 
  618             if (readers.hasNext()) {
 
  619                 ImageReader reader = readers.next();
 
  620                 reader.setInput(input);
 
  622                     return propertyExtractor.extract(reader);
 
  623                 } 
catch (IOException ex) {
 
  624                     LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file));
 
  630                 IIOException iioException = 
new IIOException(
"No ImageReader found.");
 
  631                 LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
 
  653     public static Task<javafx.scene.image.Image> 
newGetThumbnailTask(AbstractFile file, 
int iconSize, 
boolean defaultOnFailure) {
 
  669         @NbBundle.Messages({
"# {0} - file name",
 
  670             "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
 
  672             "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
 
  675             updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
 
  682         protected javafx.scene.image.Image 
call() throws Exception {
 
  691             if (cacheFile != null) {
 
  693                     if (cacheFile.exists()) {
 
  698                             BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
 
  702                             if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == 
iconSize) {
 
  703                                 return SwingFXUtils.toFXImage(cachedThumbnail, null);
 
  705                         } 
catch (Exception ex) {
 
  706                             LOGGER.log(Level.WARNING, 
"ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  714             BufferedImage thumbnail = null;
 
  717                     updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
 
  721                     thumbnail = 
VideoUtils.generateVideoThumbnail(file, iconSize);
 
  723                 if (null == thumbnail) {
 
  724                     if (defaultOnFailure) {
 
  727                         throw new IIOException(
"Failed to generate a thumbnail for " + getContentPathSafe(file));
 
  737                 BufferedImage bufferedImage = SwingFXUtils.fromFXImage(
readImage(), null);
 
  738                 if (null == bufferedImage) {
 
  739                     String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
 
  740                     LOGGER.log(Level.WARNING, msg);
 
  741                     throw new IIOException(msg);
 
  743                 updateProgress(-1, 1);
 
  753                 } 
catch (IllegalArgumentException | OutOfMemoryError e) {
 
  755                     LOGGER.log(Level.WARNING, 
"Cropping {0}, because it could not be scaled: " + e.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  757                     final int height = bufferedImage.getHeight();
 
  758                     final int width = bufferedImage.getWidth();
 
  759                     if (iconSize < height || iconSize < width) {
 
  760                         final int cropHeight = Math.min(iconSize, height);
 
  761                         final int cropWidth = Math.min(iconSize, width);
 
  770                         } 
catch (Exception cropException) {
 
  771                             LOGGER.log(Level.WARNING, 
"Could not crop {0}: " + cropException.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  774                 } 
catch (Exception e) {
 
  775                     LOGGER.log(Level.WARNING, 
"Could not scale {0}: " + e.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  780             updateProgress(-1, 1);
 
  783             if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
 
  789             return SwingFXUtils.toFXImage(thumbnail, null);
 
  801                         Files.createParentDirs(cacheFile);
 
  802                         if (cacheFile.exists()) {
 
  805                         ImageIO.write(thumbnail, FORMAT, cacheFile);
 
  807                 } 
catch (Exception ex) {
 
  808                     LOGGER.log(Level.WARNING, 
"Could not write thumbnail for {0}: " + ex.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  837         "ReadImageTask.mesageText=Reading image: {0}"})
 
  842             updateMessage(Bundle.ReadImageTask_mesageText(file.getName()));
 
  846         protected javafx.scene.image.Image 
call() throws Exception {
 
  854     static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
 
  857         final AbstractFile file;
 
  863         protected javafx.scene.image.Image 
readImage() throws IOException {
 
  870                 if (image.isError() == 
false) {
 
  873             } 
else if (file.getNameExtension().equalsIgnoreCase(
"tec")) { 
 
  874                 ReadContentInputStream readContentInputStream = 
new ReadContentInputStream(file);
 
  878                 javafx.scene.image.Image image = 
new javafx.scene.image.Image(
new BufferedInputStream(readContentInputStream));
 
  879                 if (image.isError() == 
false) {
 
  893                         ImageReadParam param = imageReader.getDefaultReadParam();
 
  894                         BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
 
  895                         param.setDestination(bufferedImage);
 
  900                             bufferedImage = imageReader.read(0, param); 
 
  901                         } 
catch (IOException iOException) {
 
  902                             LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + iOException.toString(), 
ImageUtils.getContentPathSafe(file)); 
 
  909                         return SwingFXUtils.toFXImage(bufferedImage, null);
 
  917             updateProgress(percentageDone, 100);
 
  919                 reader.removeIIOReadProgressListener(
this);
 
  927             if (Thread.interrupted()) {
 
  931             return super.isCancelled();
 
  938                 javafx.scene.image.Image fxImage = 
get();
 
  939                 if (fxImage == null) {
 
  940                     LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT, 
ImageUtils.getContentPathSafe(file));
 
  941                 } 
else if (fxImage.isError()) {
 
  943                     LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + ObjectUtils.toString(fxImage.getException()), 
ImageUtils.getContentPathSafe(file));
 
  945             } 
catch (InterruptedException | ExecutionException ex) {
 
  953             LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + 
": " + ObjectUtils.toString(getException()), 
ImageUtils.getContentPathSafe(file));
 
  958             updateProgress(100, 100);
 
  998     static String getContentPathSafe(Content content) {
 
 1000             return content.getUniquePath();
 
 1001         } 
catch (TskCoreException tskCoreException) {
 
 1002             String contentName = content.getName();
 
 1003             LOGGER.log(Level.SEVERE, 
"Failed to get unique path for " + contentName, tskCoreException); 
 
 1052     public static BufferedImage 
getIcon(Content content, 
int iconSize) {
 
static List< String > getSupportedVideoExtensions()
static File getIconFile(Content content, int iconSize)
static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT
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)
String getMIMEType(AbstractFile file)
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 synchronized BufferedImage resizeHighQuality(BufferedImage input, int width, int height)
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 boolean isOpenCvLoaded()
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()
static final boolean FFMPEG_LOADED
String getCacheDirectory()
void thumbnailComplete(ImageReader source)
static Image getDefaultThumbnail()
static final BufferedImage DEFAULT_THUMBNAIL
static long getJfifStartOfImageOffset(AbstractFile file)
javafx.scene.image.Image call()
static File getCachedThumbnailFile(Content content, int iconSize)
static boolean hasImageFileHeader(AbstractFile file)
static byte[] readHeader(AbstractFile file, int buffLength)
static File getCachedThumbnailLocation(long fileID)
static final ConcurrentHashMap< Long, File > cacheFileMap
synchronized static Logger getLogger(String name)
static int getImageWidth(AbstractFile file)
static void show(String title, String message, MessageType type, ActionListener actionListener)
static< T > T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor< T > propertyExtractor)
static Case getCurrentCaseThrows()
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
static BufferedInputStream getBufferedReadContentStream(AbstractFile file)
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()