Autopsy  4.10.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
MediaViewImagePanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.contentviewers;
20 
21 import java.awt.Dimension;
22 import java.awt.EventQueue;
23 import java.awt.event.ActionEvent;
24 import java.util.Collections;
25 import java.util.List;
26 import static java.util.Objects.nonNull;
27 import java.util.SortedSet;
28 import java.util.concurrent.ExecutionException;
29 import java.util.stream.Collectors;
30 import javafx.application.Platform;
31 import javafx.concurrent.Task;
32 import javafx.embed.swing.JFXPanel;
33 import javafx.geometry.Pos;
34 import javafx.geometry.Rectangle2D;
35 import javafx.scene.Cursor;
36 import javafx.scene.Scene;
37 import javafx.scene.control.Button;
38 import javafx.scene.control.Label;
39 import javafx.scene.control.ProgressBar;
40 import javafx.scene.control.ScrollPane;
41 import javafx.scene.control.ScrollPane.ScrollBarPolicy;
42 import javafx.scene.image.Image;
43 import javafx.scene.image.ImageView;
44 import javafx.scene.layout.VBox;
45 import javafx.scene.transform.Rotate;
46 import javafx.scene.transform.Scale;
47 import javafx.scene.transform.Translate;
48 import javax.imageio.ImageIO;
49 import javax.swing.JPanel;
50 import org.controlsfx.control.MaskerPane;
51 import org.openide.util.NbBundle;
52 import org.python.google.common.collect.Lists;
57 import org.sleuthkit.datamodel.AbstractFile;
58 
63 @NbBundle.Messages({"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E",
64  "MediaViewImagePanel.errorLabel.text=Could not load file into Media View.",
65  "MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory."})
66 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
67 class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPanel {
68 
69  private static final Image EXTERNAL = new Image(MediaViewImagePanel.class.getResource("/org/sleuthkit/autopsy/images/external.png").toExternalForm());
70 
71  private final boolean fxInited;
72 
73  private JFXPanel fxPanel;
74  private ImageView fxImageView;
75  private ScrollPane scrollPane;
76  private final ProgressBar progressBar = new ProgressBar();
77  private final MaskerPane maskerPane = new MaskerPane();
78 
79  private double zoomRatio;
80  private double rotation; // Can be 0, 90, 180, and 270.
81 
82  private static final double[] ZOOM_STEPS = {
83  0.0625, 0.125, 0.25, 0.375, 0.5, 0.75,
84  1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10};
85 
86  private static final double MIN_ZOOM_RATIO = 0.0625; // 6.25%
87  private static final double MAX_ZOOM_RATIO = 10.0; // 1000%
88 
89  static {
90  ImageIO.scanForPlugins();
91  }
92 
97  static private final SortedSet<String> supportedMimes = ImageUtils.getSupportedImageMimeTypes();
98 
102  static private final List<String> supportedExtensions = ImageUtils.getSupportedImageExtensions().stream()
103  .map("."::concat) //NOI18N
104  .collect(Collectors.toList());
105 
106  private Task<Image> readImageTask;
107 
111  public MediaViewImagePanel() {
112  initComponents();
114  if (fxInited) {
115  Platform.runLater(() -> {
116 
117  // build jfx ui (we could do this in FXML?)
118  fxImageView = new ImageView(); // will hold image
119  scrollPane = new ScrollPane(fxImageView); // scrolls and sizes imageview
120  scrollPane.getStyleClass().add("bg"); //NOI18N
121  scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
122  scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
123 
124  fxPanel = new JFXPanel(); // bridge jfx-swing
125  Scene scene = new Scene(scrollPane); //root of jfx tree
126  scene.getStylesheets().add(MediaViewImagePanel.class.getResource("MediaViewImagePanel.css").toExternalForm()); //NOI18N
127  fxPanel.setScene(scene);
128 
129  fxImageView.setSmooth(true);
130  fxImageView.setCache(true);
131 
132  EventQueue.invokeLater(() -> {
133  add(fxPanel);//add jfx ui to JPanel
134  });
135  });
136  }
137  }
138 
139  public boolean isInited() {
140  return fxInited;
141  }
142 
146  public void reset() {
147  Platform.runLater(() -> {
148  fxImageView.setViewport(new Rectangle2D(0, 0, 0, 0));
149  fxImageView.setImage(null);
150 
151  scrollPane.setContent(null);
152  scrollPane.setContent(fxImageView);
153  });
154  }
155 
156  private void showErrorNode(String errorMessage, AbstractFile file) {
157  final Button externalViewerButton = new Button(Bundle.MediaViewImagePanel_externalViewerButton_text(), new ImageView(EXTERNAL));
158  externalViewerButton.setOnAction(actionEvent
159  -> //fx ActionEvent
160  /*
161  * TODO: why is the name passed into the action constructor? it
162  * means we duplicate this string all over the place -jm
163  */ new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file))
164  .actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")) //Swing ActionEvent
165  );
166 
167  final VBox errorNode = new VBox(10, new Label(errorMessage), externalViewerButton);
168  errorNode.setAlignment(Pos.CENTER);
169  }
170 
177  void showImageFx(final AbstractFile file, final Dimension dims) {
178  if (!fxInited) {
179  return;
180  }
181 
182  Platform.runLater(() -> {
183  if (readImageTask != null) {
184  readImageTask.cancel();
185  }
186  readImageTask = ImageUtils.newReadImageTask(file);
187  readImageTask.setOnSucceeded(succeeded -> {
188  if (!Case.isCaseOpen()) {
189  /*
190  * Handle the in-between condition when case is being closed
191  * and an image was previously selected
192  *
193  * NOTE: I think this is unnecessary -jm
194  */
195  reset();
196  return;
197  }
198 
199  try {
200  Image fxImage = readImageTask.get();
201  if (nonNull(fxImage)) {
202  // We have a non-null image, so let's show it.
203  fxImageView.setImage(fxImage);
204  resetView();
205  scrollPane.setContent(fxImageView);
206  } else {
207  showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
208  }
209  } catch (InterruptedException | ExecutionException ex) {
210  showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
211  }
212  scrollPane.setCursor(Cursor.DEFAULT);
213  });
214  readImageTask.setOnFailed(failed -> {
215  if (!Case.isCaseOpen()) {
216  /*
217  * Handle in-between condition when case is being closed and
218  * an image was previously selected
219  *
220  * NOTE: I think this is unnecessary -jm
221  */
222  reset();
223  return;
224  }
225  Throwable exception = readImageTask.getException();
226  if (exception instanceof OutOfMemoryError
227  && exception.getMessage().contains("Java heap space")) {
228  showErrorNode(Bundle.MediaViewImagePanel_errorLabel_OOMText(), file);
229  } else {
230  showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
231  }
232 
233  scrollPane.setCursor(Cursor.DEFAULT);
234  });
235 
236  maskerPane.setProgressNode(progressBar);
237  progressBar.progressProperty().bind(readImageTask.progressProperty());
238  maskerPane.textProperty().bind(readImageTask.messageProperty());
239  scrollPane.setContent(null); // Prevent content display issues.
240  scrollPane.setCursor(Cursor.WAIT);
241  new Thread(readImageTask).start();
242  });
243  }
244 
248  @Override
249  public List<String> getSupportedMimeTypes() {
250  return Collections.unmodifiableList(Lists.newArrayList(supportedMimes));
251  }
252 
258  @Override
259  public List<String> getSupportedExtensions() {
260  return getExtensions();
261  }
262 
268  public List<String> getExtensions() {
269  return Collections.unmodifiableList(supportedExtensions);
270  }
271 
272  @Override
273  public boolean isSupported(AbstractFile file) {
274  return ImageUtils.isImageThumbnailSupported(file);
275  }
276 
282  @SuppressWarnings("unchecked")
283  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
284  private void initComponents() {
285 
286  toolbar = new javax.swing.JToolBar();
287  rotationTextField = new javax.swing.JTextField();
288  rotateLeftButton = new javax.swing.JButton();
289  rotateRightButton = new javax.swing.JButton();
290  jSeparator1 = new javax.swing.JToolBar.Separator();
291  zoomTextField = new javax.swing.JTextField();
292  zoomOutButton = new javax.swing.JButton();
293  zoomInButton = new javax.swing.JButton();
294  jSeparator2 = new javax.swing.JToolBar.Separator();
295  zoomResetButton = new javax.swing.JButton();
296 
297  setBackground(new java.awt.Color(0, 0, 0));
298  addComponentListener(new java.awt.event.ComponentAdapter() {
299  public void componentResized(java.awt.event.ComponentEvent evt) {
300  formComponentResized(evt);
301  }
302  });
303  setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));
304 
305  toolbar.setFloatable(false);
306  toolbar.setRollover(true);
307  toolbar.setMaximumSize(new java.awt.Dimension(32767, 23));
308  toolbar.setName(""); // NOI18N
309 
310  rotationTextField.setEditable(false);
311  rotationTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
312  rotationTextField.setText(org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.rotationTextField.text")); // NOI18N
313  rotationTextField.setMaximumSize(new java.awt.Dimension(50, 2147483647));
314  rotationTextField.setMinimumSize(new java.awt.Dimension(50, 20));
315  rotationTextField.setPreferredSize(new java.awt.Dimension(50, 20));
316  toolbar.add(rotationTextField);
317 
318  rotateLeftButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/rotate-left.png"))); // NOI18N
319  org.openide.awt.Mnemonics.setLocalizedText(rotateLeftButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.rotateLeftButton.text")); // NOI18N
320  rotateLeftButton.setToolTipText(org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.rotateLeftButton.toolTipText")); // NOI18N
321  rotateLeftButton.setFocusable(false);
322  rotateLeftButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
323  rotateLeftButton.setMaximumSize(new java.awt.Dimension(24, 24));
324  rotateLeftButton.setMinimumSize(new java.awt.Dimension(24, 24));
325  rotateLeftButton.setPreferredSize(new java.awt.Dimension(24, 24));
326  rotateLeftButton.addActionListener(new java.awt.event.ActionListener() {
327  public void actionPerformed(java.awt.event.ActionEvent evt) {
328  rotateLeftButtonActionPerformed(evt);
329  }
330  });
331  toolbar.add(rotateLeftButton);
332 
333  rotateRightButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/rotate-right.png"))); // NOI18N
334  org.openide.awt.Mnemonics.setLocalizedText(rotateRightButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.rotateRightButton.text")); // NOI18N
335  rotateRightButton.setFocusable(false);
336  rotateRightButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
337  rotateRightButton.setMaximumSize(new java.awt.Dimension(24, 24));
338  rotateRightButton.setMinimumSize(new java.awt.Dimension(24, 24));
339  rotateRightButton.setPreferredSize(new java.awt.Dimension(24, 24));
340  rotateRightButton.addActionListener(new java.awt.event.ActionListener() {
341  public void actionPerformed(java.awt.event.ActionEvent evt) {
342  rotateRightButtonActionPerformed(evt);
343  }
344  });
345  toolbar.add(rotateRightButton);
346 
347  jSeparator1.setMaximumSize(new java.awt.Dimension(6, 20));
348  toolbar.add(jSeparator1);
349 
350  zoomTextField.setEditable(false);
351  zoomTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
352  zoomTextField.setText(org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.zoomTextField.text")); // NOI18N
353  zoomTextField.setMaximumSize(new java.awt.Dimension(50, 2147483647));
354  zoomTextField.setMinimumSize(new java.awt.Dimension(50, 20));
355  zoomTextField.setPreferredSize(new java.awt.Dimension(50, 20));
356  toolbar.add(zoomTextField);
357 
358  zoomOutButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/zoom-out.png"))); // NOI18N
359  org.openide.awt.Mnemonics.setLocalizedText(zoomOutButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.zoomOutButton.text")); // NOI18N
360  zoomOutButton.setFocusable(false);
361  zoomOutButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
362  zoomOutButton.setMaximumSize(new java.awt.Dimension(24, 24));
363  zoomOutButton.setMinimumSize(new java.awt.Dimension(24, 24));
364  zoomOutButton.setPreferredSize(new java.awt.Dimension(24, 24));
365  zoomOutButton.addActionListener(new java.awt.event.ActionListener() {
366  public void actionPerformed(java.awt.event.ActionEvent evt) {
367  zoomOutButtonActionPerformed(evt);
368  }
369  });
370  toolbar.add(zoomOutButton);
371 
372  zoomInButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/zoom-in.png"))); // NOI18N
373  org.openide.awt.Mnemonics.setLocalizedText(zoomInButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.zoomInButton.text")); // NOI18N
374  zoomInButton.setFocusable(false);
375  zoomInButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
376  zoomInButton.setMaximumSize(new java.awt.Dimension(24, 24));
377  zoomInButton.setMinimumSize(new java.awt.Dimension(24, 24));
378  zoomInButton.setPreferredSize(new java.awt.Dimension(24, 24));
379  zoomInButton.addActionListener(new java.awt.event.ActionListener() {
380  public void actionPerformed(java.awt.event.ActionEvent evt) {
381  zoomInButtonActionPerformed(evt);
382  }
383  });
384  toolbar.add(zoomInButton);
385 
386  jSeparator2.setMaximumSize(new java.awt.Dimension(6, 20));
387  toolbar.add(jSeparator2);
388 
389  org.openide.awt.Mnemonics.setLocalizedText(zoomResetButton, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.zoomResetButton.text")); // NOI18N
390  zoomResetButton.setFocusable(false);
391  zoomResetButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
392  zoomResetButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
393  zoomResetButton.addActionListener(new java.awt.event.ActionListener() {
394  public void actionPerformed(java.awt.event.ActionEvent evt) {
395  zoomResetButtonActionPerformed(evt);
396  }
397  });
398  toolbar.add(zoomResetButton);
399 
400  add(toolbar);
401  }// </editor-fold>//GEN-END:initComponents
402 
403  private void rotateLeftButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rotateLeftButtonActionPerformed
404  rotation = (rotation + 270) % 360;
405  updateView();
406  }//GEN-LAST:event_rotateLeftButtonActionPerformed
407 
408  private void rotateRightButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rotateRightButtonActionPerformed
409  rotation = (rotation + 90) % 360;
410  updateView();
411  }//GEN-LAST:event_rotateRightButtonActionPerformed
412 
413  private void zoomInButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomInButtonActionPerformed
414  // Find the next zoom step.
415  for (int i=0; i < ZOOM_STEPS.length; i++) {
416  if (zoomRatio < ZOOM_STEPS[i]) {
417  zoomRatio = ZOOM_STEPS[i];
418  break;
419  }
420  }
421  updateView();
422  }//GEN-LAST:event_zoomInButtonActionPerformed
423 
424  private void zoomOutButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomOutButtonActionPerformed
425  // Find the next zoom step.
426  for (int i=ZOOM_STEPS.length-1; i >= 0; i--) {
427  if (zoomRatio > ZOOM_STEPS[i]) {
428  zoomRatio = ZOOM_STEPS[i];
429  break;
430  }
431  }
432  updateView();
433  }//GEN-LAST:event_zoomOutButtonActionPerformed
434 
435  private void zoomResetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomResetButtonActionPerformed
436  resetView();
437  }//GEN-LAST:event_zoomResetButtonActionPerformed
438 
439  private void formComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentResized
440  updateView();
441  }//GEN-LAST:event_formComponentResized
442 
443  // Variables declaration - do not modify//GEN-BEGIN:variables
444  private javax.swing.JToolBar.Separator jSeparator1;
445  private javax.swing.JToolBar.Separator jSeparator2;
446  private javax.swing.JButton rotateLeftButton;
447  private javax.swing.JButton rotateRightButton;
448  private javax.swing.JTextField rotationTextField;
449  private javax.swing.JToolBar toolbar;
450  private javax.swing.JButton zoomInButton;
451  private javax.swing.JButton zoomOutButton;
452  private javax.swing.JButton zoomResetButton;
453  private javax.swing.JTextField zoomTextField;
454  // End of variables declaration//GEN-END:variables
455 
464  private void resetView() {
465  Image image = fxImageView.getImage();
466  if (image == null) {
467  return;
468  }
469 
470  double imageWidth = image.getWidth();
471  double imageHeight = image.getHeight();
472  double scrollPaneWidth = fxPanel.getWidth();
473  double scrollPaneHeight = fxPanel.getHeight();
474  double zoomRatioWidth = scrollPaneWidth / imageWidth;
475  double zoomRatioHeight = scrollPaneHeight / imageHeight;
476 
477  // Use the smallest ratio size to fit the entire image in the view area.
478  zoomRatio = zoomRatioWidth < zoomRatioHeight ? zoomRatioWidth : zoomRatioHeight;
479 
480  rotation = 0;
481 
482  scrollPane.setHvalue(0);
483  scrollPane.setVvalue(0);
484 
485  updateView();
486  }
487 
498  private void updateView() {
499  Image image = fxImageView.getImage();
500  if (image == null) {
501  return;
502  }
503 
504  // Image dimensions
505  double imageWidth = image.getWidth();
506  double imageHeight = image.getHeight();
507 
508  // Image dimensions with zooming applied
509  double adjustedImageWidth = imageWidth * zoomRatio;
510  double adjustedImageHeight = imageHeight * zoomRatio;
511 
512  // ImageView viewport dimensions
513  double viewportWidth;
514  double viewportHeight;
515 
516  // Panel dimensions
517  double panelWidth = fxPanel.getWidth();
518  double panelHeight = fxPanel.getHeight();
519 
520  // Coordinates to center the image on the panel
521  double centerOffsetX = (panelWidth / 2) - (imageWidth / 2);
522  double centerOffsetY = (panelHeight / 2) - (imageHeight / 2);
523 
524  // Coordinates to keep the image inside the left/top boundaries
525  double leftOffsetX;
526  double topOffsetY;
527 
528  // Scroll bar positions
529  double scrollX = scrollPane.getHvalue();
530  double scrollY = scrollPane.getVvalue();
531 
532  // Scroll bar position boundaries (work-around for viewport size bug)
533  double maxScrollX;
534  double maxScrollY;
535 
536  // Set viewport size and translation offsets.
537  if ((rotation % 180) == 0) {
538  // Rotation is 0 or 180.
539  viewportWidth = adjustedImageWidth;
540  viewportHeight = adjustedImageHeight;
541  leftOffsetX = (adjustedImageWidth - imageWidth) / 2;
542  topOffsetY = (adjustedImageHeight - imageHeight) / 2;
543  maxScrollX = (adjustedImageWidth - panelWidth) / (imageWidth - panelWidth);
544  maxScrollY = (adjustedImageHeight - panelHeight) / (imageHeight - panelHeight);
545  } else {
546  // Rotation is 90 or 270.
547  viewportWidth = adjustedImageHeight;
548  viewportHeight = adjustedImageWidth;
549  leftOffsetX = (adjustedImageHeight - imageWidth) / 2;
550  topOffsetY = (adjustedImageWidth - imageHeight) / 2;
551  maxScrollX = (adjustedImageHeight - panelWidth) / (imageWidth - panelWidth);
552  maxScrollY = (adjustedImageWidth - panelHeight) / (imageHeight - panelHeight);
553  }
554 
555  // Work around bug that truncates image if dimensions are too small.
556  if (viewportWidth < imageWidth) {
557  viewportWidth = imageWidth;
558  if (scrollX > maxScrollX) {
559  scrollX = maxScrollX;
560  }
561  }
562  if (viewportHeight < imageHeight) {
563  viewportHeight = imageHeight;
564  if (scrollY > maxScrollY) {
565  scrollY = maxScrollY;
566  }
567  }
568 
569  // Update the viewport size.
570  fxImageView.setViewport(new Rectangle2D(
571  0, 0, viewportWidth, viewportHeight));
572 
573  // Step 1: Zoom
574  Scale scale = new Scale();
575  scale.setX(zoomRatio);
576  scale.setY(zoomRatio);
577  scale.setPivotX(imageWidth / 2);
578  scale.setPivotY(imageHeight / 2);
579 
580  // Step 2: Rotate
581  Rotate rotate = new Rotate();
582  rotate.setPivotX(imageWidth / 2);
583  rotate.setPivotY(imageHeight / 2);
584  rotate.setAngle(rotation);
585 
586  // Step 3: Position
587  Translate translate = new Translate();
588  translate.setX(viewportWidth > fxPanel.getWidth() ? leftOffsetX : centerOffsetX);
589  translate.setY(viewportHeight > fxPanel.getHeight() ? topOffsetY : centerOffsetY);
590 
591  // Add the transforms in reverse order of intended execution.
592  // Note: They MUST be added in this order to ensure translate is
593  // executed last.
594  fxImageView.getTransforms().clear();
595  fxImageView.getTransforms().addAll(translate, rotate, scale);
596 
597  // Adjust scroll bar positions for view changes.
598  if (viewportWidth > fxPanel.getWidth()) {
599  scrollPane.setHvalue(scrollX);
600  }
601  if (viewportHeight > fxPanel.getHeight()) {
602  scrollPane.setVvalue(scrollY);
603  }
604 
605  // Update all image controls to reflect the current values.
606  zoomOutButton.setEnabled(zoomRatio > MIN_ZOOM_RATIO);
607  zoomInButton.setEnabled(zoomRatio < MAX_ZOOM_RATIO);
608  rotationTextField.setText((int) rotation + "°");
609  zoomTextField.setText((Math.round(zoomRatio * 100.0)) + "%");
610  }
611 }

Copyright © 2012-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.