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 static final double[] ZOOM_STEPS = {
135 0.0625, 0.125, 0.25, 0.375, 0.5, 0.75,
136 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10};
138 private static final double MIN_ZOOM_RATIO = 0.0625;
139 private static final double MAX_ZOOM_RATIO = 10.0;
142 ImageIO.scanForPlugins();
149 static private final SortedSet<String> supportedMimes = ImageUtils.getSupportedImageMimeTypes();
154 static private final List<String> supportedExtensions = ImageUtils.getSupportedImageExtensions().stream()
156 .collect(Collectors.toList());
158 private Task<Image> readImageTask;
164 "MediaViewImagePanel.createTagOption=Create",
165 "MediaViewImagePanel.deleteTagOption=Delete",
166 "MediaViewImagePanel.hideTagOption=Hide",
167 "MediaViewImagePanel.exportTagOption=Export"
169 public MediaViewImagePanel() {
173 exportChooser =
new JFileChooser();
174 exportChooser.setDialogTitle(Bundle.MediaViewImagePanel_fileChooserTitle());
177 createTagMenuItem =
new JMenuItem(Bundle.MediaViewImagePanel_createTagOption());
178 createTagMenuItem.addActionListener((event) -> createTag());
179 imageTaggingOptions.add(createTagMenuItem);
181 imageTaggingOptions.add(
new JSeparator());
183 deleteTagMenuItem =
new JMenuItem(Bundle.MediaViewImagePanel_deleteTagOption());
184 deleteTagMenuItem.addActionListener((event) -> deleteTag());
185 imageTaggingOptions.add(deleteTagMenuItem);
187 imageTaggingOptions.add(
new JSeparator());
189 hideTagsMenuItem =
new JMenuItem(Bundle.MediaViewImagePanel_hideTagOption());
190 hideTagsMenuItem.addActionListener((event) -> showOrHideTags());
191 imageTaggingOptions.add(hideTagsMenuItem);
193 imageTaggingOptions.add(
new JSeparator());
195 exportTagsMenuItem =
new JMenuItem(Bundle.MediaViewImagePanel_exportTagOption());
196 exportTagsMenuItem.addActionListener((event) -> exportTags());
197 imageTaggingOptions.add(exportTagsMenuItem);
199 imageTaggingOptions.setPopupSize(300, 150);
202 if (!PlatformUtil.isWindowsOS() || !OpenCvLoader.openCvIsLoaded()) {
203 tagsMenu.setEnabled(
false);
204 imageTaggingOptions.setEnabled(
false);
208 Platform.runLater(
new Runnable() {
212 fxImageView =
new ImageView();
213 masterGroup =
new Group(fxImageView);
214 tagsGroup =
new ImageTagsGroup(fxImageView);
215 tagsGroup.getChildren().addListener((Change<? extends Node> c) -> {
216 if (c.getList().isEmpty()) {
217 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
218 "state", null, State.EMPTY));
222 subscribeTagMenuItemsToStateChanges();
224 masterGroup.getChildren().add(tagsGroup);
227 tagsGroup.addFocusChangeListener((event) -> {
228 if (event.getPropertyName().equals(ImageTagControls.NOT_FOCUSED.getName())) {
229 if (masterGroup.getChildren().contains(imageTagCreator)) {
233 if (tagsGroup.getChildren().isEmpty()) {
234 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
235 "state", null, State.EMPTY));
237 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
238 "state", null, State.CREATE));
240 }
else if (event.getPropertyName().equals(ImageTagControls.FOCUSED.getName())) {
241 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
242 "state", null, State.SELECTED));
246 scrollPane =
new ScrollPane(masterGroup);
247 scrollPane.getStyleClass().add(
"bg");
248 scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
249 scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
251 fxPanel =
new JFXPanel();
252 Scene scene =
new Scene(scrollPane);
253 scene.getStylesheets().add(MediaViewImagePanel.class.getResource(
"MediaViewImagePanel.css").toExternalForm());
254 fxPanel.setScene(scene);
256 fxImageView.setSmooth(
true);
257 fxImageView.setCache(
true);
259 EventQueue.invokeLater(() -> {
272 private void subscribeTagMenuItemsToStateChanges() {
273 pcs.addPropertyChangeListener((event) -> {
274 State currentState = (State) event.getNewValue();
275 switch (currentState) {
277 createTagMenuItem.setEnabled(
true);
278 deleteTagMenuItem.setEnabled(
false);
279 hideTagsMenuItem.setEnabled(
true);
280 exportTagsMenuItem.setEnabled(
true);
283 if (masterGroup.getChildren().contains(imageTagCreator)) {
284 imageTagCreator.disconnect();
285 masterGroup.getChildren().remove(imageTagCreator);
287 createTagMenuItem.setEnabled(
false);
288 deleteTagMenuItem.setEnabled(
true);
289 hideTagsMenuItem.setEnabled(
true);
290 exportTagsMenuItem.setEnabled(
true);
293 createTagMenuItem.setEnabled(
false);
294 deleteTagMenuItem.setEnabled(
false);
295 hideTagsMenuItem.setEnabled(
true);
296 hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
297 exportTagsMenuItem.setEnabled(
false);
300 createTagMenuItem.setEnabled(
true);
301 deleteTagMenuItem.setEnabled(
false);
302 hideTagsMenuItem.setEnabled(
true);
303 hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
304 exportTagsMenuItem.setEnabled(
true);
308 if (masterGroup.getChildren().contains(imageTagCreator)) {
309 imageTagCreator.disconnect();
311 createTagMenuItem.setEnabled(
true);
312 deleteTagMenuItem.setEnabled(
false);
313 hideTagsMenuItem.setEnabled(
false);
314 hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
315 exportTagsMenuItem.setEnabled(
false);
318 createTagMenuItem.setEnabled(
true);
319 deleteTagMenuItem.setEnabled(
false);
320 hideTagsMenuItem.setEnabled(
true);
321 exportTagsMenuItem.setEnabled(
true);
324 createTagMenuItem.setEnabled(
false);
325 deleteTagMenuItem.setEnabled(
false);
326 hideTagsMenuItem.setEnabled(
false);
327 exportTagsMenuItem.setEnabled(
false);
335 public boolean isInited() {
342 public void reset() {
343 Platform.runLater(() -> {
344 fxImageView.setViewport(
new Rectangle2D(0, 0, 0, 0));
345 fxImageView.setImage(null);
346 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
347 "state", null, State.DEFAULT));
348 masterGroup.getChildren().clear();
349 scrollPane.setContent(null);
350 scrollPane.setContent(masterGroup);
354 private void showErrorNode(String errorMessage, AbstractFile file) {
355 final Button externalViewerButton =
new Button(Bundle.MediaViewImagePanel_externalViewerButton_text(),
new ImageView(EXTERNAL));
356 externalViewerButton.setOnAction(actionEvent
361 new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(),
new FileNode(file))
362 .actionPerformed(
new ActionEvent(
this, ActionEvent.ACTION_PERFORMED,
""))
365 final VBox errorNode =
new VBox(10,
new Label(errorMessage), externalViewerButton);
366 errorNode.setAlignment(Pos.CENTER);
374 void showImageFx(
final AbstractFile file) {
379 Platform.runLater(() -> {
380 if (readImageTask != null) {
381 readImageTask.cancel();
383 readImageTask = ImageUtils.newReadImageTask(file);
384 readImageTask.setOnSucceeded(succeeded -> {
385 if (!Case.isCaseOpen()) {
397 Image fxImage = readImageTask.get();
398 masterGroup.getChildren().clear();
399 tagsGroup.getChildren().clear();
401 if (nonNull(fxImage)) {
403 fxImageView.setImage(fxImage);
405 masterGroup.getChildren().add(fxImageView);
406 masterGroup.getChildren().add(tagsGroup);
409 List<ContentTag> tags = Case.getCurrentCase().getServices()
410 .getTagsManager().getContentTagsByContent(file);
412 List<ContentViewerTag<ImageTagRegion>> contentViewerTags = getContentViewerTags(tags);
414 tagsGroup = buildImageTagsGroup(contentViewerTags);
415 if (!tagsGroup.getChildren().isEmpty()) {
416 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
417 "state", null, State.NONEMPTY));
419 }
catch (TskCoreException | NoCurrentCaseException ex) {
420 LOGGER.log(Level.WARNING,
"Could not retrieve image tags for file in case db", ex);
422 scrollPane.setContent(masterGroup);
424 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
426 }
catch (InterruptedException | ExecutionException ex) {
427 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
429 scrollPane.setCursor(Cursor.DEFAULT);
431 readImageTask.setOnFailed(failed -> {
432 if (!Case.isCaseOpen()) {
442 Throwable exception = readImageTask.getException();
443 if (exception instanceof OutOfMemoryError
444 && exception.getMessage().contains(
"Java heap space")) {
445 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_OOMText(), file);
447 showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
450 scrollPane.setCursor(Cursor.DEFAULT);
453 maskerPane.setProgressNode(progressBar);
454 progressBar.progressProperty().bind(readImageTask.progressProperty());
455 maskerPane.textProperty().bind(readImageTask.messageProperty());
456 scrollPane.setContent(null);
457 scrollPane.setCursor(Cursor.WAIT);
458 new Thread(readImageTask).start();
471 private List<ContentViewerTag<ImageTagRegion>> getContentViewerTags(List<ContentTag> contentTags)
472 throws TskCoreException, NoCurrentCaseException {
473 List<ContentViewerTag<ImageTagRegion>> contentViewerTags =
new ArrayList<>();
474 for (ContentTag contentTag : contentTags) {
475 ContentViewerTag<ImageTagRegion> contentViewerTag = ContentViewerTagManager
476 .getTag(contentTag, ImageTagRegion.class);
477 if (contentViewerTag == null) {
481 contentViewerTags.add(contentViewerTag);
483 return contentViewerTags;
495 private ImageTagsGroup buildImageTagsGroup(List<ContentViewerTag<ImageTagRegion>> contentViewerTags) {
497 contentViewerTags.forEach(contentViewerTag -> {
502 tagsGroup.getChildren().add(buildImageTag(contentViewerTag));
512 public List<String> getSupportedMimeTypes() {
513 return Collections.unmodifiableList(Lists.newArrayList(supportedMimes));
522 public List<String> getSupportedExtensions() {
523 return getExtensions();
531 public List<String> getExtensions() {
532 return Collections.unmodifiableList(supportedExtensions);
536 public boolean isSupported(AbstractFile file) {
537 return ImageUtils.isImageThumbnailSupported(file);
545 @SuppressWarnings(
"unchecked")
547 private
void initComponents() {
549 toolbar =
new javax.swing.JToolBar();
550 rotationTextField =
new javax.swing.JTextField();
551 rotateLeftButton =
new javax.swing.JButton();
552 rotateRightButton =
new javax.swing.JButton();
553 jSeparator1 =
new javax.swing.JToolBar.Separator();
554 zoomTextField =
new javax.swing.JTextField();
555 zoomOutButton =
new javax.swing.JButton();
556 zoomInButton =
new javax.swing.JButton();
557 jSeparator2 =
new javax.swing.JToolBar.Separator();
558 zoomResetButton =
new javax.swing.JButton();
559 filler1 =
new javax.swing.Box.Filler(
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0));
560 filler2 =
new javax.swing.Box.Filler(
new java.awt.Dimension(0, 0),
new java.awt.Dimension(0, 0),
new java.awt.Dimension(32767, 0));
561 jPanel1 =
new javax.swing.JPanel();
562 tagsMenu =
new javax.swing.JButton();
564 setBackground(
new java.awt.Color(0, 0, 0));
565 addComponentListener(
new java.awt.event.ComponentAdapter() {
566 public void componentResized(java.awt.event.ComponentEvent evt) {
567 formComponentResized(evt);
570 setLayout(
new javax.swing.BoxLayout(
this, javax.swing.BoxLayout.Y_AXIS));
572 toolbar.setFloatable(
false);
573 toolbar.setRollover(
true);
574 toolbar.setMaximumSize(
new java.awt.Dimension(32767, 23));
577 rotationTextField.setEditable(
false);
578 rotationTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
579 rotationTextField.setText(
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotationTextField.text"));
580 rotationTextField.setMaximumSize(
new java.awt.Dimension(50, 2147483647));
581 rotationTextField.setMinimumSize(
new java.awt.Dimension(50, 20));
582 rotationTextField.setPreferredSize(
new java.awt.Dimension(50, 20));
583 toolbar.add(rotationTextField);
585 rotateLeftButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/rotate-left.png")));
586 org.openide.awt.Mnemonics.setLocalizedText(rotateLeftButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotateLeftButton.text"));
587 rotateLeftButton.setToolTipText(
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotateLeftButton.toolTipText"));
588 rotateLeftButton.setFocusable(
false);
589 rotateLeftButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
590 rotateLeftButton.setMaximumSize(
new java.awt.Dimension(24, 24));
591 rotateLeftButton.setMinimumSize(
new java.awt.Dimension(24, 24));
592 rotateLeftButton.setPreferredSize(
new java.awt.Dimension(24, 24));
593 rotateLeftButton.addActionListener(
new java.awt.event.ActionListener() {
594 public void actionPerformed(java.awt.event.ActionEvent evt) {
595 rotateLeftButtonActionPerformed(evt);
598 toolbar.add(rotateLeftButton);
600 rotateRightButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/rotate-right.png")));
601 org.openide.awt.Mnemonics.setLocalizedText(rotateRightButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.rotateRightButton.text"));
602 rotateRightButton.setFocusable(
false);
603 rotateRightButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
604 rotateRightButton.setMaximumSize(
new java.awt.Dimension(24, 24));
605 rotateRightButton.setMinimumSize(
new java.awt.Dimension(24, 24));
606 rotateRightButton.setPreferredSize(
new java.awt.Dimension(24, 24));
607 rotateRightButton.addActionListener(
new java.awt.event.ActionListener() {
608 public void actionPerformed(java.awt.event.ActionEvent evt) {
609 rotateRightButtonActionPerformed(evt);
612 toolbar.add(rotateRightButton);
614 jSeparator1.setMaximumSize(
new java.awt.Dimension(6, 20));
615 toolbar.add(jSeparator1);
617 zoomTextField.setEditable(
false);
618 zoomTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
619 zoomTextField.setText(
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomTextField.text"));
620 zoomTextField.setMaximumSize(
new java.awt.Dimension(50, 2147483647));
621 zoomTextField.setMinimumSize(
new java.awt.Dimension(50, 20));
622 zoomTextField.setPreferredSize(
new java.awt.Dimension(50, 20));
623 toolbar.add(zoomTextField);
625 zoomOutButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/zoom-out.png")));
626 org.openide.awt.Mnemonics.setLocalizedText(zoomOutButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomOutButton.text"));
627 zoomOutButton.setFocusable(
false);
628 zoomOutButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
629 zoomOutButton.setMaximumSize(
new java.awt.Dimension(24, 24));
630 zoomOutButton.setMinimumSize(
new java.awt.Dimension(24, 24));
631 zoomOutButton.setPreferredSize(
new java.awt.Dimension(24, 24));
632 zoomOutButton.addActionListener(
new java.awt.event.ActionListener() {
633 public void actionPerformed(java.awt.event.ActionEvent evt) {
634 zoomOutButtonActionPerformed(evt);
637 toolbar.add(zoomOutButton);
639 zoomInButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/contentviewers/images/zoom-in.png")));
640 org.openide.awt.Mnemonics.setLocalizedText(zoomInButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomInButton.text"));
641 zoomInButton.setFocusable(
false);
642 zoomInButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
643 zoomInButton.setMaximumSize(
new java.awt.Dimension(24, 24));
644 zoomInButton.setMinimumSize(
new java.awt.Dimension(24, 24));
645 zoomInButton.setPreferredSize(
new java.awt.Dimension(24, 24));
646 zoomInButton.addActionListener(
new java.awt.event.ActionListener() {
647 public void actionPerformed(java.awt.event.ActionEvent evt) {
648 zoomInButtonActionPerformed(evt);
651 toolbar.add(zoomInButton);
653 jSeparator2.setMaximumSize(
new java.awt.Dimension(6, 20));
654 toolbar.add(jSeparator2);
656 org.openide.awt.Mnemonics.setLocalizedText(zoomResetButton,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.zoomResetButton.text"));
657 zoomResetButton.setFocusable(
false);
658 zoomResetButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
659 zoomResetButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
660 zoomResetButton.addActionListener(
new java.awt.event.ActionListener() {
661 public void actionPerformed(java.awt.event.ActionEvent evt) {
662 zoomResetButtonActionPerformed(evt);
665 toolbar.add(zoomResetButton);
666 toolbar.add(filler1);
667 toolbar.add(filler2);
668 toolbar.add(jPanel1);
670 org.openide.awt.Mnemonics.setLocalizedText(tagsMenu,
org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class,
"MediaViewImagePanel.tagsMenu.text_1"));
671 tagsMenu.setFocusable(
false);
672 tagsMenu.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
673 tagsMenu.setMaximumSize(
new java.awt.Dimension(75, 21));
674 tagsMenu.setMinimumSize(
new java.awt.Dimension(75, 21));
675 tagsMenu.setPreferredSize(
new java.awt.Dimension(75, 21));
676 tagsMenu.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
677 tagsMenu.addMouseListener(
new java.awt.event.MouseAdapter() {
678 public void mousePressed(java.awt.event.MouseEvent evt) {
679 tagsMenuMousePressed(evt);
682 toolbar.add(tagsMenu);
687 private void rotateLeftButtonActionPerformed(java.awt.event.ActionEvent evt) {
688 rotation = (rotation + 270) % 360;
692 private void rotateRightButtonActionPerformed(java.awt.event.ActionEvent evt) {
693 rotation = (rotation + 90) % 360;
697 private void zoomInButtonActionPerformed(java.awt.event.ActionEvent evt) {
699 for (
int i = 0; i < ZOOM_STEPS.length; i++) {
700 if (zoomRatio < ZOOM_STEPS[i]) {
701 zoomRatio = ZOOM_STEPS[i];
708 private void zoomOutButtonActionPerformed(java.awt.event.ActionEvent evt) {
710 for (
int i = ZOOM_STEPS.length - 1; i >= 0; i--) {
711 if (zoomRatio > ZOOM_STEPS[i]) {
712 zoomRatio = ZOOM_STEPS[i];
719 private void zoomResetButtonActionPerformed(java.awt.event.ActionEvent evt) {
723 private void formComponentResized(java.awt.event.ComponentEvent evt) {
731 private void deleteTag() {
732 Platform.runLater(() -> {
733 ImageTag tagInFocus = tagsGroup.getFocus();
734 if (tagInFocus == null) {
739 ContentViewerTag<ImageTagRegion> contentViewerTag = tagInFocus.getContentViewerTag();
740 scrollPane.setCursor(Cursor.WAIT);
741 ContentViewerTagManager.deleteTag(contentViewerTag);
742 Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(contentViewerTag.getContentTag());
743 tagsGroup.getChildren().remove(tagInFocus);
744 }
catch (TskCoreException | NoCurrentCaseException ex) {
745 LOGGER.log(Level.WARNING,
"Could not delete image tag in case db", ex);
748 scrollPane.setCursor(Cursor.DEFAULT);
751 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
752 "state", null, State.CREATE));
759 private void createTag() {
760 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
761 "state", null, State.DISABLE));
762 imageTagCreator =
new ImageTagCreator(fxImageView);
764 PropertyChangeListener newTagListener = (event) -> {
765 SwingUtilities.invokeLater(() -> {
766 ImageTagRegion tag = (ImageTagRegion) event.getNewValue();
768 TagNameAndComment result = GetTagNameAndCommentDialog.doDialog();
769 if (result != null) {
771 Platform.runLater(() -> {
773 scrollPane.setCursor(Cursor.WAIT);
774 ContentViewerTag<ImageTagRegion> contentViewerTag = storeImageTag(tag, result);
775 ImageTag imageTag = buildImageTag(contentViewerTag);
776 tagsGroup.getChildren().add(imageTag);
777 }
catch (TskCoreException | SerializationException | NoCurrentCaseException ex) {
778 LOGGER.log(Level.WARNING,
"Could not save new image tag in case db", ex);
781 scrollPane.setCursor(Cursor.DEFAULT);
785 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
786 "state", null, State.CREATE));
790 Platform.runLater(() -> {
791 imageTagCreator.disconnect();
792 masterGroup.getChildren().remove(imageTagCreator);
796 imageTagCreator.addNewTagListener(newTagListener);
797 Platform.runLater(() -> masterGroup.getChildren().add(imageTagCreator));
806 private ImageTag buildImageTag(ContentViewerTag<ImageTagRegion> contentViewerTag) {
807 ImageTag imageTag =
new ImageTag(contentViewerTag, fxImageView);
810 imageTag.subscribeToEditEvents((edit) -> {
812 scrollPane.setCursor(Cursor.WAIT);
813 ImageTagRegion newRegion = (ImageTagRegion) edit.getNewValue();
814 ContentViewerTagManager.updateTag(contentViewerTag, newRegion);
815 }
catch (SerializationException | TskCoreException | NoCurrentCaseException ex) {
816 LOGGER.log(Level.WARNING,
"Could not save edit for image tag in case db", ex);
818 scrollPane.setCursor(Cursor.DEFAULT);
830 private ContentViewerTag<ImageTagRegion> storeImageTag(ImageTagRegion data, TagNameAndComment result)
831 throws TskCoreException, SerializationException, NoCurrentCaseException {
832 scrollPane.setCursor(Cursor.WAIT);
834 ContentTag contentTag = Case.getCurrentCaseThrows().getServices().getTagsManager()
835 .addContentTag(file, result.getTagName(), result.getComment());
836 return ContentViewerTagManager.saveTag(contentTag, data);
838 scrollPane.setCursor(Cursor.DEFAULT);
846 private void showOrHideTags() {
847 Platform.runLater(() -> {
848 if (DisplayOptions.HIDE_TAGS.getName().equals(hideTagsMenuItem.getText())) {
850 masterGroup.getChildren().remove(tagsGroup);
851 hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
852 tagsGroup.clearFocus();
853 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
854 "state", null, State.HIDDEN));
857 masterGroup.getChildren().add(tagsGroup);
858 hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
859 pcs.firePropertyChange(
new PropertyChangeEvent(
this,
860 "state", null, State.VISIBLE));
866 "MediaViewImagePanel.exportSaveText=Save",
867 "MediaViewImagePanel.successfulExport=Tagged image was successfully saved.",
868 "MediaViewImagePanel.unsuccessfulExport=Unable to export tagged image to disk.",
869 "MediaViewImagePanel.fileChooserTitle=Choose a save location"
871 private void exportTags() {
872 tagsGroup.clearFocus();
873 exportChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
875 exportChooser.setCurrentDirectory(
new File(Case.getCurrentCase().getExportDirectory()));
876 int returnVal = exportChooser.showDialog(
this, Bundle.MediaViewImagePanel_exportSaveText());
877 if (returnVal == JFileChooser.APPROVE_OPTION) {
878 new SwingWorker<Void, Void>() {
880 protected Void doInBackground() {
883 List<ContentTag> tags = Case.getCurrentCase().getServices()
884 .getTagsManager().getContentTagsByContent(file);
885 List<ContentViewerTag<ImageTagRegion>> contentViewerTags = getContentViewerTags(tags);
888 Collection<ImageTagRegion> regions = contentViewerTags.stream()
889 .map(cvTag -> cvTag.getDetails()).collect(Collectors.toList());
892 BufferedImage taggedImage = ImageTagsUtil.getImageWithTags(file, regions);
893 Path output = Paths.get(exportChooser.getSelectedFile().getPath(),
894 FilenameUtils.getBaseName(file.getName()) +
"-with_tags.png");
895 ImageIO.write(taggedImage,
"png", output.toFile());
897 JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_successfulExport());
898 }
catch (Exception ex) {
900 LOGGER.log(Level.WARNING,
"Unable to export tagged image to disk", ex);
901 JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_unsuccessfulExport());
909 private void tagsMenuMousePressed(java.awt.event.MouseEvent evt) {
910 if (imageTaggingOptions.isEnabled()) {
911 imageTaggingOptions.show(tagsMenu, -300 + tagsMenu.getWidth(), tagsMenu.getHeight() + 3);
918 enum DisplayOptions {
922 private final String name;
924 DisplayOptions(String name) {
949 private javax.swing.Box.Filler filler1;
950 private javax.swing.Box.Filler filler2;
951 private javax.swing.JPanel jPanel1;
952 private javax.swing.JToolBar.Separator jSeparator1;
953 private javax.swing.JToolBar.Separator jSeparator2;
954 private javax.swing.JButton rotateLeftButton;
955 private javax.swing.JButton rotateRightButton;
956 private javax.swing.JTextField rotationTextField;
957 private javax.swing.JButton tagsMenu;
958 private javax.swing.JToolBar toolbar;
959 private javax.swing.JButton zoomInButton;
960 private javax.swing.JButton zoomOutButton;
961 private javax.swing.JButton zoomResetButton;
962 private javax.swing.JTextField zoomTextField;
973 private void resetView() {
974 Image image = fxImageView.getImage();
979 double imageWidth = image.getWidth();
980 double imageHeight = image.getHeight();
981 double scrollPaneWidth = fxPanel.getWidth();
982 double scrollPaneHeight = fxPanel.getHeight();
983 double zoomRatioWidth = scrollPaneWidth / imageWidth;
984 double zoomRatioHeight = scrollPaneHeight / imageHeight;
987 zoomRatio = zoomRatioWidth < zoomRatioHeight ? zoomRatioWidth : zoomRatioHeight;
991 scrollPane.setHvalue(0);
992 scrollPane.setVvalue(0);
1007 private void updateView() {
1008 Image image = fxImageView.getImage();
1009 if (image == null) {
1014 double imageWidth = image.getWidth();
1015 double imageHeight = image.getHeight();
1018 double adjustedImageWidth = imageWidth * zoomRatio;
1019 double adjustedImageHeight = imageHeight * zoomRatio;
1022 double viewportWidth;
1023 double viewportHeight;
1026 double panelWidth = fxPanel.getWidth();
1027 double panelHeight = fxPanel.getHeight();
1030 double centerOffsetX = (panelWidth / 2) - (imageWidth / 2);
1031 double centerOffsetY = (panelHeight / 2) - (imageHeight / 2);
1038 double scrollX = scrollPane.getHvalue();
1039 double scrollY = scrollPane.getVvalue();
1046 if ((rotation % 180) == 0) {
1048 viewportWidth = adjustedImageWidth;
1049 viewportHeight = adjustedImageHeight;
1050 leftOffsetX = (adjustedImageWidth - imageWidth) / 2;
1051 topOffsetY = (adjustedImageHeight - imageHeight) / 2;
1052 maxScrollX = (adjustedImageWidth - panelWidth) / (imageWidth - panelWidth);
1053 maxScrollY = (adjustedImageHeight - panelHeight) / (imageHeight - panelHeight);
1056 viewportWidth = adjustedImageHeight;
1057 viewportHeight = adjustedImageWidth;
1058 leftOffsetX = (adjustedImageHeight - imageWidth) / 2;
1059 topOffsetY = (adjustedImageWidth - imageHeight) / 2;
1060 maxScrollX = (adjustedImageHeight - panelWidth) / (imageWidth - panelWidth);
1061 maxScrollY = (adjustedImageWidth - panelHeight) / (imageHeight - panelHeight);
1065 if (viewportWidth < imageWidth) {
1066 viewportWidth = imageWidth;
1067 if (scrollX > maxScrollX) {
1068 scrollX = maxScrollX;
1071 if (viewportHeight < imageHeight) {
1072 viewportHeight = imageHeight;
1073 if (scrollY > maxScrollY) {
1074 scrollY = maxScrollY;
1079 fxImageView.setViewport(
new Rectangle2D(
1080 0, 0, viewportWidth, viewportHeight));
1083 Scale scale =
new Scale();
1084 scale.setX(zoomRatio);
1085 scale.setY(zoomRatio);
1086 scale.setPivotX(imageWidth / 2);
1087 scale.setPivotY(imageHeight / 2);
1090 Rotate rotate =
new Rotate();
1091 rotate.setPivotX(imageWidth / 2);
1092 rotate.setPivotY(imageHeight / 2);
1093 rotate.setAngle(rotation);
1096 Translate translate =
new Translate();
1097 translate.setX(viewportWidth > fxPanel.getWidth() ? leftOffsetX : centerOffsetX);
1098 translate.setY(viewportHeight > fxPanel.getHeight() ? topOffsetY : centerOffsetY);
1103 masterGroup.getTransforms().clear();
1104 masterGroup.getTransforms().addAll(translate, rotate, scale);
1107 if (viewportWidth > fxPanel.getWidth()) {
1108 scrollPane.setHvalue(scrollX);
1110 if (viewportHeight > fxPanel.getHeight()) {
1111 scrollPane.setVvalue(scrollY);
1115 zoomOutButton.setEnabled(zoomRatio > MIN_ZOOM_RATIO);
1116 zoomInButton.setEnabled(zoomRatio < MAX_ZOOM_RATIO);
1117 rotationTextField.setText((
int) rotation +
"°");
1118 zoomTextField.setText((Math.round(zoomRatio * 100.0)) +
"%");
static boolean isJavaFxInited()