Autopsy  4.19.3
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 java.util.Objects;
42 import static java.util.Objects.nonNull;
43 import java.util.SortedSet;
44 import java.util.TreeSet;
45 import java.util.concurrent.ConcurrentHashMap;
46 import java.util.concurrent.ExecutionException;
47 import java.util.concurrent.Executor;
48 import java.util.concurrent.Executors;
49 import java.util.logging.Level;
50 import java.util.stream.Collectors;
51 import java.util.stream.Stream;
52 import javafx.concurrent.Task;
53 import javafx.embed.swing.SwingFXUtils;
54 import javax.annotation.Nonnull;
55 import javax.annotation.Nullable;
56 import javax.imageio.IIOException;
57 import javax.imageio.ImageIO;
58 import javax.imageio.ImageReadParam;
59 import javax.imageio.ImageReader;
60 import javax.imageio.event.IIOReadProgressListener;
61 import javax.imageio.stream.ImageInputStream;
62 import org.apache.commons.lang3.StringUtils;
63 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
64 import org.openide.util.NbBundle;
65 import org.openide.util.NbBundle.Messages;
72 import org.sleuthkit.datamodel.AbstractFile;
73 import org.sleuthkit.datamodel.Content;
74 import org.sleuthkit.datamodel.ReadContentInputStream;
75 import org.sleuthkit.datamodel.TskCoreException;
76 
81 public class ImageUtils {
82 
83  private static final Logger LOGGER = Logger.getLogger(ImageUtils.class.getName());
84 
88  private static final String FORMAT = "png"; //NON-NLS
89 
90  public static final int ICON_SIZE_SMALL = 50;
91  public static final int ICON_SIZE_MEDIUM = 100;
92  public static final int ICON_SIZE_LARGE = 200;
93 
94  private static final BufferedImage DEFAULT_THUMBNAIL;
95 
96  private static final List<String> GIF_EXTENSION_LIST = Arrays.asList("gif");
97  private static final SortedSet<String> GIF_MIME_SET = ImmutableSortedSet.copyOf(new String[]{"image/gif"});
98 
99  private static final List<String> SUPPORTED_IMAGE_EXTENSIONS = new ArrayList<>();
100  private static final SortedSet<String> SUPPORTED_IMAGE_MIME_TYPES;
101 
102  private static final boolean FFMPEG_LOADED;
103 
111  @Messages({"ImageUtils.ffmpegLoadedError.title=OpenCV FFMpeg",
112  "ImageUtils.ffmpegLoadedError.msg=OpenCV FFMpeg library failed to load, see log for more details"})
113  private static final ConcurrentHashMap<Long, File> cacheFileMap = new ConcurrentHashMap<>();
114 
115  static {
116  ImageIO.scanForPlugins();
117  BufferedImage tempImage;
118  try {
119  tempImage = ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/file-icon.png"));//NON-NLS
120  } catch (IOException ex) {
121  LOGGER.log(Level.SEVERE, "Failed to load default icon.", ex); //NON-NLS
122  tempImage = null;
123  }
124  DEFAULT_THUMBNAIL = tempImage;
125  boolean tempFfmpegLoaded = false;
127  try {
128  if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { //NON-NLS
129  System.loadLibrary("opencv_ffmpeg248_64"); //NON-NLS
130  } else {
131  System.loadLibrary("opencv_ffmpeg248"); //NON-NLS
132  }
133  tempFfmpegLoaded = true;
134  } catch (UnsatisfiedLinkError e) {
135  tempFfmpegLoaded = false;
136  LOGGER.log(Level.SEVERE, Bundle.ImageUtils_ffmpegLoadedError_msg(), e); //NON-NLS
137  MessageNotifyUtil.Notify.show(Bundle.ImageUtils_ffmpegLoadedError_title(), Bundle.ImageUtils_ffmpegLoadedError_msg(), MessageNotifyUtil.MessageType.WARNING);
138  }
139  }
140  FFMPEG_LOADED = tempFfmpegLoaded;
141 
142  // remove any empty extension types provided by ImageIO.getReaderFileSuffixes()
143  // This prevents extensions added by SPI implementations from causing errors
144  // (i.e. 'jai-imageio' utilized with IcePDF)
145  List<String> imageSuffixList = Arrays.stream(ImageIO.getReaderFileSuffixes())
146  .filter((extension) -> StringUtils.isNotBlank(extension))
147  .collect(Collectors.toList());
148 
149  SUPPORTED_IMAGE_EXTENSIONS.addAll(imageSuffixList);
150  SUPPORTED_IMAGE_EXTENSIONS.add("tec"); // Add JFIF .tec files
151  SUPPORTED_IMAGE_EXTENSIONS.removeIf("db"::equals); // remove db files
152 
153  List<String> mimeTypeList = Stream.of(ImageIO.getReaderMIMETypes())
154  // remove any empty mime types provided by ImageIO.getReaderMIMETypes()
155  // This prevents mime types added by SPI implementations from causing errors
156  // (i.e. 'jai-imageio' utilized with IcePDF)
157  .filter((mimeType) -> StringUtils.isNotBlank(mimeType))
158  .collect(Collectors.toList());
159 
160  SUPPORTED_IMAGE_MIME_TYPES = new TreeSet<>(mimeTypeList);
161  /*
162  * special cases and variants that we support, but don't get registered
163  * with ImageIO automatically
164  */
165  SUPPORTED_IMAGE_MIME_TYPES.addAll(Arrays.asList(
166  "image/x-rgb", //NON-NLS
167  "image/x-ms-bmp", //NON-NLS
168  "image/x-portable-graymap", //NON-NLS
169  "image/x-portable-bitmap", //NON-NLS
170  "image/webp", //NON-NLS
171  "application/x-123")); //TODO: is this correct? -jm //NON-NLS
172  SUPPORTED_IMAGE_MIME_TYPES.removeIf("application/octet-stream"::equals); //NON-NLS
173 
174  //Clear the file map when the case changes, so we don't accidentaly get images from the old case.
175  Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> cacheFileMap.clear());
176  }
177 
182 
186  private static final Executor imageSaver
187  = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
188  .namingPattern("thumbnail-saver-%d").build()); //NON-NLS
189 
190  public static List<String> getSupportedImageExtensions() {
191  return Collections.unmodifiableList(SUPPORTED_IMAGE_EXTENSIONS);
192  }
193 
194  public static SortedSet<String> getSupportedImageMimeTypes() {
195  return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES);
196  }
197 
204  public static Image getDefaultThumbnail() {
205  return DEFAULT_THUMBNAIL;
206  }
207 
218  public static boolean thumbnailSupported(Content content) {
219 
220  if (!(content instanceof AbstractFile)) {
221  return false;
222  }
223  AbstractFile file = (AbstractFile) content;
224 
231  List<String> supportedExtensions = new ArrayList<>(SUPPORTED_IMAGE_EXTENSIONS);
232  supportedExtensions.addAll(VideoUtils.getSupportedVideoExtensions());
233  if (isSupportedMediaExtension(file, supportedExtensions)) {
234  return true;
235  }
236 
238  || isImageThumbnailSupported(file);
239  }
240 
249  public static boolean isImageThumbnailSupported(AbstractFile file) {
250  return isMediaThumbnailSupported(file, "image/", SUPPORTED_IMAGE_MIME_TYPES, SUPPORTED_IMAGE_EXTENSIONS) || hasImageFileHeader(file);//NON-NLS
251  }
252 
261  public static boolean isGIF(AbstractFile file) {
262  return isMediaThumbnailSupported(file, null, GIF_MIME_SET, GIF_EXTENSION_LIST);
263  }
264 
285  static boolean isMediaThumbnailSupported(AbstractFile file, String mimeTypePrefix, final Collection<String> supportedMimeTypes, final List<String> supportedExtension) {
286  if (false == file.isFile() || file.getSize() <= 0) {
287  return false;
288  }
289 
290  if (isSupportedMediaExtension(file, supportedExtension)) {
291  return true;
292  } else {
293  try {
294  String mimeType = getFileTypeDetector().getMIMEType(file);
295  if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) {
296  return true;
297  }
298  return supportedMimeTypes.contains(mimeType);
299  } catch (FileTypeDetectorInitException ex) {
300  LOGGER.log(Level.SEVERE, "Error determining MIME type of " + getContentPathSafe(file), ex);//NON-NLS
301  return false;
302  }
303  }
304  }
305 
315  static boolean isSupportedMediaExtension(final AbstractFile file, final List<String> supportedExtensions) {
316  String extension = file.getNameExtension();
317 
318  return (StringUtils.isNotBlank(extension) && supportedExtensions.contains(extension));
319  }
320 
333  if (fileTypeDetector == null) {
334  fileTypeDetector = new FileTypeDetector();
335  }
336  return fileTypeDetector;
337  }
338 
349  public static BufferedImage getThumbnail(Content content, int iconSize) {
350  if (content instanceof AbstractFile) {
351  AbstractFile file = (AbstractFile) content;
352  if (ImageUtils.isGIF(file)) {
353  /*
354  * Intercepting the image reading code for GIFs here allows us
355  * to rescale easily, but we lose animations.
356  */
357  try (BufferedInputStream bufferedReadContentStream = getBufferedReadContentStream(file);) {
358  if (Thread.interrupted()) {
359  return DEFAULT_THUMBNAIL;
360  }
361  final BufferedImage image = ImageIO.read(bufferedReadContentStream);
362  if (image != null) {
363  if (Thread.interrupted()) {
364  return DEFAULT_THUMBNAIL;
365  }
366  return ScalrWrapper.resizeHighQuality(image, iconSize, iconSize);
367  }
368  } catch (IOException iOException) {
369  LOGGER.log(Level.WARNING, "Failed to get thumbnail for " + getContentPathSafe(content), iOException); //NON-NLS
370  }
371  return DEFAULT_THUMBNAIL;
372  }
373 
374  Task<javafx.scene.image.Image> thumbnailTask = newGetThumbnailTask(file, iconSize, true);
375  if (Thread.interrupted()) {
376  return DEFAULT_THUMBNAIL;
377  }
378  thumbnailTask.run();
379  try {
380  return SwingFXUtils.fromFXImage(thumbnailTask.get(), null);
381  } catch (InterruptedException | ExecutionException ex) {
382  LOGGER.log(Level.WARNING, "Failed to get thumbnail for " + getContentPathSafe(content), ex); //NON-NLS
383  }
384  }
385  return DEFAULT_THUMBNAIL;
386  }
387 
397  private static BufferedInputStream getBufferedReadContentStream(AbstractFile file) {
398  return new BufferedInputStream(new ReadContentInputStream(file));
399  }
400 
411  @Nullable
412  public static File getCachedThumbnailFile(Content content, int iconSize) {
413  getThumbnail(content, iconSize);
414  return getCachedThumbnailLocation(content.getId());
415  }
416 
427  private static File getCachedThumbnailLocation(long fileID) {
428  return cacheFileMap.computeIfAbsent(fileID, id -> {
429  try {
430  String cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
431  return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS
432  } catch (NoCurrentCaseException e) {
433  LOGGER.log(Level.INFO, "Could not get cached thumbnail location. No case is open."); //NON-NLS
434  return null;
435  }
436  });
437  }
438 
447  public static boolean hasImageFileHeader(AbstractFile file) {
448  return isJpegFileHeader(file) || isPngFileHeader(file);
449  }
450 
458  public static boolean isJpegFileHeader(AbstractFile file) {
459  if (file.getSize() < 100) {
460  return false;
461  }
462 
463  try {
464  byte[] fileHeaderBuffer = readHeader(file, 2);
465  /*
466  * Check for the JPEG header. Since Java bytes are signed, we cast
467  * them to an int first.
468  */
469  return (((fileHeaderBuffer[0] & 0xff) == 0xff) && ((fileHeaderBuffer[1] & 0xff) == 0xd8));
470  } catch (TskCoreException ex) {
471  //ignore if can't read the first few bytes, not a JPEG
472  return false;
473  }
474  }
475 
485  private static long getJfifStartOfImageOffset(AbstractFile file) {
486  byte[] fileHeaderBuffer;
487  long length;
488  try {
489  length = file.getSize();
490  if (length % 2 != 0) {
491  length -= 1; // Make it an even number so we can parse two bytes at a time
492  }
493  if (length >= 1024) {
494  length = 1024;
495  }
496  fileHeaderBuffer = readHeader(file, (int) length); // read up to first 1024 bytes
497  } catch (TskCoreException ex) {
498  // Couldn't read header. Let ImageIO try it.
499  return 0;
500  }
501 
502  if (fileHeaderBuffer != null) {
503  for (int index = 0; index < length; index += 2) {
504  // Look for Start Of Image marker and return the index when it's found
505  if ((fileHeaderBuffer[index] == (byte) 0xFF) && (fileHeaderBuffer[index + 1] == (byte) 0xD8)) {
506  return index;
507  }
508  }
509  }
510 
511  // Didn't match JFIF. Let ImageIO try to open it from offset 0.
512  return 0;
513  }
514 
522  public static boolean isPngFileHeader(AbstractFile file) {
523  if (file.getSize() < 10) {
524  return false;
525  }
526 
527  try {
528  byte[] fileHeaderBuffer = readHeader(file, 8);
529  /*
530  * Check for the png header. Since Java bytes are signed, we cast
531  * them to an int first.
532  */
533  return (((fileHeaderBuffer[1] & 0xff) == 0x50) && ((fileHeaderBuffer[2] & 0xff) == 0x4E)
534  && ((fileHeaderBuffer[3] & 0xff) == 0x47) && ((fileHeaderBuffer[4] & 0xff) == 0x0D)
535  && ((fileHeaderBuffer[5] & 0xff) == 0x0A) && ((fileHeaderBuffer[6] & 0xff) == 0x1A)
536  && ((fileHeaderBuffer[7] & 0xff) == 0x0A));
537 
538  } catch (TskCoreException ex) {
539  //ignore if can't read the first few bytes, not an png
540  return false;
541  }
542  }
543 
544  private static byte[] readHeader(AbstractFile file, int buffLength) throws TskCoreException {
545  byte[] fileHeaderBuffer = new byte[buffLength];
546  int bytesRead = file.read(fileHeaderBuffer, 0, buffLength);
547 
548  if (bytesRead != buffLength) {
549  //ignore if can't read the first few bytes, not an image
550  throw new TskCoreException("Could not read " + buffLength + " bytes from " + file.getName());//NON-NLS
551  }
552  return fileHeaderBuffer;
553  }
554 
565  static public int getImageWidth(AbstractFile file) throws IOException {
566  return getImageProperty(file,
567  "ImageIO could not determine width of {0}: ", //NON-NLS
568  imageReader -> imageReader.getWidth(0)
569  );
570  }
571 
582  static public int getImageHeight(AbstractFile file) throws IOException {
583  return getImageProperty(file,
584  "ImageIO could not determine height of {0}: ", //NON-NLS
585  imageReader -> imageReader.getHeight(0)
586  );
587 
588  }
589 
598  @FunctionalInterface
599  private static interface PropertyExtractor<T> {
600 
601  public T extract(ImageReader reader) throws IOException;
602  }
603 
625  private static <T> T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor<T> propertyExtractor) throws IOException {
626  try (InputStream inputStream = getBufferedReadContentStream(file);
627  ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
628  if (input == null) {
629  IIOException iioException = new IIOException("Could not create ImageInputStream.");
630  LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
631  throw iioException;
632  }
633  Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
634 
635  if (readers.hasNext()) {
636  ImageReader reader = readers.next();
637  reader.setInput(input);
638  try {
639  return propertyExtractor.extract(reader);
640  } catch (IOException ex) {
641  LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file));
642  throw ex;
643  } finally {
644  reader.dispose();
645  }
646  } else {
647  IIOException iioException = new IIOException("No ImageReader found.");
648  LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
649  throw iioException;
650  }
651  }
652  }
653 
670  public static Task<javafx.scene.image.Image> newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
671  return new GetThumbnailTask(file, iconSize, defaultOnFailure);
672 
673  }
674 
678  static private class GetThumbnailTask extends ReadImageTaskBase {
679 
680  private static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION = "Failed to read {0} for thumbnail generation."; //NON-NLS
681 
682  private final int iconSize;
683  private final File cacheFile;
684  private final boolean defaultOnFailure;
685 
686  @NbBundle.Messages({"# {0} - file name",
687  "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
688  "# {0} - file name",
689  "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
690  private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
691  super(file);
692  updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
693  this.iconSize = iconSize;
694  this.defaultOnFailure = defaultOnFailure;
695  this.cacheFile = getCachedThumbnailLocation(file.getId());
696  }
697 
698  @Override
699  protected javafx.scene.image.Image call() throws Exception {
700  if (isCancelled()) {
701  return null;
702  }
703  if (isGIF(file)) {
704  return readImage();
705  }
706 
707  // If a thumbnail file is already saved locally, just read that.
708  if (cacheFile != null) {
709  synchronized (cacheFile) {
710  if (cacheFile.exists()) {
711  try {
712  if (isCancelled()) {
713  return null;
714  }
715  BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
716  if (isCancelled()) {
717  return null;
718  }
719  if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
720  return SwingFXUtils.toFXImage(cachedThumbnail, null);
721  }
722  } catch (Exception ex) {
723  LOGGER.log(Level.WARNING, "ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
724  cacheFile.delete(); //since we can't read the file we might as well delete it.
725  }
726  }
727  }
728  }
729 
730  //There was no correctly-sized cached thumbnail so make one.
731  BufferedImage thumbnail = null;
733  if (FFMPEG_LOADED) {
734  updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
735  if (isCancelled()) {
736  return null;
737  }
738  thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
739  }
740  if (null == thumbnail) {
741  if (defaultOnFailure) {
742  thumbnail = DEFAULT_THUMBNAIL;
743  } else {
744  throw new IIOException("Failed to generate a thumbnail for " + getContentPathSafe(file));//NON-NLS
745  }
746  }
747 
748  } else {
749  if (isCancelled()) {
750  return null;
751  }
752  //read the image into a buffered image.
753  //TODO: I don't like this, we just converted it from BufferedIamge to fx Image -jm
754  BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null);
755  if (null == bufferedImage) {
756  String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
757  LOGGER.log(Level.WARNING, msg);
758  throw new IIOException(msg);
759  }
760  updateProgress(-1, 1);
761  if (isCancelled()) {
762  return null;
763  }
764  //resize, or if that fails, crop it
765  try {
766  if (isCancelled()) {
767  return null;
768  }
769  thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize);
770  } catch (IllegalArgumentException | OutOfMemoryError e) {
771  // if resizing does not work due to extreme aspect ratio or oom, crop the image instead.
772  LOGGER.log(Level.WARNING, "Cropping {0}, because it could not be scaled: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
773 
774  final int height = bufferedImage.getHeight();
775  final int width = bufferedImage.getWidth();
776  if (iconSize < height || iconSize < width) {
777  final int cropHeight = Math.min(iconSize, height);
778  final int cropWidth = Math.min(iconSize, width);
779  try {
780  if (isCancelled()) {
781  return null;
782  }
783  if (isCancelled()) {
784  return null;
785  }
786  thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight);
787  } catch (Exception cropException) {
788  LOGGER.log(Level.WARNING, "Could not crop {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
789  }
790  }
791  } catch (Exception e) {
792  LOGGER.log(Level.WARNING, "Could not scale {0}: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
793  throw e;
794  }
795  }
796 
797  updateProgress(-1, 1);
798 
799  //if we got a valid thumbnail save it
800  if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
801  saveThumbnail(thumbnail);
802  }
803  if (isCancelled()) {
804  return null;
805  }
806  return SwingFXUtils.toFXImage(thumbnail, null);
807  }
808 
814  private void saveThumbnail(BufferedImage thumbnail) {
815  imageSaver.execute(() -> {
816  try {
817  synchronized (cacheFile) {
818  Path path = Paths.get(cacheFile.getParent());
819  File thumbsDir = Paths.get(cacheFile.getParent()).toFile();
820  if (!thumbsDir.exists()) {
821  thumbsDir.mkdirs();
822  }
823 
824  if (cacheFile.exists()) {
825  cacheFile.delete();
826  }
827  ImageIO.write(thumbnail, FORMAT, cacheFile);
828  }
829  } catch (Exception ex) {
830  LOGGER.log(Level.WARNING, "Could not write thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
831  }
832  });
833  }
834  }
835 
849  public static Task<javafx.scene.image.Image> newReadImageTask(AbstractFile file) {
850  return new ReadImageTask(file);
851 
852  }
853 
857  @NbBundle.Messages({
858  "# {0} - file name",
859  "ReadImageTask.mesageText=Reading image: {0}"})
860  static private class ReadImageTask extends ReadImageTaskBase {
861 
862  ReadImageTask(AbstractFile file) {
863  super(file);
864  updateMessage(Bundle.ReadImageTask_mesageText(file.getName()));
865  }
866 
867  @Override
868  protected javafx.scene.image.Image call() throws Exception {
869  return readImage();
870  }
871  }
872 
876  static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
877 
878  private static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NON-NLS
879  final AbstractFile file;
880 
881  ReadImageTaskBase(AbstractFile file) {
882  this.file = file;
883  }
884 
885  protected javafx.scene.image.Image readImage() throws IOException {
886  if (isCancelled()) {
887  return null;
888  }
889  if (ImageUtils.isGIF(file)) {
890  //use JavaFX to directly read GIF to preserve potential animation
891  javafx.scene.image.Image image = new javafx.scene.image.Image(getBufferedReadContentStream(file));
892  if (image.isError() == false) {
893  return image;
894  }
895  } else if (file.getNameExtension().equalsIgnoreCase("tec")) { //NON-NLS
896  ReadContentInputStream readContentInputStream = new ReadContentInputStream(file);
897  // Find first Start Of Image marker
898  readContentInputStream.seek(getJfifStartOfImageOffset(file));
899  //use JavaFX to directly read .tec files
900  javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(readContentInputStream));
901  if (image.isError() == false) {
902  return image;
903  }
904  }
905  //fall through to default image reading code if there was an error
906  return getImageProperty(file, "ImageIO could not read {0}: ",
907  imageReader -> {
908  imageReader.addIIOReadProgressListener(ReadImageTaskBase.this);
909  /*
910  * This is the important part, get or create a
911  * ImageReadParam, create a destination image to hold
912  * the decoded result, then pass that image with the
913  * param.
914  */
915  ImageReadParam param = imageReader.getDefaultReadParam();
916  BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
917  param.setDestination(bufferedImage);
918  try {
919  if (isCancelled()) {
920  return null;
921  }
922  bufferedImage = imageReader.read(0, param); //should always be same bufferedImage object
923  } catch (IOException iOException) {
924  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
925  } finally {
926  imageReader.removeIIOReadProgressListener(ReadImageTaskBase.this);
927  }
928  if (isCancelled()) {
929  return null;
930  }
931  return SwingFXUtils.toFXImage(bufferedImage, null);
932  }
933  );
934  }
935 
936  @Override
937  public void imageProgress(ImageReader reader, float percentageDone) {
938  //update this task with the progress reported by ImageReader.read
939  updateProgress(percentageDone, 100);
940  if (isCancelled()) {
941  reader.removeIIOReadProgressListener(this);
942  reader.abort();
943  reader.dispose();
944  }
945  }
946 
947  @Override
948  public boolean isCancelled() {
949  if (Thread.interrupted()) {
950  this.cancel(true);
951  return true;
952  }
953  return super.isCancelled();
954  }
955 
956  @Override
957  protected void succeeded() {
958  super.succeeded();
959  try {
960  javafx.scene.image.Image fxImage = get();
961  if (fxImage == null) {
962  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT, ImageUtils.getContentPathSafe(file));
963  } else if (fxImage.isError()) {
964  //if there was somekind of error, log it
965  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + Objects.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file));
966  }
967  } catch (InterruptedException | ExecutionException ex) {
968  failed();
969  }
970  }
971 
972  @Override
973  protected void failed() {
974  super.failed();
975  LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + Objects.toString(getException()), ImageUtils.getContentPathSafe(file));
976  }
977 
978  @Override
979  public void imageComplete(ImageReader source) {
980  updateProgress(100, 100);
981  }
982 
983  @Override
984  public void imageStarted(ImageReader source, int imageIndex) {
985  }
986 
987  @Override
988  public void sequenceStarted(ImageReader source, int minIndex) {
989  }
990 
991  @Override
992  public void sequenceComplete(ImageReader source) {
993  }
994 
995  @Override
996  public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
997  }
998 
999  @Override
1000  public void thumbnailProgress(ImageReader source, float percentageDone) {
1001  }
1002 
1003  @Override
1004  public void thumbnailComplete(ImageReader source) {
1005  }
1006 
1007  @Override
1008  public void readAborted(ImageReader source) {
1009  }
1010  }
1011 
1020  static String getContentPathSafe(Content content) {
1021  try {
1022  return content.getUniquePath();
1023  } catch (TskCoreException tskCoreException) {
1024  String contentName = content.getName();
1025  LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NON-NLS
1026  return contentName;
1027  }
1028  }
1029 
1038  @Deprecated
1039  public static Image getDefaultIcon() {
1040  return getDefaultThumbnail();
1041  }
1042 
1053  @Deprecated
1054 
1055  public static File getFile(long id) {
1056  return getCachedThumbnailLocation(id);
1057  }
1058 
1072  @Nonnull
1073  @Deprecated
1074  public static BufferedImage getIcon(Content content, int iconSize) {
1075  return getThumbnail(content, iconSize);
1076  }
1077 
1092  @Nullable
1093  @Deprecated
1094  public static File getIconFile(Content content, int iconSize) {
1095  return getCachedThumbnailFile(content, iconSize);
1096 
1097  }
1098 }
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:99
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:97
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:94
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:711
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:96
synchronized static FileTypeDetector getFileTypeDetector()
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()

Copyright © 2012-2022 Basis Technology. Generated on: Fri Dec 2 2022
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.