Autopsy 4.22.1
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 */
22package org.sleuthkit.autopsy.coreutils;
23
24import com.google.common.collect.ImmutableSortedSet;
25import java.awt.Image;
26import java.awt.image.BufferedImage;
27import java.io.BufferedInputStream;
28import java.io.File;
29import java.io.IOException;
30import java.io.InputStream;
31import java.nio.file.Path;
32import java.nio.file.Paths;
33import java.text.MessageFormat;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.EnumSet;
39import java.util.Iterator;
40import java.util.List;
41import java.util.Objects;
42import static java.util.Objects.nonNull;
43import java.util.SortedSet;
44import java.util.TreeSet;
45import java.util.concurrent.ConcurrentHashMap;
46import java.util.concurrent.ExecutionException;
47import java.util.concurrent.Executor;
48import java.util.concurrent.Executors;
49import java.util.logging.Level;
50import java.util.stream.Collectors;
51import java.util.stream.Stream;
52import javafx.concurrent.Task;
53import javafx.embed.swing.SwingFXUtils;
54import javax.annotation.Nonnull;
55import javax.annotation.Nullable;
56import javax.imageio.IIOException;
57import javax.imageio.ImageIO;
58import javax.imageio.ImageReadParam;
59import javax.imageio.ImageReader;
60import javax.imageio.event.IIOReadProgressListener;
61import javax.imageio.stream.ImageInputStream;
62import org.apache.commons.lang3.StringUtils;
63import org.apache.commons.lang3.concurrent.BasicThreadFactory;
64import org.openide.util.NbBundle;
65import org.openide.util.NbBundle.Messages;
66import org.sleuthkit.autopsy.casemodule.Case;
67import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
68import org.sleuthkit.autopsy.corelibs.OpenCvLoader;
69import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
70import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
71import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector.FileTypeDetectorInitException;
72import org.sleuthkit.datamodel.AbstractFile;
73import org.sleuthkit.datamodel.Content;
74import org.sleuthkit.datamodel.ReadContentInputStream;
75import org.sleuthkit.datamodel.TskCoreException;
76
81public 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_ffmpeg2413_64"); //NON-NLS
130 } else {
131 System.loadLibrary("opencv_ffmpeg2413"); //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
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) {
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 void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition Case.java:712
static synchronized BufferedImage resizeFast(BufferedImage input, int size)
static synchronized BufferedImage cropImage(BufferedImage input, int width, int height)
static synchronized BufferedImage resizeHighQuality(BufferedImage input, int width, int height)
GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex)
void imageStarted(ImageReader source, int imageIndex)
void sequenceStarted(ImageReader source, int minIndex)
void imageProgress(ImageReader reader, float percentageDone)
void thumbnailProgress(ImageReader source, float percentageDone)
static final List< String > SUPPORTED_IMAGE_EXTENSIONS
static List< String > getSupportedImageExtensions()
static Task< javafx.scene.image.Image > newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure)
static final SortedSet< String > GIF_MIME_SET
static File getIconFile(Content content, int iconSize)
static boolean hasImageFileHeader(AbstractFile file)
static BufferedImage getIcon(Content content, int iconSize)
static File getCachedThumbnailLocation(long fileID)
static boolean thumbnailSupported(Content content)
static long getJfifStartOfImageOffset(AbstractFile file)
static int getImageWidth(AbstractFile file)
static final SortedSet< String > SUPPORTED_IMAGE_MIME_TYPES
static BufferedInputStream getBufferedReadContentStream(AbstractFile file)
static boolean isGIF(AbstractFile file)
static File getCachedThumbnailFile(Content content, int iconSize)
static boolean isImageThumbnailSupported(AbstractFile file)
static BufferedImage getThumbnail(Content content, int iconSize)
static byte[] readHeader(AbstractFile file, int buffLength)
synchronized static FileTypeDetector getFileTypeDetector()
static int getImageHeight(AbstractFile file)
static boolean isJpegFileHeader(AbstractFile file)
static final ConcurrentHashMap< Long, File > cacheFileMap
static final List< String > GIF_EXTENSION_LIST
static boolean isPngFileHeader(AbstractFile file)
static< T > T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor< T > propertyExtractor)
static final BufferedImage DEFAULT_THUMBNAIL
static Task< javafx.scene.image.Image > newReadImageTask(AbstractFile file)
static SortedSet< String > getSupportedImageMimeTypes()
synchronized static Logger getLogger(String name)
Definition Logger.java:124
static void show(String title, String message, MessageType type, ActionListener actionListener)
static boolean isVideoThumbnailSupported(AbstractFile file)
static List< String > getSupportedVideoExtensions()

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.