Autopsy  4.13.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ImageUtils.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2018 Basis Technology Corp.
5  *
6  * Copyright 2012 42six Solutions.
7  * Contact: aebadirad <at> 42six <dot> com
8  * Project Contact/Architect: carrier <at> sleuthkit <dot> org
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  */
22 package org.sleuthkit.autopsy.coreutils;
23 
24 import com.google.common.collect.ImmutableSortedSet;
25 import java.awt.Image;
26 import java.awt.image.BufferedImage;
27 import java.io.BufferedInputStream;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.nio.file.Path;
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;
70 import org.sleuthkit.datamodel.AbstractFile;
71 import org.sleuthkit.datamodel.Content;
72 import org.sleuthkit.datamodel.ReadContentInputStream;
73 import org.sleuthkit.datamodel.TskCoreException;
74 
79 public class ImageUtils {
80 
81  private static final Logger LOGGER = Logger.getLogger(ImageUtils.class.getName());
82 
86  private static final String FORMAT = "png"; //NON-NLS
87 
88  public static final int ICON_SIZE_SMALL = 50;
89  public static final int ICON_SIZE_MEDIUM = 100;
90  public static final int ICON_SIZE_LARGE = 200;
91 
92  private static final BufferedImage DEFAULT_THUMBNAIL;
93 
94  private static final List<String> GIF_EXTENSION_LIST = Arrays.asList("gif");
95  private static final SortedSet<String> GIF_MIME_SET = ImmutableSortedSet.copyOf(new String[]{"image/gif"});
96 
97  private static final List<String> SUPPORTED_IMAGE_EXTENSIONS = new ArrayList<>();
98  private static final SortedSet<String> SUPPORTED_IMAGE_MIME_TYPES;
99 
100  private static final boolean FFMPEG_LOADED;
101 
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<>();
112 
113  static {
114  ImageIO.scanForPlugins();
115  BufferedImage tempImage;
116  try {
117  tempImage = ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/file-icon.png"));//NON-NLS
118  } catch (IOException ex) {
119  LOGGER.log(Level.SEVERE, "Failed to load default icon.", ex); //NON-NLS
120  tempImage = null;
121  }
122  DEFAULT_THUMBNAIL = tempImage;
123  boolean tempFfmpegLoaded = false;
125  try {
126  if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { //NON-NLS
127  System.loadLibrary("opencv_ffmpeg248_64"); //NON-NLS
128  } else {
129  System.loadLibrary("opencv_ffmpeg248"); //NON-NLS
130  }
131  tempFfmpegLoaded = true;
132  } catch (UnsatisfiedLinkError e) {
133  tempFfmpegLoaded = false;
134  LOGGER.log(Level.SEVERE, Bundle.ImageUtils_ffmpegLoadedError_msg(), e); //NON-NLS
135  MessageNotifyUtil.Notify.show(Bundle.ImageUtils_ffmpegLoadedError_title(), Bundle.ImageUtils_ffmpegLoadedError_msg(), MessageNotifyUtil.MessageType.WARNING);
136  }
137  }
138  FFMPEG_LOADED = tempFfmpegLoaded;
139 
140  SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes()));
141  SUPPORTED_IMAGE_EXTENSIONS.add("tec"); // Add JFIF .tec files
142  SUPPORTED_IMAGE_EXTENSIONS.removeIf("db"::equals); // remove db files
143 
144  SUPPORTED_IMAGE_MIME_TYPES = new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes()));
145  /*
146  * special cases and variants that we support, but don't get registered
147  * with ImageIO automatically
148  */
149  SUPPORTED_IMAGE_MIME_TYPES.addAll(Arrays.asList(
150  "image/x-rgb", //NON-NLS
151  "image/x-ms-bmp", //NON-NLS
152  "image/x-portable-graymap", //NON-NLS
153  "image/x-portable-bitmap", //NON-NLS
154  "image/webp", //NON-NLS
155  "application/x-123")); //TODO: is this correct? -jm //NON-NLS
156  SUPPORTED_IMAGE_MIME_TYPES.removeIf("application/octet-stream"::equals); //NON-NLS
157 
158  //Clear the file map when the case changes, so we don't accidentaly get images from the old case.
159  Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> cacheFileMap.clear());
160  }
161 
166 
170  private static final Executor imageSaver
171  = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
172  .namingPattern("thumbnail-saver-%d").build()); //NON-NLS
173 
174  public static List<String> getSupportedImageExtensions() {
175  return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
176  }
177 
178  public static SortedSet<String> getSupportedImageMimeTypes() {
179  return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES);
180  }
181 
188  public static Image getDefaultThumbnail() {
189  return DEFAULT_THUMBNAIL;
190  }
191 
202  public static boolean thumbnailSupported(Content content) {
203 
204  if (!(content instanceof AbstractFile)) {
205  return false;
206  }
207  AbstractFile file = (AbstractFile) content;
208 
215  List<String> supportedExtensions = new ArrayList<>(SUPPORTED_IMAGE_EXTENSIONS);
216  supportedExtensions.addAll(VideoUtils.getSupportedVideoExtensions());
217  if (isSupportedMediaExtension(file, supportedExtensions)) {
218  return true;
219  }
220 
222  || isImageThumbnailSupported(file);
223  }
224 
233  public static boolean isImageThumbnailSupported(AbstractFile file) {
234  return isMediaThumbnailSupported(file, "image/", SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS) || hasImageFileHeader(file);//NON-NLS
235  }
236 
245  public static boolean isGIF(AbstractFile file) {
246  return isMediaThumbnailSupported(file, null, GIF_MIME_SET, GIF_EXTENSION_LIST);
247  }
248 
269  static boolean isMediaThumbnailSupported(AbstractFile file, String mimeTypePrefix, final Collection<String> supportedMimeTypes, final List<String> supportedExtension) {
270  if (false == file.isFile() || file.getSize() <= 0) {
271  return false;
272  }
273 
274  if (isSupportedMediaExtension(file, supportedExtension)) {
275  return true;
276  } else {
277  try {
278  String mimeType = getFileTypeDetector().getMIMEType(file);
279  if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) {
280  return true;
281  }
282  return supportedMimeTypes.contains(mimeType);
283  } catch (FileTypeDetectorInitException ex) {
284  LOGGER.log(Level.SEVERE, "Error determining MIME type of " + getContentPathSafe(file), ex);//NON-NLS
285  return false;
286  }
287  }
288  }
289 
299  static boolean isSupportedMediaExtension(final AbstractFile file, final List<String> supportedExtensions) {
300  String extension = file.getNameExtension();
301 
302  return (StringUtils.isNotBlank(extension) && supportedExtensions.contains(extension));
303  }
304 
317  if (fileTypeDetector == null) {
318  fileTypeDetector = new FileTypeDetector();
319  }
320  return fileTypeDetector;
321  }
322 
333  public static BufferedImage getThumbnail(Content content, int iconSize) {
334  if (content instanceof AbstractFile) {
335  AbstractFile file = (AbstractFile) content;
336  if (ImageUtils.isGIF(file)) {
337  /*
338  * Intercepting the image reading code for GIFs here allows us
339  * to rescale easily, but we lose animations.
340  */
341  try (BufferedInputStream bufferedReadContentStream = getBufferedReadContentStream(file);) {
342  if (Thread.interrupted()) {
343  return DEFAULT_THUMBNAIL;
344  }
345  final BufferedImage image = ImageIO.read(bufferedReadContentStream);
346  if (image != null) {
347  if (Thread.interrupted()) {
348  return DEFAULT_THUMBNAIL;
349  }
350  return ScalrWrapper.resizeHighQuality(image, iconSize, iconSize);
351  }
352  } catch (IOException iOException) {
353  LOGGER.log(Level.WARNING, "Failed to get thumbnail for " + getContentPathSafe(content), iOException); //NON-NLS
354  }
355  return DEFAULT_THUMBNAIL;
356  }
357 
358  Task<javafx.scene.image.Image> thumbnailTask = newGetThumbnailTask(file, iconSize, true);
359  if (Thread.interrupted()) {
360  return DEFAULT_THUMBNAIL;
361  }
362  thumbnailTask.run();
363  try {
364  return SwingFXUtils.fromFXImage(thumbnailTask.get(), null);
365  } catch (InterruptedException | ExecutionException ex) {
366  LOGGER.log(Level.WARNING, "Failed to get thumbnail for " + getContentPathSafe(content), ex); //NON-NLS
367  }
368  }
369  return DEFAULT_THUMBNAIL;
370  }
371 
381  private static BufferedInputStream getBufferedReadContentStream(AbstractFile file) {
382  return new BufferedInputStream(new ReadContentInputStream(file));
383  }
384 
395  @Nullable
396  public static File getCachedThumbnailFile(Content content, int iconSize) {
397  getThumbnail(content, iconSize);
398  return getCachedThumbnailLocation(content.getId());
399  }
400 
411  private static File getCachedThumbnailLocation(long fileID) {
412  return cacheFileMap.computeIfAbsent(fileID, id -> {
413  try {
414  String cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
415  return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS
416  } catch (NoCurrentCaseException e) {
417  LOGGER.log(Level.INFO, "Could not get cached thumbnail location. No case is open."); //NON-NLS
418  return null;
419  }
420  });
421  }
422 
431  public static boolean hasImageFileHeader(AbstractFile file) {
432  return isJpegFileHeader(file) || isPngFileHeader(file);
433  }
434 
442  public static boolean isJpegFileHeader(AbstractFile file) {
443  if (file.getSize() < 100) {
444  return false;
445  }
446 
447  try {
448  byte[] fileHeaderBuffer = readHeader(file, 2);
449  /*
450  * Check for the JPEG header. Since Java bytes are signed, we cast
451  * them to an int first.
452  */
453  return (((fileHeaderBuffer[0] & 0xff) == 0xff) && ((fileHeaderBuffer[1] & 0xff) == 0xd8));
454  } catch (TskCoreException ex) {
455  //ignore if can't read the first few bytes, not a JPEG
456  return false;
457  }
458  }
459 
469  private static long getJfifStartOfImageOffset(AbstractFile file) {
470  byte[] fileHeaderBuffer;
471  long length;
472  try {
473  length = file.getSize();
474  if (length % 2 != 0) {
475  length -= 1; // Make it an even number so we can parse two bytes at a time
476  }
477  if (length >= 1024) {
478  length = 1024;
479  }
480  fileHeaderBuffer = readHeader(file, (int) length); // read up to first 1024 bytes
481  } catch (TskCoreException ex) {
482  // Couldn't read header. Let ImageIO try it.
483  return 0;
484  }
485 
486  if (fileHeaderBuffer != null) {
487  for (int index = 0; index < length; index += 2) {
488  // Look for Start Of Image marker and return the index when it's found
489  if ((fileHeaderBuffer[index] == (byte) 0xFF) && (fileHeaderBuffer[index + 1] == (byte) 0xD8)) {
490  return index;
491  }
492  }
493  }
494 
495  // Didn't match JFIF. Let ImageIO try to open it from offset 0.
496  return 0;
497  }
498 
506  public static boolean isPngFileHeader(AbstractFile file) {
507  if (file.getSize() < 10) {
508  return false;
509  }
510 
511  try {
512  byte[] fileHeaderBuffer = readHeader(file, 8);
513  /*
514  * Check for the png header. Since Java bytes are signed, we cast
515  * them to an int first.
516  */
517  return (((fileHeaderBuffer[1] & 0xff) == 0x50) && ((fileHeaderBuffer[2] & 0xff) == 0x4E)
518  && ((fileHeaderBuffer[3] & 0xff) == 0x47) && ((fileHeaderBuffer[4] & 0xff) == 0x0D)
519  && ((fileHeaderBuffer[5] & 0xff) == 0x0A) && ((fileHeaderBuffer[6] & 0xff) == 0x1A)
520  && ((fileHeaderBuffer[7] & 0xff) == 0x0A));
521 
522  } catch (TskCoreException ex) {
523  //ignore if can't read the first few bytes, not an png
524  return false;
525  }
526  }
527 
528  private static byte[] readHeader(AbstractFile file, int buffLength) throws TskCoreException {
529  byte[] fileHeaderBuffer = new byte[buffLength];
530  int bytesRead = file.read(fileHeaderBuffer, 0, buffLength);
531 
532  if (bytesRead != buffLength) {
533  //ignore if can't read the first few bytes, not an image
534  throw new TskCoreException("Could not read " + buffLength + " bytes from " + file.getName());//NON-NLS
535  }
536  return fileHeaderBuffer;
537  }
538 
549  static public int getImageWidth(AbstractFile file) throws IOException {
550  return getImageProperty(file,
551  "ImageIO could not determine width of {0}: ", //NON-NLS
552  imageReader -> imageReader.getWidth(0)
553  );
554  }
555 
566  static public int getImageHeight(AbstractFile file) throws IOException {
567  return getImageProperty(file,
568  "ImageIO could not determine height of {0}: ", //NON-NLS
569  imageReader -> imageReader.getHeight(0)
570  );
571 
572  }
573 
582  @FunctionalInterface
583  private static interface PropertyExtractor<T> {
584 
585  public T extract(ImageReader reader) throws IOException;
586  }
587 
609  private static <T> T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor<T> propertyExtractor) throws IOException {
610  try (InputStream inputStream = getBufferedReadContentStream(file);
611  ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
612  if (input == null) {
613  IIOException iioException = new IIOException("Could not create ImageInputStream.");
614  LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
615  throw iioException;
616  }
617  Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
618 
619  if (readers.hasNext()) {
620  ImageReader reader = readers.next();
621  reader.setInput(input);
622  try {
623  return propertyExtractor.extract(reader);
624  } catch (IOException ex) {
625  LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file));
626  throw ex;
627  } finally {
628  reader.dispose();
629  }
630  } else {
631  IIOException iioException = new IIOException("No ImageReader found.");
632  LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
633  throw iioException;
634  }
635  }
636  }
637 
654  public static Task<javafx.scene.image.Image> newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
655  return new GetThumbnailTask(file, iconSize, defaultOnFailure);
656 
657  }
658 
662  static private class GetThumbnailTask extends ReadImageTaskBase {
663 
664  private static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION = "Failed to read {0} for thumbnail generation."; //NON-NLS
665 
666  private final int iconSize;
667  private final File cacheFile;
668  private final boolean defaultOnFailure;
669 
670  @NbBundle.Messages({"# {0} - file name",
671  "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
672  "# {0} - file name",
673  "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
674  private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
675  super(file);
676  updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
677  this.iconSize = iconSize;
678  this.defaultOnFailure = defaultOnFailure;
679  this.cacheFile = getCachedThumbnailLocation(file.getId());
680  }
681 
682  @Override
683  protected javafx.scene.image.Image call() throws Exception {
684  if (isCancelled()) {
685  return null;
686  }
687  if (isGIF(file)) {
688  return readImage();
689  }
690 
691  // If a thumbnail file is already saved locally, just read that.
692  if (cacheFile != null) {
693  synchronized (cacheFile) {
694  if (cacheFile.exists()) {
695  try {
696  if (isCancelled()) {
697  return null;
698  }
699  BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
700  if (isCancelled()) {
701  return null;
702  }
703  if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
704  return SwingFXUtils.toFXImage(cachedThumbnail, null);
705  }
706  } catch (Exception ex) {
707  LOGGER.log(Level.WARNING, "ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
708  cacheFile.delete(); //since we can't read the file we might as well delete it.
709  }
710  }
711  }
712  }
713 
714  //There was no correctly-sized cached thumbnail so make one.
715  BufferedImage thumbnail = null;
717  if (FFMPEG_LOADED) {
718  updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
719  if (isCancelled()) {
720  return null;
721  }
722  thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
723  }
724  if (null == thumbnail) {
725  if (defaultOnFailure) {
726  thumbnail = DEFAULT_THUMBNAIL;
727  } else {
728  throw new IIOException("Failed to generate a thumbnail for " + getContentPathSafe(file));//NON-NLS
729  }
730  }
731 
732  } else {
733  if (isCancelled()) {
734  return null;
735  }
736  //read the image into a buffered image.
737  //TODO: I don't like this, we just converted it from BufferedIamge to fx Image -jm
738  BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null);
739  if (null == bufferedImage) {
740  String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
741  LOGGER.log(Level.WARNING, msg);
742  throw new IIOException(msg);
743  }
744  updateProgress(-1, 1);
745  if (isCancelled()) {
746  return null;
747  }
748  //resize, or if that fails, crop it
749  try {
750  if (isCancelled()) {
751  return null;
752  }
753  thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize);
754  } catch (IllegalArgumentException | OutOfMemoryError e) {
755  // if resizing does not work due to extreme aspect ratio or oom, crop the image instead.
756  LOGGER.log(Level.WARNING, "Cropping {0}, because it could not be scaled: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
757 
758  final int height = bufferedImage.getHeight();
759  final int width = bufferedImage.getWidth();
760  if (iconSize < height || iconSize < width) {
761  final int cropHeight = Math.min(iconSize, height);
762  final int cropWidth = Math.min(iconSize, width);
763  try {
764  if (isCancelled()) {
765  return null;
766  }
767  if (isCancelled()) {
768  return null;
769  }
770  thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight);
771  } catch (Exception cropException) {
772  LOGGER.log(Level.WARNING, "Could not crop {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
773  }
774  }
775  } catch (Exception e) {
776  LOGGER.log(Level.WARNING, "Could not scale {0}: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
777  throw e;
778  }
779  }
780 
781  updateProgress(-1, 1);
782 
783  //if we got a valid thumbnail save it
784  if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
785  saveThumbnail(thumbnail);
786  }
787  if (isCancelled()) {
788  return null;
789  }
790  return SwingFXUtils.toFXImage(thumbnail, null);
791  }
792 
798  private void saveThumbnail(BufferedImage thumbnail) {
799  imageSaver.execute(() -> {
800  try {
801  synchronized (cacheFile) {
802  Path path = Paths.get(cacheFile.getParent());
803  File thumbsDir = Paths.get(cacheFile.getParent()).toFile();
804  if (!thumbsDir.exists()) {
805  thumbsDir.mkdirs();
806  }
807 
808  if (cacheFile.exists()) {
809  cacheFile.delete();
810  }
811  ImageIO.write(thumbnail, FORMAT, cacheFile);
812  }
813  } catch (Exception ex) {
814  LOGGER.log(Level.WARNING, "Could not write thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
815  }
816  });
817  }
818  }
819 
833  public static Task<javafx.scene.image.Image> newReadImageTask(AbstractFile file) {
834  return new ReadImageTask(file);
835 
836  }
837 
841  @NbBundle.Messages({
842  "# {0} - file name",
843  "ReadImageTask.mesageText=Reading image: {0}"})
844  static private class ReadImageTask extends ReadImageTaskBase {
845 
846  ReadImageTask(AbstractFile file) {
847  super(file);
848  updateMessage(Bundle.ReadImageTask_mesageText(file.getName()));
849  }
850 
851  @Override
852  protected javafx.scene.image.Image call() throws Exception {
853  return readImage();
854  }
855  }
856 
860  static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
861 
862  private static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NON-NLS
863  final AbstractFile file;
864 
865  ReadImageTaskBase(AbstractFile file) {
866  this.file = file;
867  }
868 
869  protected javafx.scene.image.Image readImage() throws IOException {
870  if (isCancelled()) {
871  return null;
872  }
873  if (ImageUtils.isGIF(file)) {
874  //use JavaFX to directly read GIF to preserve potential animation
875  javafx.scene.image.Image image = new javafx.scene.image.Image(getBufferedReadContentStream(file));
876  if (image.isError() == false) {
877  return image;
878  }
879  } else if (file.getNameExtension().equalsIgnoreCase("tec")) { //NON-NLS
880  ReadContentInputStream readContentInputStream = new ReadContentInputStream(file);
881  // Find first Start Of Image marker
882  readContentInputStream.seek(getJfifStartOfImageOffset(file));
883  //use JavaFX to directly read .tec files
884  javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(readContentInputStream));
885  if (image.isError() == false) {
886  return image;
887  }
888  }
889  //fall through to default image reading code if there was an error
890  return getImageProperty(file, "ImageIO could not read {0}: ",
891  imageReader -> {
892  imageReader.addIIOReadProgressListener(ReadImageTaskBase.this);
893  /*
894  * This is the important part, get or create a
895  * ImageReadParam, create a destination image to hold
896  * the decoded result, then pass that image with the
897  * param.
898  */
899  ImageReadParam param = imageReader.getDefaultReadParam();
900  BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
901  param.setDestination(bufferedImage);
902  try {
903  if (isCancelled()) {
904  return null;
905  }
906  bufferedImage = imageReader.read(0, param); //should always be same bufferedImage object
907  } catch (IOException iOException) {
908  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
909  } finally {
910  imageReader.removeIIOReadProgressListener(ReadImageTaskBase.this);
911  }
912  if (isCancelled()) {
913  return null;
914  }
915  return SwingFXUtils.toFXImage(bufferedImage, null);
916  }
917  );
918  }
919 
920  @Override
921  public void imageProgress(ImageReader reader, float percentageDone) {
922  //update this task with the progress reported by ImageReader.read
923  updateProgress(percentageDone, 100);
924  if (isCancelled()) {
925  reader.removeIIOReadProgressListener(this);
926  reader.abort();
927  reader.dispose();
928  }
929  }
930 
931  @Override
932  public boolean isCancelled() {
933  if (Thread.interrupted()) {
934  this.cancel(true);
935  return true;
936  }
937  return super.isCancelled();
938  }
939 
940  @Override
941  protected void succeeded() {
942  super.succeeded();
943  try {
944  javafx.scene.image.Image fxImage = get();
945  if (fxImage == null) {
946  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT, ImageUtils.getContentPathSafe(file));
947  } else if (fxImage.isError()) {
948  //if there was somekind of error, log it
949  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file));
950  }
951  } catch (InterruptedException | ExecutionException ex) {
952  failed();
953  }
954  }
955 
956  @Override
957  protected void failed() {
958  super.failed();
959  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file));
960  }
961 
962  @Override
963  public void imageComplete(ImageReader source) {
964  updateProgress(100, 100);
965  }
966 
967  @Override
968  public void imageStarted(ImageReader source, int imageIndex) {
969  }
970 
971  @Override
972  public void sequenceStarted(ImageReader source, int minIndex) {
973  }
974 
975  @Override
976  public void sequenceComplete(ImageReader source) {
977  }
978 
979  @Override
980  public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
981  }
982 
983  @Override
984  public void thumbnailProgress(ImageReader source, float percentageDone) {
985  }
986 
987  @Override
988  public void thumbnailComplete(ImageReader source) {
989  }
990 
991  @Override
992  public void readAborted(ImageReader source) {
993  }
994  }
995 
1004  static String getContentPathSafe(Content content) {
1005  try {
1006  return content.getUniquePath();
1007  } catch (TskCoreException tskCoreException) {
1008  String contentName = content.getName();
1009  LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NON-NLS
1010  return contentName;
1011  }
1012  }
1013 
1022  @Deprecated
1023  public static Image getDefaultIcon() {
1024  return getDefaultThumbnail();
1025  }
1026 
1037  @Deprecated
1038 
1039  public static File getFile(long id) {
1040  return getCachedThumbnailLocation(id);
1041  }
1042 
1056  @Nonnull
1057  @Deprecated
1058  public static BufferedImage getIcon(Content content, int iconSize) {
1059  return getThumbnail(content, iconSize);
1060  }
1061 
1076  @Nullable
1077  @Deprecated
1078  public static File getIconFile(Content content, int iconSize) {
1079  return getCachedThumbnailFile(content, iconSize);
1080 
1081  }
1082 }
static List< String > getSupportedVideoExtensions()
Definition: VideoUtils.java:75
static File getIconFile(Content content, int iconSize)
static boolean isGIF(AbstractFile file)
static final List< String > SUPPORTED_IMAGE_EXTENSIONS
Definition: ImageUtils.java:97
static boolean isPngFileHeader(AbstractFile file)
static boolean thumbnailSupported(Content content)
void imageProgress(ImageReader reader, float percentageDone)
static Task< javafx.scene.image.Image > newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
void imageStarted(ImageReader source, int imageIndex)
static synchronized BufferedImage resizeFast(BufferedImage input, int size)
static FileTypeDetector fileTypeDetector
static final SortedSet< String > GIF_MIME_SET
Definition: ImageUtils.java:95
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)
void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex)
GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
static int getImageHeight(AbstractFile file)
void thumbnailProgress(ImageReader source, float percentageDone)
static List< String > getSupportedImageExtensions()
static final BufferedImage DEFAULT_THUMBNAIL
Definition: ImageUtils.java:92
static long getJfifStartOfImageOffset(AbstractFile file)
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)
Definition: Logger.java:124
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 void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:486
static BufferedInputStream getBufferedReadContentStream(AbstractFile file)
static boolean isImageThumbnailSupported(AbstractFile file)
static BufferedImage getThumbnail(Content content, int iconSize)
static final List< String > GIF_EXTENSION_LIST
Definition: ImageUtils.java:94
synchronized static FileTypeDetector getFileTypeDetector()
static BufferedImage getIcon(Content content, int iconSize)
static boolean isVideoThumbnailSupported(AbstractFile file)
static final SortedSet< String > SUPPORTED_IMAGE_MIME_TYPES
Definition: ImageUtils.java:98
static synchronized BufferedImage cropImage(BufferedImage input, int width, int height)
static SortedSet< String > getSupportedImageMimeTypes()

Copyright © 2012-2019 Basis Technology. Generated on: Tue Jan 7 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.