Autopsy  4.10.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 com.google.common.io.Files;
26 import java.awt.Image;
27 import java.awt.image.BufferedImage;
28 import java.io.BufferedInputStream;
29 import java.io.File;
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;
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  "application/x-123")); //TODO: is this correct? -jm //NON-NLS
155  SUPPORTED_IMAGE_MIME_TYPES.removeIf("application/octet-stream"::equals); //NON-NLS
156 
157  //Clear the file map when the case changes, so we don't accidentaly get images from the old case.
158  Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> cacheFileMap.clear());
159  }
160 
165 
169  private static final Executor imageSaver
170  = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
171  .namingPattern("thumbnail-saver-%d").build()); //NON-NLS
172 
173  public static List<String> getSupportedImageExtensions() {
174  return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
175  }
176 
177  public static SortedSet<String> getSupportedImageMimeTypes() {
178  return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES);
179  }
180 
187  public static Image getDefaultThumbnail() {
188  return DEFAULT_THUMBNAIL;
189  }
190 
201  public static boolean thumbnailSupported(Content content) {
202 
203  if (!(content instanceof AbstractFile)) {
204  return false;
205  }
206  AbstractFile file = (AbstractFile) content;
207 
214  List<String> supportedExtensions = new ArrayList<>(SUPPORTED_IMAGE_EXTENSIONS);
215  supportedExtensions.addAll(VideoUtils.getSupportedVideoExtensions());
216  if (isSupportedMediaExtension(file, supportedExtensions)) {
217  return true;
218  }
219 
221  || isImageThumbnailSupported(file);
222  }
223 
232  public static boolean isImageThumbnailSupported(AbstractFile file) {
233  return isMediaThumbnailSupported(file, "image/", SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS) || hasImageFileHeader(file);//NON-NLS
234  }
235 
244  public static boolean isGIF(AbstractFile file) {
245  return isMediaThumbnailSupported(file, null, GIF_MIME_SET, GIF_EXTENSION_LIST);
246  }
247 
268  static boolean isMediaThumbnailSupported(AbstractFile file, String mimeTypePrefix, final Collection<String> supportedMimeTypes, final List<String> supportedExtension) {
269  if (false == file.isFile() || file.getSize() <= 0) {
270  return false;
271  }
272 
273  if (isSupportedMediaExtension(file, supportedExtension)) {
274  return true;
275  } else {
276  try {
277  String mimeType = getFileTypeDetector().getMIMEType(file);
278  if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) {
279  return true;
280  }
281  return supportedMimeTypes.contains(mimeType);
282  } catch (FileTypeDetectorInitException ex) {
283  LOGGER.log(Level.SEVERE, "Error determining MIME type of " + getContentPathSafe(file), ex);//NON-NLS
284  return false;
285  }
286  }
287  }
288 
298  static boolean isSupportedMediaExtension(final AbstractFile file, final List<String> supportedExtensions) {
299  String extension = file.getNameExtension();
300 
301  return (StringUtils.isNotBlank(extension) && supportedExtensions.contains(extension));
302  }
303 
316  if (fileTypeDetector == null) {
317  fileTypeDetector = new FileTypeDetector();
318  }
319  return fileTypeDetector;
320  }
321 
332  public static BufferedImage getThumbnail(Content content, int iconSize) {
333  if (content instanceof AbstractFile) {
334  AbstractFile file = (AbstractFile) content;
335  if (ImageUtils.isGIF(file)) {
336  /*
337  * Intercepting the image reading code for GIFs here allows us
338  * to rescale easily, but we lose animations.
339  */
340  try (BufferedInputStream bufferedReadContentStream = getBufferedReadContentStream(file);) {
341  if (Thread.interrupted()) {
342  return DEFAULT_THUMBNAIL;
343  }
344  final BufferedImage image = ImageIO.read(bufferedReadContentStream);
345  if (image != null) {
346  if (Thread.interrupted()) {
347  return DEFAULT_THUMBNAIL;
348  }
349  return ScalrWrapper.resizeHighQuality(image, iconSize, iconSize);
350  }
351  } catch (IOException iOException) {
352  LOGGER.log(Level.WARNING, "Failed to get thumbnail for " + getContentPathSafe(content), iOException); //NON-NLS
353  }
354  return DEFAULT_THUMBNAIL;
355  }
356 
357  Task<javafx.scene.image.Image> thumbnailTask = newGetThumbnailTask(file, iconSize, true);
358  if (Thread.interrupted()) {
359  return DEFAULT_THUMBNAIL;
360  }
361  thumbnailTask.run();
362  try {
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); //NON-NLS
366  }
367  }
368  return DEFAULT_THUMBNAIL;
369  }
370 
380  private static BufferedInputStream getBufferedReadContentStream(AbstractFile file) {
381  return new BufferedInputStream(new ReadContentInputStream(file));
382  }
383 
394  @Nullable
395  public static File getCachedThumbnailFile(Content content, int iconSize) {
396  getThumbnail(content, iconSize);
397  return getCachedThumbnailLocation(content.getId());
398  }
399 
410  private static File getCachedThumbnailLocation(long fileID) {
411  return cacheFileMap.computeIfAbsent(fileID, id -> {
412  try {
413  String cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
414  return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS
415  } catch (NoCurrentCaseException e) {
416  LOGGER.log(Level.INFO, "Could not get cached thumbnail location. No case is open."); //NON-NLS
417  return null;
418  }
419  });
420  }
421 
430  public static boolean hasImageFileHeader(AbstractFile file) {
431  return isJpegFileHeader(file) || isPngFileHeader(file);
432  }
433 
441  public static boolean isJpegFileHeader(AbstractFile file) {
442  if (file.getSize() < 100) {
443  return false;
444  }
445 
446  try {
447  byte[] fileHeaderBuffer = readHeader(file, 2);
448  /*
449  * Check for the JPEG header. Since Java bytes are signed, we cast
450  * them to an int first.
451  */
452  return (((fileHeaderBuffer[0] & 0xff) == 0xff) && ((fileHeaderBuffer[1] & 0xff) == 0xd8));
453  } catch (TskCoreException ex) {
454  //ignore if can't read the first few bytes, not a JPEG
455  return false;
456  }
457  }
458 
468  private static long getJfifStartOfImageOffset(AbstractFile file) {
469  byte[] fileHeaderBuffer;
470  long length;
471  try {
472  length = file.getSize();
473  if (length % 2 != 0) {
474  length -= 1; // Make it an even number so we can parse two bytes at a time
475  }
476  if (length >= 1024) {
477  length = 1024;
478  }
479  fileHeaderBuffer = readHeader(file, (int) length); // read up to first 1024 bytes
480  } catch (TskCoreException ex) {
481  // Couldn't read header. Let ImageIO try it.
482  return 0;
483  }
484 
485  if (fileHeaderBuffer != null) {
486  for (int index = 0; index < length; index += 2) {
487  // Look for Start Of Image marker and return the index when it's found
488  if ((fileHeaderBuffer[index] == (byte) 0xFF) && (fileHeaderBuffer[index + 1] == (byte) 0xD8)) {
489  return index;
490  }
491  }
492  }
493 
494  // Didn't match JFIF. Let ImageIO try to open it from offset 0.
495  return 0;
496  }
497 
505  public static boolean isPngFileHeader(AbstractFile file) {
506  if (file.getSize() < 10) {
507  return false;
508  }
509 
510  try {
511  byte[] fileHeaderBuffer = readHeader(file, 8);
512  /*
513  * Check for the png header. Since Java bytes are signed, we cast
514  * them to an int first.
515  */
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));
520 
521  } catch (TskCoreException ex) {
522  //ignore if can't read the first few bytes, not an png
523  return false;
524  }
525  }
526 
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);
530 
531  if (bytesRead != buffLength) {
532  //ignore if can't read the first few bytes, not an image
533  throw new TskCoreException("Could not read " + buffLength + " bytes from " + file.getName());//NON-NLS
534  }
535  return fileHeaderBuffer;
536  }
537 
548  static public int getImageWidth(AbstractFile file) throws IOException {
549  return getImageProperty(file,
550  "ImageIO could not determine width of {0}: ", //NON-NLS
551  imageReader -> imageReader.getWidth(0)
552  );
553  }
554 
565  static public int getImageHeight(AbstractFile file) throws IOException {
566  return getImageProperty(file,
567  "ImageIO could not determine height of {0}: ", //NON-NLS
568  imageReader -> imageReader.getHeight(0)
569  );
570 
571  }
572 
581  @FunctionalInterface
582  private static interface PropertyExtractor<T> {
583 
584  public T extract(ImageReader reader) throws IOException;
585  }
586 
608  private static <T> T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor<T> propertyExtractor) throws IOException {
609  try (InputStream inputStream = getBufferedReadContentStream(file);
610  ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
611  if (input == null) {
612  IIOException iioException = new IIOException("Could not create ImageInputStream.");
613  LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
614  throw iioException;
615  }
616  Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
617 
618  if (readers.hasNext()) {
619  ImageReader reader = readers.next();
620  reader.setInput(input);
621  try {
622  return propertyExtractor.extract(reader);
623  } catch (IOException ex) {
624  LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file));
625  throw ex;
626  } finally {
627  reader.dispose();
628  }
629  } else {
630  IIOException iioException = new IIOException("No ImageReader found.");
631  LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
632  throw iioException;
633  }
634  }
635  }
636 
653  public static Task<javafx.scene.image.Image> newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
654  return new GetThumbnailTask(file, iconSize, defaultOnFailure);
655 
656  }
657 
661  static private class GetThumbnailTask extends ReadImageTaskBase {
662 
663  private static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION = "Failed to read {0} for thumbnail generation."; //NON-NLS
664 
665  private final int iconSize;
666  private final File cacheFile;
667  private final boolean defaultOnFailure;
668 
669  @NbBundle.Messages({"# {0} - file name",
670  "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
671  "# {0} - file name",
672  "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
673  private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
674  super(file);
675  updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
676  this.iconSize = iconSize;
677  this.defaultOnFailure = defaultOnFailure;
678  this.cacheFile = getCachedThumbnailLocation(file.getId());
679  }
680 
681  @Override
682  protected javafx.scene.image.Image call() throws Exception {
683  if (isCancelled()) {
684  return null;
685  }
686  if (isGIF(file)) {
687  return readImage();
688  }
689 
690  // If a thumbnail file is already saved locally, just read that.
691  if (cacheFile != null) {
692  synchronized (cacheFile) {
693  if (cacheFile.exists()) {
694  try {
695  if (isCancelled()) {
696  return null;
697  }
698  BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
699  if (isCancelled()) {
700  return null;
701  }
702  if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
703  return SwingFXUtils.toFXImage(cachedThumbnail, null);
704  }
705  } catch (Exception ex) {
706  LOGGER.log(Level.WARNING, "ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
707  cacheFile.delete(); //since we can't read the file we might as well delete it.
708  }
709  }
710  }
711  }
712 
713  //There was no correctly-sized cached thumbnail so make one.
714  BufferedImage thumbnail = null;
716  if (FFMPEG_LOADED) {
717  updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
718  if (isCancelled()) {
719  return null;
720  }
721  thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
722  }
723  if (null == thumbnail) {
724  if (defaultOnFailure) {
725  thumbnail = DEFAULT_THUMBNAIL;
726  } else {
727  throw new IIOException("Failed to generate a thumbnail for " + getContentPathSafe(file));//NON-NLS
728  }
729  }
730 
731  } else {
732  if (isCancelled()) {
733  return null;
734  }
735  //read the image into a buffered image.
736  //TODO: I don't like this, we just converted it from BufferedIamge to fx Image -jm
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);
742  }
743  updateProgress(-1, 1);
744  if (isCancelled()) {
745  return null;
746  }
747  //resize, or if that fails, crop it
748  try {
749  if (isCancelled()) {
750  return null;
751  }
752  thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize);
753  } catch (IllegalArgumentException | OutOfMemoryError e) {
754  // if resizing does not work due to extreme aspect ratio or oom, crop the image instead.
755  LOGGER.log(Level.WARNING, "Cropping {0}, because it could not be scaled: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
756 
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);
762  try {
763  if (isCancelled()) {
764  return null;
765  }
766  if (isCancelled()) {
767  return null;
768  }
769  thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight);
770  } catch (Exception cropException) {
771  LOGGER.log(Level.WARNING, "Could not crop {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
772  }
773  }
774  } catch (Exception e) {
775  LOGGER.log(Level.WARNING, "Could not scale {0}: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
776  throw e;
777  }
778  }
779 
780  updateProgress(-1, 1);
781 
782  //if we got a valid thumbnail save it
783  if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
784  saveThumbnail(thumbnail);
785  }
786  if (isCancelled()) {
787  return null;
788  }
789  return SwingFXUtils.toFXImage(thumbnail, null);
790  }
791 
797  private void saveThumbnail(BufferedImage thumbnail) {
798  imageSaver.execute(() -> {
799  try {
800  synchronized (cacheFile) {
801  Files.createParentDirs(cacheFile);
802  if (cacheFile.exists()) {
803  cacheFile.delete();
804  }
805  ImageIO.write(thumbnail, FORMAT, cacheFile);
806  }
807  } catch (Exception ex) {
808  LOGGER.log(Level.WARNING, "Could not write thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
809  }
810  });
811  }
812  }
813 
827  public static Task<javafx.scene.image.Image> newReadImageTask(AbstractFile file) {
828  return new ReadImageTask(file);
829 
830  }
831 
835  @NbBundle.Messages({
836  "# {0} - file name",
837  "ReadImageTask.mesageText=Reading image: {0}"})
838  static private class ReadImageTask extends ReadImageTaskBase {
839 
840  ReadImageTask(AbstractFile file) {
841  super(file);
842  updateMessage(Bundle.ReadImageTask_mesageText(file.getName()));
843  }
844 
845  @Override
846  protected javafx.scene.image.Image call() throws Exception {
847  return readImage();
848  }
849  }
850 
854  static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
855 
856  private static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NON-NLS
857  final AbstractFile file;
858 
859  ReadImageTaskBase(AbstractFile file) {
860  this.file = file;
861  }
862 
863  protected javafx.scene.image.Image readImage() throws IOException {
864  if (isCancelled()) {
865  return null;
866  }
867  if (ImageUtils.isGIF(file)) {
868  //use JavaFX to directly read GIF to preserve potential animation
869  javafx.scene.image.Image image = new javafx.scene.image.Image(getBufferedReadContentStream(file));
870  if (image.isError() == false) {
871  return image;
872  }
873  } else if (file.getNameExtension().equalsIgnoreCase("tec")) { //NON-NLS
874  ReadContentInputStream readContentInputStream = new ReadContentInputStream(file);
875  // Find first Start Of Image marker
876  readContentInputStream.seek(getJfifStartOfImageOffset(file));
877  //use JavaFX to directly read .tec files
878  javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(readContentInputStream));
879  if (image.isError() == false) {
880  return image;
881  }
882  }
883  //fall through to default image reading code if there was an error
884  return getImageProperty(file, "ImageIO could not read {0}: ",
885  imageReader -> {
886  imageReader.addIIOReadProgressListener(ReadImageTaskBase.this);
887  /*
888  * This is the important part, get or create a
889  * ImageReadParam, create a destination image to hold
890  * the decoded result, then pass that image with the
891  * param.
892  */
893  ImageReadParam param = imageReader.getDefaultReadParam();
894  BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
895  param.setDestination(bufferedImage);
896  try {
897  if (isCancelled()) {
898  return null;
899  }
900  bufferedImage = imageReader.read(0, param); //should always be same bufferedImage object
901  } catch (IOException iOException) {
902  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
903  } finally {
904  imageReader.removeIIOReadProgressListener(ReadImageTaskBase.this);
905  }
906  if (isCancelled()) {
907  return null;
908  }
909  return SwingFXUtils.toFXImage(bufferedImage, null);
910  }
911  );
912  }
913 
914  @Override
915  public void imageProgress(ImageReader reader, float percentageDone) {
916  //update this task with the progress reported by ImageReader.read
917  updateProgress(percentageDone, 100);
918  if (isCancelled()) {
919  reader.removeIIOReadProgressListener(this);
920  reader.abort();
921  reader.dispose();
922  }
923  }
924 
925  @Override
926  public boolean isCancelled() {
927  if (Thread.interrupted()) {
928  this.cancel(true);
929  return true;
930  }
931  return super.isCancelled();
932  }
933 
934  @Override
935  protected void succeeded() {
936  super.succeeded();
937  try {
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()) {
942  //if there was somekind of error, log it
943  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file));
944  }
945  } catch (InterruptedException | ExecutionException ex) {
946  failed();
947  }
948  }
949 
950  @Override
951  protected void failed() {
952  super.failed();
953  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file));
954  }
955 
956  @Override
957  public void imageComplete(ImageReader source) {
958  updateProgress(100, 100);
959  }
960 
961  @Override
962  public void imageStarted(ImageReader source, int imageIndex) {
963  }
964 
965  @Override
966  public void sequenceStarted(ImageReader source, int minIndex) {
967  }
968 
969  @Override
970  public void sequenceComplete(ImageReader source) {
971  }
972 
973  @Override
974  public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
975  }
976 
977  @Override
978  public void thumbnailProgress(ImageReader source, float percentageDone) {
979  }
980 
981  @Override
982  public void thumbnailComplete(ImageReader source) {
983  }
984 
985  @Override
986  public void readAborted(ImageReader source) {
987  }
988  }
989 
998  static String getContentPathSafe(Content content) {
999  try {
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); //NON-NLS
1004  return contentName;
1005  }
1006  }
1007 
1016  @Deprecated
1017  public static Image getDefaultIcon() {
1018  return getDefaultThumbnail();
1019  }
1020 
1031  @Deprecated
1032 
1033  public static File getFile(long id) {
1034  return getCachedThumbnailLocation(id);
1035  }
1036 
1050  @Nonnull
1051  @Deprecated
1052  public static BufferedImage getIcon(Content content, int iconSize) {
1053  return getThumbnail(content, iconSize);
1054  }
1055 
1070  @Nullable
1071  @Deprecated
1072  public static File getIconFile(Content content, int iconSize) {
1073  return getCachedThumbnailFile(content, iconSize);
1074 
1075  }
1076 }
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:437
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-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.