19 package org.sleuthkit.autopsy.contentviewers;
21 import java.awt.EventQueue;
22 import java.awt.event.ActionEvent;
23 import java.awt.image.BufferedImage;
24 import java.beans.PropertyChangeEvent;
25 import java.beans.PropertyChangeListener;
26 import java.beans.PropertyChangeSupport;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.List;
34 import static java.util.Objects.nonNull;
35 import java.util.SortedSet;
36 import java.util.concurrent.ExecutionException;
37 import java.util.logging.Level;
38 import java.util.stream.Collectors;
39 import javafx.application.Platform;
40 import javafx.collections.ListChangeListener.Change;
41 import javafx.concurrent.Task;
42 import javafx.embed.swing.JFXPanel;
43 import javafx.geometry.Pos;
44 import javafx.geometry.Rectangle2D;
45 import javafx.scene.Cursor;
46 import javafx.scene.Group;
47 import javafx.scene.Scene;
48 import javafx.scene.control.Button;
49 import javafx.scene.control.Label;
50 import javafx.scene.control.ProgressBar;
51 import javafx.scene.control.ScrollPane;
52 import javafx.scene.control.ScrollPane.ScrollBarPolicy;
53 import javafx.scene.image.Image;
54 import javafx.scene.image.ImageView;
55 import javafx.scene.layout.VBox;
56 import javafx.scene.transform.Rotate;
57 import javafx.scene.transform.Scale;
58 import javafx.scene.transform.Translate;
59 import javax.imageio.ImageIO;
60 import javax.swing.JFileChooser;
61 import javafx.scene.Node;
62 import javax.swing.JMenuItem;
63 import javax.swing.JOptionPane;
64 import javax.swing.JPanel;
65 import javax.swing.JPopupMenu;
66 import javax.swing.JSeparator;
67 import javax.swing.SwingUtilities;
68 import javax.swing.SwingWorker;
69 import org.apache.commons.io.FilenameUtils;
70 import org.controlsfx.control.MaskerPane;
71 import org.openide.util.NbBundle;
72 import org.python.google.common.collect.Lists;
100 @NbBundle.Messages({
"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E",
101 "MediaViewImagePanel.errorLabel.text=Could not load file into Media View.",
102 "MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory."})
103 @SuppressWarnings(
"PMD.SingularField")
104 class MediaViewImagePanel
extends JPanel implements MediaFileViewer.MediaViewPanel {
106 private static final Image EXTERNAL =
new Image(MediaViewImagePanel.class.getResource(
"/org/sleuthkit/autopsy/images/external.png").toExternalForm());
107 private final static Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName());
109 private final boolean fxInited;
111 private JFXPanel fxPanel;
112 private AbstractFile file;
113 private Group masterGroup;
114 private ImageTagsGroup tagsGroup;
115 private ImageTagCreator imageTagCreator;
116 private ImageView fxImageView;
117 private ScrollPane scrollPane;
118 private final ProgressBar progressBar =
new ProgressBar();
119 private final MaskerPane maskerPane =
new MaskerPane();
121 private final JPopupMenu imageTaggingOptions =
new JPopupMenu();
122 private final JMenuItem createTagMenuItem;
123 private final JMenuItem deleteTagMenuItem;
124 private final JMenuItem hideTagsMenuItem;
125 private final JMenuItem exportTagsMenuItem;
127 private final JFileChooser exportChooser;
129 private final PropertyChangeSupport pcs =
new PropertyChangeSupport(
this);
131 private double zoomRatio;
132 private double rotation;
134 private boolean autoResize =
true;
136 private static final double[] ZOOM_STEPS = {
137 0.0625, 0.125, 0.25, 0.375, 0.5, 0.75,
138 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10};
140 private static final double MIN_ZOOM_RATIO = 0.0625;
141 private static final double MAX_ZOOM_RATIO = 10.0;
144 ImageIO.scanForPlugins();
151 static private final SortedSet<String> supportedMimes = ImageUtils.getSupportedImageMimeTypes();
156 static private final List<String> supportedExtensions = ImageUtils.getSupportedImageExtensions().stream()
158 .collect(Collectors.toList());
160 private Task<Image> readImageTask;
166 "MediaViewImagePanel.createTagOption=Create",
167 "MediaViewImagePanel.deleteTagOption=Delete",
168 "MediaViewImagePanel.hideTagOption=Hide",
169 "MediaViewImagePanel.exportTagOption=Export"
171 public MediaViewImagePanel() {
175 exportChooser =
new JFileChooser();
176 exportChooser.setDialogTitle(Bundle.MediaViewImagePanel_fileChooserTitle());
179 createTagMenuItem =
new JMenuItem(Bundle.MediaViewImagePanel_createTagOption());
180 createTagMenuItem.addActionListener((event) -> createTag());
181 imageTaggingOptions.add(createTagMenuItem);
183 imageTaggingOptions.add(
new JSeparator());
185 deleteTagMenuItem =
new JMenuItem(Bundle.MediaViewImagePanel_deleteTagOption());
186 deleteTagMenuItem.addActionListener((event) -> deleteTag());
187 imageTaggingOptions.add(deleteTagMenuItem);
189 imageTaggingOptions.add(
new JSeparator());
191 hideTagsMenuItem =
new JMenuItem(Bundle.MediaViewImagePanel_hideTagOption());
192 hideTagsMenuItem.addActionListener((event) -> showOrHideTags());
193 imageTaggingOptions.add(hideTagsMenuItem);
195 imageTaggingOptions.add(
new JSeparator());
197 exportTagsMenuItem =
new JMenuItem(Bundle.MediaViewImagePanel_exportTagOption());
198 exportTagsMenuItem.addActionListener((event) -> exportTags());
199 imageTaggingOptions.add(exportTagsMenuItem);
201 imageTaggingOptions.setPopupSize(300, 150);
204 if (!PlatformUtil.isWindowsOS() || !OpenCvLoader.openCvIsLoaded()) {
205 tagsMenu.setEnabled(
false);
206 imageTaggingOptions.setEnabled(
false);
210 Platform.runLater(
new Runnable() {
214 fxImageView =
new ImageView();
215 masterGroup =
new Group(fxImageView);
216 tagsGroup =
new ImageTagsGroup(fxImageView);
217 tagsGroup.getChildren().addListener((Change<? extends Node> c) -> {
218 if (c.getList().isEmpty()) {
219 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
220 "state", null, State.EMPTY));
224 subscribeTagMenuItemsToStateChanges();
226 masterGroup.getChildren().add(tagsGroup);
229 tagsGroup.addFocusChangeListener((event) -> {
230 if (event.getPropertyName().equals(ImageTagControls.NOT_FOCUSED.getName())) {
231 if (masterGroup.getChildren().contains(imageTagCreator)) {
235 if (tagsGroup.getChildren().isEmpty()) {
236 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
237 "state", null, State.EMPTY));
239 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
240 "state", null, State.CREATE));
242 }
else if (event.getPropertyName().equals(ImageTagControls.FOCUSED.getName())) {
243 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
244 "state", null, State.SELECTED));
248 scrollPane =
new ScrollPane(masterGroup);
249 scrollPane.getStyleClass().add(
"bg");
250 scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
251 scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
253 fxPanel =
new JFXPanel();
254 Scene scene =
new Scene(scrollPane);
255 scene.getStylesheets().add(MediaViewImagePanel.class.getResource(
"MediaViewImagePanel.css").toExternalForm());
256 fxPanel.setScene(scene);
258 fxImageView.setSmooth(
true);
259 fxImageView.setCache(
true);
261 EventQueue.invokeLater(() -> {
274 private void subscribeTagMenuItemsToStateChanges() {
275 pcs.addPropertyChangeListener((event) -> {
276 State currentState = (State) event.getNewValue();
277 switch (currentState) {
279 createTagMenuItem.setEnabled(
true);
280 deleteTagMenuItem.setEnabled(
false);
281 hideTagsMenuItem.setEnabled(
true);
282 exportTagsMenuItem.setEnabled(
true);
285 if (masterGroup.getChildren().contains(imageTagCreator)) {
286 imageTagCreator.disconnect();
287 masterGroup.getChildren().remove(imageTagCreator);
289 createTagMenuItem.setEnabled(
false);
290 deleteTagMenuItem.setEnabled(
true);
291 hideTagsMenuItem.setEnabled(
true);
292 exportTagsMenuItem.setEnabled(
true);
295 createTagMenuItem.setEnabled(
false);
296 deleteTagMenuItem.setEnabled(
false);
297 hideTagsMenuItem.setEnabled(
true);
298 hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
299 exportTagsMenuItem.setEnabled(
false);
302 createTagMenuItem.setEnabled(
true);
303 deleteTagMenuItem.setEnabled(
false);
304 hideTagsMenuItem.setEnabled(
true);
305 hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
306 exportTagsMenuItem.setEnabled(
true);
310 if (masterGroup.getChildren().contains(imageTagCreator)) {
311 imageTagCreator.disconnect();
313 createTagMenuItem.setEnabled(
true);
314 deleteTagMenuItem.setEnabled(
false);
315 hideTagsMenuItem.setEnabled(
false);
316 hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
317 exportTagsMenuItem.setEnabled(
false);
320 createTagMenuItem.setEnabled(
true);
321 deleteTagMenuItem.setEnabled(
false);
322 hideTagsMenuItem.setEnabled(
true);
323 exportTagsMenuItem.setEnabled(
true);
326 createTagMenuItem.setEnabled(
false);
327 deleteTagMenuItem.setEnabled(
false);
328 hideTagsMenuItem.setEnabled(
false);
329 exportTagsMenuItem.setEnabled(
false);
337 public boolean isInited() {
344 public void reset() {
345 Platform.runLater(() -> {
346 fxImageView.setViewport(
new Rectangle2D(0, 0, 0, 0));
347 fxImageView.setImage(null);
348 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
349 "state", null, State.DEFAULT));
350 masterGroup.getChildren().clear();
351 scrollPane.setContent(null);
352 scrollPane.setContent(masterGroup);
356 private void showErrorNode(String errorMessage, AbstractFile file) {
357 final Button externalViewerButton =
new Button(Bundle.MediaViewImagePanel_externalViewerButton_text(),
new ImageView(EXTERNAL));
358 externalViewerButton.setOnAction(actionEvent
363 new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(),
new FileNode(file))
364 .actionPerformed(
new ActionEvent(
this, ActionEvent.ACTION_PERFORMED,
""))
367 final VBox errorNode =
new VBox(10,
new Label(errorMessage), externalViewerButton);
368 errorNode.setAlignment(Pos.CENTER);
376 void showImageFx(
final AbstractFile file) {
381 Platform.runLater(() -> {
382 if (readImageTask != null) {
383 readImageTask.cancel();
385 readImageTask = ImageUtils.newReadImageTask(file);
386 readImageTask.setOnSucceeded(succeeded -> {
387 if (!Case.isCaseOpen()) {
400 Image fxImage = readImageTask.get();
401 masterGroup.getChildren().clear();
402 tagsGroup.getChildren().clear();
404 if (nonNull(fxImage)) {
406 fxImageView.setImage(fxImage);
408 masterGroup.getChildren().add(fxImageView);
409 masterGroup.getChildren().add(tagsGroup);
412 List<ContentTag> tags = Case.getCurrentCase().getServices()
413 .getTagsManager().getContentTagsByContent(file);
415 List<ContentViewerTag<ImageTagRegion>> contentViewerTags = getContentViewerTags(tags);
417 tagsGroup = buildImageTagsGroup(contentViewerTags);
418 if (!tagsGroup.getChildren().isEmpty()) {
419 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
420 "state", null, State.NONEMPTY));
422 }
catch (TskCoreException | NoCurrentCaseException ex) {
423 LOGGER.log(Level.WARNING,
"Could not retrieve image tags for file in case db", ex);
425 scrollPane.setContent(masterGroup);
427 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
429 }
catch (InterruptedException | ExecutionException ex) {
430 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
432 scrollPane.setCursor(Cursor.DEFAULT);
434 readImageTask.setOnFailed(failed -> {
435 if (!Case.isCaseOpen()) {
445 Throwable exception = readImageTask.getException();
446 if (exception instanceof OutOfMemoryError
447 && exception.getMessage().contains(
"Java heap space")) {
448 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_OOMText(), file);
450 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
453 scrollPane.setCursor(Cursor.DEFAULT);
456 maskerPane.setProgressNode(progressBar);
457 progressBar.progressProperty().bind(readImageTask.progressProperty());
458 maskerPane.textProperty().bind(readImageTask.messageProperty());
459 scrollPane.setContent(null);
460 scrollPane.setCursor(Cursor.WAIT);
461 new Thread(readImageTask).start();
476 private List<ContentViewerTag<ImageTagRegion>> getContentViewerTags(List<ContentTag> contentTags)
477 throws TskCoreException, NoCurrentCaseException {
478 List<ContentViewerTag<ImageTagRegion>> contentViewerTags =
new ArrayList<>();
479 for (ContentTag contentTag : contentTags) {
480 ContentViewerTag<ImageTagRegion> contentViewerTag = ContentViewerTagManager
481 .getTag(contentTag, ImageTagRegion.class);
482 if (contentViewerTag == null) {
486 contentViewerTags.add(contentViewerTag);
488 return contentViewerTags;
502 private ImageTagsGroup buildImageTagsGroup(List<ContentViewerTag<ImageTagRegion>> contentViewerTags) {
504 contentViewerTags.forEach(contentViewerTag -> {
509 tagsGroup.getChildren().add(buildImageTag(contentViewerTag));
519 public List<String> getSupportedMimeTypes() {
520 return Collections.unmodifiableList(Lists.newArrayList(supportedMimes));
529 public List<String> getSupportedExtensions() {
530 return getExtensions();
538 public List<String> getExtensions() {
539 return Collections.unmodifiableList(supportedExtensions);
543 public boolean isSupported(AbstractFile file) {
544 return ImageUtils.isImageThumbnailSupported(file);
552 @SuppressWarnings(
"unchecked")
554 private
void initComponents() {
556 toolbar =
new javax.swing.JToolBar();
557 rotationTextField =
new javax.swing.JTextField();
558 rotateLeftButton =
new javax.swing.JButton();
559 rotateRightButton =
new javax.swing.JButton();
560 jSeparator1 =
new javax.swing.JToolBar.Separator();
561 zoomTextField =
new javax.swing.JTextField();
562 zoomOutButton =
new javax.swing.JButton();
563 zoomInButton =
new javax.swing.JButton();
564 jSeparator2 =
new javax.swing.JToolBar.Separator();
565 zoomResetButton =
new javax.swing.JButton();
566 filler1 =
new javax.swing.Box.Filler(
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0));
567 filler2 =
new javax.swing.Box.Filler(
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0),
new java.awt.Dimension(32767, 0));
568 jPanel1 =
new javax.swing.JPanel();
569 tagsMenu =
new javax.swing.JButton();
571 setBackground(
new java.awt.Color(0, 0, 0));
572 addComponentListener(
new java.awt.event.ComponentAdapter() {
573 public void componentResized(java.awt.event.ComponentEvent evt) {
574 formComponentResized(evt);
577 setLayout(
new javax.swing.BoxLayout(
this, javax.swing.BoxLayout.Y_AXIS));
579 toolbar.setFloatable(
false);
580 toolbar.setRollover(
true);
581 toolbar.setMaximumSize(
new java.awt.Dimension(32767, 23));
584 rotationTextField.setEditable(
false);
585 rotationTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
586 rotationTextField.setText(
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotationTextField.text"));
587 rotationTextField.setMaximumSize(
new java.awt.Dimension(50, 2147483647));
588 rotationTextField.setMinimumSize(
new java.awt.Dimension(50, 20));
589 rotationTextField.setPreferredSize(
new java.awt.Dimension(50, 20));
590 toolbar.add(rotationTextField);
592 rotateLeftButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/rotate-left.png")));
593 org.openide.awt.Mnemonics.setLocalizedText(rotateLeftButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotateLeftButton.text"));
594 rotateLeftButton.setToolTipText(
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotateLeftButton.toolTipText"));
595 rotateLeftButton.setFocusable(
false);
596 rotateLeftButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
597 rotateLeftButton.setMaximumSize(
new java.awt.Dimension(24, 24));
598 rotateLeftButton.setMinimumSize(
new java.awt.Dimension(24, 24));
599 rotateLeftButton.setPreferredSize(
new java.awt.Dimension(24, 24));
600 rotateLeftButton.addActionListener(
new java.awt.event.ActionListener() {
601 public void actionPerformed(java.awt.event.ActionEvent evt) {
602 rotateLeftButtonActionPerformed(evt);
605 toolbar.add(rotateLeftButton);
607 rotateRightButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/rotate-right.png")));
608 org.openide.awt.Mnemonics.setLocalizedText(rotateRightButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotateRightButton.text"));
609 rotateRightButton.setFocusable(
false);
610 rotateRightButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
611 rotateRightButton.setMaximumSize(
new java.awt.Dimension(24, 24));
612 rotateRightButton.setMinimumSize(
new java.awt.Dimension(24, 24));
613 rotateRightButton.setPreferredSize(
new java.awt.Dimension(24, 24));
614 rotateRightButton.addActionListener(
new java.awt.event.ActionListener() {
615 public void actionPerformed(java.awt.event.ActionEvent evt) {
616 rotateRightButtonActionPerformed(evt);
619 toolbar.add(rotateRightButton);
621 jSeparator1.setMaximumSize(
new java.awt.Dimension(6, 20));
622 toolbar.add(jSeparator1);
624 zoomTextField.setEditable(
false);
625 zoomTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
626 zoomTextField.setText(
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomTextField.text"));
627 zoomTextField.setMaximumSize(
new java.awt.Dimension(50, 2147483647));
628 zoomTextField.setMinimumSize(
new java.awt.Dimension(50, 20));
629 zoomTextField.setPreferredSize(
new java.awt.Dimension(50, 20));
630 toolbar.add(zoomTextField);
632 zoomOutButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/zoom-out.png")));
633 org.openide.awt.Mnemonics.setLocalizedText(zoomOutButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomOutButton.text"));
634 zoomOutButton.setFocusable(
false);
635 zoomOutButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
636 zoomOutButton.setMaximumSize(
new java.awt.Dimension(24, 24));
637 zoomOutButton.setMinimumSize(
new java.awt.Dimension(24, 24));
638 zoomOutButton.setPreferredSize(
new java.awt.Dimension(24, 24));
639 zoomOutButton.addActionListener(
new java.awt.event.ActionListener() {
640 public void actionPerformed(java.awt.event.ActionEvent evt) {
641 zoomOutButtonActionPerformed(evt);
644 toolbar.add(zoomOutButton);
646 zoomInButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/zoom-in.png")));
647 org.openide.awt.Mnemonics.setLocalizedText(zoomInButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomInButton.text"));
648 zoomInButton.setFocusable(
false);
649 zoomInButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
650 zoomInButton.setMaximumSize(
new java.awt.Dimension(24, 24));
651 zoomInButton.setMinimumSize(
new java.awt.Dimension(24, 24));
652 zoomInButton.setPreferredSize(
new java.awt.Dimension(24, 24));
653 zoomInButton.addActionListener(
new java.awt.event.ActionListener() {
654 public void actionPerformed(java.awt.event.ActionEvent evt) {
655 zoomInButtonActionPerformed(evt);
658 toolbar.add(zoomInButton);
660 jSeparator2.setMaximumSize(
new java.awt.Dimension(6, 20));
661 toolbar.add(jSeparator2);
663 org.openide.awt.Mnemonics.setLocalizedText(zoomResetButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomResetButton.text"));
664 zoomResetButton.setFocusable(
false);
665 zoomResetButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
666 zoomResetButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
667 zoomResetButton.addActionListener(
new java.awt.event.ActionListener() {
668 public void actionPerformed(java.awt.event.ActionEvent evt) {
669 zoomResetButtonActionPerformed(evt);
672 toolbar.add(zoomResetButton);
673 toolbar.add(filler1);
674 toolbar.add(filler2);
675 toolbar.add(jPanel1);
677 org.openide.awt.Mnemonics.setLocalizedText(tagsMenu,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.tagsMenu.text_1"));
678 tagsMenu.setFocusable(
false);
679 tagsMenu.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
680 tagsMenu.setMaximumSize(
new java.awt.Dimension(75, 21));
681 tagsMenu.setMinimumSize(
new java.awt.Dimension(75, 21));
682 tagsMenu.setPreferredSize(
new java.awt.Dimension(75, 21));
683 tagsMenu.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
684 tagsMenu.addMouseListener(
new java.awt.event.MouseAdapter() {
685 public void mousePressed(java.awt.event.MouseEvent evt) {
686 tagsMenuMousePressed(evt);
689 toolbar.add(tagsMenu);
694 private void rotateLeftButtonActionPerformed(java.awt.event.ActionEvent evt) {
697 rotation = (rotation + 270) % 360;
701 private void rotateRightButtonActionPerformed(java.awt.event.ActionEvent evt) {
704 rotation = (rotation + 90) % 360;
708 private void zoomInButtonActionPerformed(java.awt.event.ActionEvent evt) {
711 for (
int i = 0; i < ZOOM_STEPS.length; i++) {
712 if (zoomRatio < ZOOM_STEPS[i]) {
713 zoomRatio = ZOOM_STEPS[i];
720 private void zoomOutButtonActionPerformed(java.awt.event.ActionEvent evt) {
723 for (
int i = ZOOM_STEPS.length - 1; i >= 0; i--) {
724 if (zoomRatio > ZOOM_STEPS[i]) {
725 zoomRatio = ZOOM_STEPS[i];
732 private void zoomResetButtonActionPerformed(java.awt.event.ActionEvent evt) {
737 private void formComponentResized(java.awt.event.ComponentEvent evt) {
749 private void deleteTag() {
750 Platform.runLater(() -> {
751 ImageTag tagInFocus = tagsGroup.getFocus();
752 if (tagInFocus == null) {
757 ContentViewerTag<ImageTagRegion> contentViewerTag = tagInFocus.getContentViewerTag();
758 scrollPane.setCursor(Cursor.WAIT);
759 ContentViewerTagManager.deleteTag(contentViewerTag);
760 Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(contentViewerTag.getContentTag());
761 tagsGroup.getChildren().remove(tagInFocus);
762 }
catch (TskCoreException | NoCurrentCaseException ex) {
763 LOGGER.log(Level.WARNING,
"Could not delete image tag in case db", ex);
766 scrollPane.setCursor(Cursor.DEFAULT);
769 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
770 "state", null, State.CREATE));
777 private void createTag() {
778 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
779 "state", null, State.DISABLE));
780 imageTagCreator =
new ImageTagCreator(fxImageView);
782 PropertyChangeListener newTagListener = (event) -> {
783 SwingUtilities.invokeLater(() -> {
784 ImageTagRegion tag = (ImageTagRegion) event.getNewValue();
786 TagNameAndComment result = GetTagNameAndCommentDialog.doDialog();
787 if (result != null) {
789 Platform.runLater(() -> {
791 scrollPane.setCursor(Cursor.WAIT);
792 ContentViewerTag<ImageTagRegion> contentViewerTag = storeImageTag(tag, result);
793 ImageTag imageTag = buildImageTag(contentViewerTag);
794 tagsGroup.getChildren().add(imageTag);
795 }
catch (TskCoreException | SerializationException | NoCurrentCaseException ex) {
796 LOGGER.log(Level.WARNING,
"Could not save new image tag in case db", ex);
799 scrollPane.setCursor(Cursor.DEFAULT);
803 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
804 "state", null, State.CREATE));
808 Platform.runLater(() -> {
809 imageTagCreator.disconnect();
810 masterGroup.getChildren().remove(imageTagCreator);
814 imageTagCreator.addNewTagListener(newTagListener);
815 Platform.runLater(() -> masterGroup.getChildren().add(imageTagCreator));
825 private ImageTag buildImageTag(ContentViewerTag<ImageTagRegion> contentViewerTag) {
826 ImageTag imageTag =
new ImageTag(contentViewerTag, fxImageView);
829 imageTag.subscribeToEditEvents((edit) -> {
831 scrollPane.setCursor(Cursor.WAIT);
832 ImageTagRegion newRegion = (ImageTagRegion) edit.getNewValue();
833 ContentViewerTagManager.updateTag(contentViewerTag, newRegion);
834 }
catch (SerializationException | TskCoreException | NoCurrentCaseException ex) {
835 LOGGER.log(Level.WARNING,
"Could not save edit for image tag in case db", ex);
837 scrollPane.setCursor(Cursor.DEFAULT);
849 private ContentViewerTag<ImageTagRegion> storeImageTag(ImageTagRegion data, TagNameAndComment result)
850 throws TskCoreException, SerializationException, NoCurrentCaseException {
851 scrollPane.setCursor(Cursor.WAIT);
853 ContentTag contentTag = Case.getCurrentCaseThrows().getServices().getTagsManager()
854 .addContentTag(file, result.getTagName(), result.getComment());
855 return ContentViewerTagManager.saveTag(contentTag, data);
857 scrollPane.setCursor(Cursor.DEFAULT);
865 private void showOrHideTags() {
866 Platform.runLater(() -> {
867 if (DisplayOptions.HIDE_TAGS.getName().equals(hideTagsMenuItem.getText())) {
869 masterGroup.getChildren().remove(tagsGroup);
870 hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
871 tagsGroup.clearFocus();
872 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
873 "state", null, State.HIDDEN));
876 masterGroup.getChildren().add(tagsGroup);
877 hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
878 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
879 "state", null, State.VISIBLE));
885 "MediaViewImagePanel.exportSaveText=Save",
886 "MediaViewImagePanel.successfulExport=Tagged image was successfully saved.",
887 "MediaViewImagePanel.unsuccessfulExport=Unable to export tagged image to disk.",
888 "MediaViewImagePanel.fileChooserTitle=Choose a save location"
890 private void exportTags() {
891 tagsGroup.clearFocus();
892 exportChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
894 exportChooser.setCurrentDirectory(
new File(Case.getCurrentCase().getExportDirectory()));
895 int returnVal = exportChooser.showDialog(
this, Bundle.MediaViewImagePanel_exportSaveText());
896 if (returnVal == JFileChooser.APPROVE_OPTION) {
897 new SwingWorker<Void, Void>() {
899 protected Void doInBackground() {
902 List<ContentTag> tags = Case.getCurrentCase().getServices()
903 .getTagsManager().getContentTagsByContent(file);
904 List<ContentViewerTag<ImageTagRegion>> contentViewerTags = getContentViewerTags(tags);
907 Collection<ImageTagRegion> regions = contentViewerTags.stream()
908 .map(cvTag -> cvTag.getDetails()).collect(Collectors.toList());
911 BufferedImage taggedImage = ImageTagsUtil.getImageWithTags(file, regions);
912 Path output = Paths.get(exportChooser.getSelectedFile().getPath(),
913 FilenameUtils.getBaseName(file.getName()) +
"-with_tags.png");
914 ImageIO.write(taggedImage,
"png", output.toFile());
916 JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_successfulExport());
917 }
catch (Exception ex) {
919 LOGGER.log(Level.WARNING,
"Unable to export tagged image to disk", ex);
920 JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_unsuccessfulExport());
928 private void tagsMenuMousePressed(java.awt.event.MouseEvent evt) {
929 if (imageTaggingOptions.isEnabled()) {
930 imageTaggingOptions.show(tagsMenu, -300 + tagsMenu.getWidth(), tagsMenu.getHeight() + 3);
937 enum DisplayOptions {
941 private final String name;
943 DisplayOptions(String name) {
968 private javax.swing.Box.Filler filler1;
969 private javax.swing.Box.Filler filler2;
970 private javax.swing.JPanel jPanel1;
971 private javax.swing.JToolBar.Separator jSeparator1;
972 private javax.swing.JToolBar.Separator jSeparator2;
973 private javax.swing.JButton rotateLeftButton;
974 private javax.swing.JButton rotateRightButton;
975 private javax.swing.JTextField rotationTextField;
976 private javax.swing.JButton tagsMenu;
977 private javax.swing.JToolBar toolbar;
978 private javax.swing.JButton zoomInButton;
979 private javax.swing.JButton zoomOutButton;
980 private javax.swing.JButton zoomResetButton;
981 private javax.swing.JTextField zoomTextField;
992 private void resetView() {
993 Image image = fxImageView.getImage();
998 double imageWidth = image.getWidth();
999 double imageHeight = image.getHeight();
1000 double scrollPaneWidth = fxPanel.getWidth();
1001 double scrollPaneHeight = fxPanel.getHeight();
1002 double zoomRatioWidth = scrollPaneWidth / imageWidth;
1003 double zoomRatioHeight = scrollPaneHeight / imageHeight;
1006 zoomRatio = zoomRatioWidth < zoomRatioHeight ? zoomRatioWidth : zoomRatioHeight;
1010 scrollPane.setHvalue(0);
1011 scrollPane.setVvalue(0);
1026 private void updateView() {
1027 Image image = fxImageView.getImage();
1028 if (image == null) {
1033 double imageWidth = image.getWidth();
1034 double imageHeight = image.getHeight();
1037 double adjustedImageWidth = imageWidth * zoomRatio;
1038 double adjustedImageHeight = imageHeight * zoomRatio;
1041 double viewportWidth;
1042 double viewportHeight;
1045 double panelWidth = fxPanel.getWidth();
1046 double panelHeight = fxPanel.getHeight();
1049 double centerOffsetX = (panelWidth / 2) - (imageWidth / 2);
1050 double centerOffsetY = (panelHeight / 2) - (imageHeight / 2);
1057 double scrollX = scrollPane.getHvalue();
1058 double scrollY = scrollPane.getVvalue();
1065 if ((rotation % 180) == 0) {
1067 viewportWidth = adjustedImageWidth;
1068 viewportHeight = adjustedImageHeight;
1069 leftOffsetX = (adjustedImageWidth - imageWidth) / 2;
1070 topOffsetY = (adjustedImageHeight - imageHeight) / 2;
1071 maxScrollX = (adjustedImageWidth - panelWidth) / (imageWidth - panelWidth);
1072 maxScrollY = (adjustedImageHeight - panelHeight) / (imageHeight - panelHeight);
1075 viewportWidth = adjustedImageHeight;
1076 viewportHeight = adjustedImageWidth;
1077 leftOffsetX = (adjustedImageHeight - imageWidth) / 2;
1078 topOffsetY = (adjustedImageWidth - imageHeight) / 2;
1079 maxScrollX = (adjustedImageHeight - panelWidth) / (imageWidth - panelWidth);
1080 maxScrollY = (adjustedImageWidth - panelHeight) / (imageHeight - panelHeight);
1084 if (viewportWidth < imageWidth) {
1085 viewportWidth = imageWidth;
1086 if (scrollX > maxScrollX) {
1087 scrollX = maxScrollX;
1090 if (viewportHeight < imageHeight) {
1091 viewportHeight = imageHeight;
1092 if (scrollY > maxScrollY) {
1093 scrollY = maxScrollY;
1098 fxImageView.setViewport(
new Rectangle2D(
1099 0, 0, viewportWidth, viewportHeight));
1102 Scale scale =
new Scale();
1103 scale.setX(zoomRatio);
1104 scale.setY(zoomRatio);
1105 scale.setPivotX(imageWidth / 2);
1106 scale.setPivotY(imageHeight / 2);
1109 Rotate rotate =
new Rotate();
1110 rotate.setPivotX(imageWidth / 2);
1111 rotate.setPivotY(imageHeight / 2);
1112 rotate.setAngle(rotation);
1115 Translate translate =
new Translate();
1116 translate.setX(viewportWidth > fxPanel.getWidth() ? leftOffsetX : centerOffsetX);
1117 translate.setY(viewportHeight > fxPanel.getHeight() ? topOffsetY : centerOffsetY);
1122 masterGroup.getTransforms().clear();
1123 masterGroup.getTransforms().addAll(translate, rotate, scale);
1126 if (viewportWidth > fxPanel.getWidth()) {
1127 scrollPane.setHvalue(scrollX);
1129 if (viewportHeight > fxPanel.getHeight()) {
1130 scrollPane.setVvalue(scrollY);
1134 zoomOutButton.setEnabled(zoomRatio > MIN_ZOOM_RATIO);
1135 zoomInButton.setEnabled(zoomRatio < MAX_ZOOM_RATIO);
1136 rotationTextField.setText((
int) rotation +
"°");
1137 zoomTextField.setText((Math.round(zoomRatio * 100.0)) +
"%");
static boolean isJavaFxInited()