001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.canvas; 020 021import java.awt.AlphaComposite; 022import java.awt.BasicStroke; 023import java.awt.BorderLayout; 024import java.awt.Color; 025import java.awt.Component; 026import java.awt.Cursor; 027import java.awt.Dimension; 028import java.awt.Font; 029import java.awt.Graphics; 030import java.awt.Graphics2D; 031import java.awt.Image; 032import java.awt.Point; 033import java.awt.Rectangle; 034import java.awt.RenderingHints; 035import java.awt.Shape; 036import java.awt.event.ActionEvent; 037import java.awt.event.ActionListener; 038import java.awt.event.ComponentAdapter; 039import java.awt.event.ComponentEvent; 040import java.awt.event.InputEvent; 041import java.awt.event.KeyEvent; 042import java.awt.event.MouseEvent; 043import java.awt.event.MouseListener; 044import java.awt.event.MouseMotionListener; 045import java.awt.event.MouseWheelEvent; 046import java.awt.event.MouseWheelListener; 047import java.awt.geom.AffineTransform; 048import java.awt.geom.Point2D; 049import java.awt.geom.Rectangle2D; 050import java.awt.image.BufferedImage; 051import java.util.ArrayList; 052import java.util.List; 053import java.util.concurrent.TimeUnit; 054 055import javax.swing.BorderFactory; 056import javax.swing.JPanel; 057import javax.swing.JToolBar; 058import javax.swing.SwingUtilities; 059import javax.swing.Timer; 060 061import icy.canvas.Canvas2D.CanvasView.ImageCache.ImageCacheTile; 062import icy.canvas.CanvasLayerEvent.LayersEventType; 063import icy.canvas.IcyCanvasEvent.IcyCanvasEventType; 064import icy.gui.component.button.IcyToggleButton; 065import icy.gui.menu.ROITask; 066import icy.gui.menu.ROITask.ROITaskListener; 067import icy.gui.util.GuiUtil; 068import icy.gui.viewer.Viewer; 069import icy.image.IcyBufferedImage; 070import icy.image.IcyBufferedImageUtil; 071import icy.image.ImageUtil; 072import icy.image.lut.LUT; 073import icy.main.Icy; 074import icy.math.Interpolator; 075import icy.math.MathUtil; 076import icy.math.MultiSmoothMover; 077import icy.math.MultiSmoothMover.MultiSmoothMoverAdapter; 078import icy.math.SmoothMover; 079import icy.math.SmoothMover.SmoothMoveType; 080import icy.math.SmoothMover.SmoothMoverAdapter; 081import icy.painter.ImageOverlay; 082import icy.painter.Overlay; 083import icy.preferences.CanvasPreferences; 084import icy.preferences.XMLPreferences; 085import icy.resource.ResourceUtil; 086import icy.resource.icon.IcyIcon; 087import icy.roi.ROI; 088import icy.sequence.DimensionId; 089import icy.sequence.Sequence; 090import icy.sequence.SequenceEvent.SequenceEventType; 091import icy.system.thread.SingleProcessor; 092import icy.system.thread.ThreadUtil; 093import icy.type.rectangle.Rectangle2DUtil; 094import icy.type.rectangle.Rectangle5D; 095import icy.util.EventUtil; 096import icy.util.GraphicsUtil; 097import icy.util.ShapeUtil; 098import icy.util.StringUtil; 099import plugins.kernel.roi.tool.plugin.ROILineCutterPlugin; 100 101/** 102 * New Canvas 2D : default ICY 2D viewer.<br> 103 * Support translation / scale and rotation transformation.<br> 104 * 105 * @author Stephane 106 */ 107public class Canvas2D extends IcyCanvas2D implements ROITaskListener 108{ 109 /** 110 * 111 */ 112 private static final long serialVersionUID = 8850168605044063031L; 113 114 static final int ICON_SIZE = 20; 115 static final int ICON_TARGET_SIZE = 20; 116 117 static final Image ICON_CENTER_IMAGE = ResourceUtil.ICON_CENTER_IMAGE; 118 static final Image ICON_FIT_IMAGE = ResourceUtil.ICON_FIT_IMAGE; 119 static final Image ICON_FIT_CANVAS = ResourceUtil.ICON_FIT_CANVAS; 120 // static final Image ICON_CENTER_IMAGE = ImageUtil.scale(ResourceUtil.ICON_CENTER_IMAGE, 121 // ICON_SIZE, ICON_SIZE); 122 // static final Image ICON_FIT_IMAGE = ImageUtil.scale(ResourceUtil.ICON_FIT_IMAGE, ICON_SIZE, 123 // ICON_SIZE); 124 // static final Image ICON_FIT_CANVAS = ImageUtil.scale(ResourceUtil.ICON_FIT_CANVAS, ICON_SIZE, 125 // ICON_SIZE); 126 127 static final Image ICON_TARGET = ImageUtil.scale(ResourceUtil.ICON_TARGET, ICON_SIZE, ICON_SIZE); 128 static final Image ICON_TARGET_BLACK = ImageUtil.getColorImageFromAlphaImage(ICON_TARGET, Color.black); 129 static final Image ICON_TARGET_LIGHT = ImageUtil.getColorImageFromAlphaImage(ICON_TARGET, Color.lightGray); 130 131 /** 132 * Possible rounded zoom factor : 0.01 --> 100 133 */ 134 final static double[] zoomRoundedFactors = new double[] {0.01d, 0.02d, 0.0333d, 0.05d, 0.075d, 0.1d, 0.15d, 0.2d, 135 0.25d, 0.333d, 0.5d, 0.66d, 0.75d, 1d, 1.25d, 1.5d, 1.75d, 2d, 2.5d, 3d, 4d, 5d, 6.6d, 7.5d, 10d, 15d, 20d, 136 30d, 50d, 66d, 75d, 100d}; 137 138 /** 139 * Image overlay to encapsulate image display in a canvas layer 140 */ 141 protected class Canvas2DImageOverlay extends IcyCanvasImageOverlay 142 { 143 @Override 144 public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) 145 { 146 if (g == null) 147 return; 148 149 final List<ImageCacheTile> tiles = canvasView.imageCache.getImageAsTiles(); 150 151 // draw image 152 for (ImageCacheTile tile : tiles) 153 g.drawImage(tile.image, tile.rect.x, tile.rect.y, null); 154 155 if (tiles.isEmpty()) 156 { 157 final Graphics2D g2 = (Graphics2D) g.create(); 158 159 // set back canvas coordinate 160 g2.transform(getInverseTransform()); 161 162 g2.setFont(canvasView.font); 163 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 164 165 if (canvasView.imageCache.isProcessing()) 166 // cache not yet built 167 canvasView.drawTextCenter(g2, "Loading...", 0.8f); 168 else if (canvasView.imageCache.getNotEnoughMemory()) 169 // not enough memory to render image 170 canvasView.drawTextCenter(g2, "Not enough memory to display image", 0.8f); 171 else 172 // no image 173 canvasView.drawTextCenter(g2, " No image ", 0.8f); 174 175 g2.dispose(); 176 } 177 } 178 } 179 180 public class CanvasMap extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener 181 { 182 /** 183 * 184 */ 185 private static final long serialVersionUID = -7305605644605013768L; 186 187 private Point mouseMapPos; 188 private Point mapStartDragPos; 189 private double mapStartRotationZ; 190 private boolean mapMoving; 191 private boolean mapRotating; 192 193 public CanvasMap() 194 { 195 super(); 196 197 mouseMapPos = new Point(0, 0); 198 mapStartDragPos = null; 199 mapStartRotationZ = 0; 200 mapMoving = false; 201 mapRotating = false; 202 203 setBorder(BorderFactory.createRaisedBevelBorder()); 204 // height will then be fixed to 160 205 setPreferredSize(new Dimension(160, 160)); 206 207 addMouseListener(this); 208 addMouseMotionListener(this); 209 addMouseWheelListener(this); 210 } 211 212 /** 213 * Return AffineTransform object which transform an image coordinate to map coordinate. 214 */ 215 public AffineTransform getImageTransform() 216 { 217 final int w = getWidth(); 218 final int h = getHeight(); 219 final int imgW = getImageSizeX(); 220 final int imgH = getImageSizeY(); 221 222 if ((imgW == 0) || (imgH == 0)) 223 return null; 224 225 final double sx = (double) w / (double) imgW; 226 final double sy = (double) h / (double) imgH; 227 final double tx, ty; 228 final double s; 229 230 // scale to viewport 231 if (sx < sy) 232 { 233 s = sx; 234 tx = 0; 235 ty = (h - (imgH * s)) / 2; 236 } 237 else if (sx > sy) 238 { 239 s = sy; 240 ty = 0; 241 tx = (w - (imgW * s)) / 2; 242 } 243 else 244 { 245 s = sx; 246 tx = 0; 247 ty = 0; 248 } 249 250 final AffineTransform result = new AffineTransform(); 251 252 // get transformation to fit image in minimap 253 result.translate(tx, ty); 254 result.scale(s, s); 255 256 return result; 257 } 258 259 /** 260 * Transform a CanvasMap point in CanvasView point 261 */ 262 public Point getCanvasPosition(Point p) 263 { 264 // transform map coordinate to canvas coordinate 265 return imageToCanvas(getImagePosition(p)); 266 } 267 268 /** 269 * Transforms a Image point in CanvasView point. 270 */ 271 public Point getCanvasPosition(Point2D.Double p) 272 { 273 // transform image coordinate to canvas coordinate 274 return imageToCanvas(p); 275 } 276 277 /** 278 * Transforms a CanvasMap point in Image point. 279 */ 280 public Point2D.Double getImagePosition(Point p) 281 { 282 final AffineTransform trans = getImageTransform(); 283 284 try 285 { 286 // get image coordinates 287 return (Point2D.Double) trans.inverseTransform(p, new Point2D.Double()); 288 } 289 catch (Exception ecx) 290 { 291 return new Point2D.Double(0, 0); 292 } 293 } 294 295 public boolean isDragging() 296 { 297 return mapStartDragPos != null; 298 } 299 300 protected void updateDrag(InputEvent e) 301 { 302 // not moving --> exit 303 if (!mapMoving) 304 return; 305 306 final Point2D.Double startDragImagePoint = getImagePosition(mapStartDragPos); 307 final Point2D.Double imagePoint = getImagePosition(mouseMapPos); 308 309 // shift action --> limit to one direction 310 if (EventUtil.isShiftDown(e)) 311 { 312 // X drag 313 if (Math.abs(mouseMapPos.x - mapStartDragPos.x) > Math.abs(mouseMapPos.y - mapStartDragPos.y)) 314 imagePoint.y = startDragImagePoint.y; 315 // Y drag 316 else 317 imagePoint.x = startDragImagePoint.x; 318 } 319 320 // center view on this point (this update mouse canvas position) 321 centerOnImage(imagePoint); 322 // no need to update mouse canvas position here as it stays at center 323 } 324 325 protected void updateRot(InputEvent e) 326 { 327 // not rotating --> exit 328 if (!mapRotating) 329 return; 330 331 final Point2D.Double imagePoint = getImagePosition(mouseMapPos); 332 333 // update mouse canvas position from image position 334 setMousePos(imageToCanvas(imagePoint)); 335 336 // get map center 337 final int mapCenterX = getWidth() / 2; 338 final int mapCenterY = getHeight() / 2; 339 340 // get last and current mouse position delta with center 341 final int lastMouseDeltaPosX = mapStartDragPos.x - mapCenterX; 342 final int lastMouseDeltaPosY = mapStartDragPos.y - mapCenterY; 343 final int newMouseDeltaPosX = mouseMapPos.x - mapCenterX; 344 final int newMouseDeltaPosY = mouseMapPos.y - mapCenterY; 345 346 // get angle in radian between last and current mouse position 347 // relative to image center 348 double newAngle = Math.atan2(newMouseDeltaPosY, newMouseDeltaPosX); 349 double lastAngle = Math.atan2(lastMouseDeltaPosY, lastMouseDeltaPosX); 350 351 // inverse rotation 352 double angle = lastAngle - newAngle; 353 354 // control button down --> rotation is enforced 355 if (EventUtil.isControlDown(e)) 356 angle *= 3; 357 358 final double destAngle; 359 360 // shift action --> limit to 45° rotation 361 if (EventUtil.isShiftDown(e)) 362 destAngle = Math.rint((mapStartRotationZ + angle) * (8d / (2 * Math.PI))) * ((2 * Math.PI) / 8d); 363 else 364 destAngle = mapStartRotationZ + angle; 365 366 // modify rotation with smooth mover 367 setRotation(destAngle, true); 368 } 369 370 @Override 371 public void mouseDragged(MouseEvent e) 372 { 373 canvasView.handlingMouseMoveEvent = true; 374 try 375 { 376 mouseMapPos = new Point(e.getPoint()); 377 378 // get the drag event ? 379 if (isDragging()) 380 { 381 // left button action --> center view on mouse point 382 if (EventUtil.isLeftMouseButton(e)) 383 { 384 385 mapMoving = true; 386 if (mapRotating) 387 { 388 mapRotating = false; 389 // force repaint so the cross is no more visible 390 canvasView.repaint(); 391 } 392 393 updateDrag(e); 394 } 395 else if (EventUtil.isRightMouseButton(e)) 396 { 397 mapMoving = false; 398 if (!mapRotating) 399 { 400 mapRotating = true; 401 // force repaint so the cross is visible 402 canvasView.repaint(); 403 } 404 405 updateRot(e); 406 } 407 408 // consume event 409 e.consume(); 410 } 411 } 412 finally 413 { 414 canvasView.handlingMouseMoveEvent = false; 415 } 416 } 417 418 @Override 419 public void mouseMoved(MouseEvent e) 420 { 421 mouseMapPos = new Point(e.getPoint()); 422 423 // send to canvas view with converted canvas position 424 canvasView.onMousePositionChanged(getCanvasPosition(e.getPoint())); 425 } 426 427 @Override 428 public void mouseClicked(MouseEvent e) 429 { 430 // nothing here 431 } 432 433 @Override 434 public void mousePressed(MouseEvent e) 435 { 436 // start drag mouse position 437 mapStartDragPos = (Point) e.getPoint().clone(); 438 // store canvas parameters 439 mapStartRotationZ = getRotationZ(); 440 441 // left click action --> center view on mouse point 442 if (EventUtil.isLeftMouseButton(e)) 443 { 444 final AffineTransform trans = getImageTransform(); 445 446 if (trans != null) 447 { 448 try 449 { 450 // get image coordinates 451 final Point2D imagePoint = trans.inverseTransform(e.getPoint(), null); 452 // center view on this point 453 centerOnImage(imagePoint.getX(), imagePoint.getY()); 454 // update new canvas position 455 setMousePos(imageToCanvas(imagePoint.getX(), imagePoint.getY())); 456 // consume event 457 e.consume(); 458 } 459 catch (Exception ecx) 460 { 461 // ignore 462 } 463 } 464 } 465 } 466 467 @Override 468 public void mouseReleased(MouseEvent e) 469 { 470 // assume end dragging 471 mapStartDragPos = null; 472 mapRotating = false; 473 mapMoving = false; 474 // repaint 475 repaint(); 476 } 477 478 @Override 479 public void mouseEntered(MouseEvent e) 480 { 481 // nothing here 482 } 483 484 @Override 485 public void mouseExited(MouseEvent e) 486 { 487 // nothing here 488 } 489 490 @Override 491 public void mouseWheelMoved(MouseWheelEvent e) 492 { 493 // we first center image to mouse position 494 final AffineTransform trans = getImageTransform(); 495 496 if (trans != null) 497 { 498 try 499 { 500 // get image coordinates 501 final Point2D imagePoint = trans.inverseTransform(e.getPoint(), null); 502 // center view on this point 503 centerOnImage(imagePoint.getX(), imagePoint.getY()); 504 // update new canvas position 505 setMousePos(imageToCanvas(imagePoint.getX(), imagePoint.getY())); 506 } 507 catch (Exception ecx) 508 { 509 // ignore 510 } 511 } 512 513 // send to canvas view 514 if (canvasView.onMouseWheelMoved(e.isConsumed(), e.getWheelRotation(), EventUtil.isLeftMouseButton(e), 515 EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e), EventUtil.isShiftDown(e))) 516 e.consume(); 517 } 518 519 public void keyPressed(KeyEvent e) 520 { 521 // just for the shift key state change 522 updateDrag(e); 523 updateRot(e); 524 } 525 526 public void keyReleased(KeyEvent e) 527 { 528 // just for the shift key state change 529 updateDrag(e); 530 updateRot(e); 531 } 532 533 @Override 534 protected void paintComponent(Graphics g) 535 { 536 super.paintComponent(g); 537 538 final AffineTransform trans = getImageTransform(); 539 540 if (trans != null) 541 { 542 final Graphics2D g2 = (Graphics2D) g.create(); 543 final List<ImageCacheTile> tiles = canvasView.imageCache.getImageAsTiles(); 544 // final BufferedImage img = canvasView.imageCache.getImage(); 545 546 // draw image 547 for (ImageCacheTile tile : tiles) 548 { 549 trans.translate(tile.rect.getX(), tile.rect.getY()); 550 g2.drawImage(tile.image, trans, null); 551 trans.translate(-tile.rect.getX(), -tile.rect.getY()); 552 } 553 // if (img != null) 554 // g2.drawImage(img, trans, null); 555 556 // then apply canvas inverse transformation 557 trans.scale(1 / getScaleX(), 1 / getScaleY()); 558 trans.translate(-getOffsetX(), -getOffsetY()); 559 560 final int canvasSizeX = getCanvasSizeX(); 561 final int canvasSizeY = getCanvasSizeY(); 562 final int canvasCenterX = canvasSizeX / 2; 563 final int canvasCenterY = canvasSizeY / 2; 564 565 trans.translate(canvasCenterX, canvasCenterY); 566 trans.rotate(-getRotationZ()); 567 trans.translate(-canvasCenterX, -canvasCenterY); 568 569 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 570 571 // get transformed rectangle 572 final Shape shape = trans.createTransformedShape(new Rectangle(canvasSizeX, canvasSizeY)); 573 574 // and draw canvas view rect of the image 575 // TODO : the g2.draw(shape) cost sometime ! 576 g2.setStroke(new BasicStroke(3)); 577 g2.setColor(Color.black); 578 g2.draw(shape); 579 g2.setStroke(new BasicStroke(2)); 580 g2.setColor(Color.white); 581 g2.draw(shape); 582 583 // rotation helper 584 if (mapRotating) 585 { 586 final Point2D center = trans.transform(new Point(canvasCenterX, canvasCenterY), null); 587 final int centerX = (int) Math.round(center.getX()); 588 final int centerY = (int) Math.round(center.getY()); 589 590 final BasicStroke blackStr = new BasicStroke(4); 591 final BasicStroke greenStr = new BasicStroke(2); 592 593 g2.setStroke(blackStr); 594 g2.setColor(Color.black); 595 g2.drawLine(centerX - 4, centerY - 4, centerX + 4, centerY + 4); 596 g2.drawLine(centerX - 4, centerY + 4, centerX + 4, centerY - 4); 597 598 g2.setStroke(greenStr); 599 g2.setColor(Color.green); 600 g2.drawLine(centerX - 4, centerY - 4, centerX + 4, centerY + 4); 601 g2.drawLine(centerX - 4, centerY + 4, centerX + 4, centerY - 4); 602 } 603 604 g2.dispose(); 605 } 606 } 607 } 608 609 public class CanvasView extends JPanel 610 implements ActionListener, MouseWheelListener, MouseListener, MouseMotionListener 611 { 612 /** 613 * 614 */ 615 private static final long serialVersionUID = 4041355608444378172L; 616 617 public class ImageCache implements Runnable 618 { 619 public class ImageCacheTile 620 { 621 final static int TILE_SIZE = 2048; 622 623 public Rectangle rect; 624 public BufferedImage image; 625 626 public ImageCacheTile(Rectangle r, BufferedImage img) 627 { 628 super(); 629 630 rect = new Rectangle(r); 631 image = img; 632 } 633 634 public ImageCacheTile(Rectangle r) 635 { 636 this(r, new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB)); 637 } 638 } 639 640 /** 641 * image cache 642 */ 643 private List<ImageCacheTile> tiles; 644 645 /** 646 * processor 647 */ 648 private final SingleProcessor processor; 649 /** 650 * internals 651 */ 652 private boolean needRebuild; 653 private boolean notEnoughMemory; 654 655 public ImageCache() 656 { 657 super(); 658 659 processor = new SingleProcessor(true, "Canvas2D renderer"); 660 // we want the processor to stay alive for sometime 661 processor.setKeepAliveTime(3, TimeUnit.SECONDS); 662 663 tiles = new ArrayList<ImageCacheTile>(); 664 needRebuild = true; 665 notEnoughMemory = false; 666 667 // build cache 668 processor.submit(this); 669 } 670 671 public void invalidCache() 672 { 673 needRebuild = true; 674 } 675 676 public boolean isValid() 677 { 678 return !needRebuild; 679 } 680 681 public boolean isProcessing() 682 { 683 return processor.isProcessing(); 684 } 685 686 public void refresh() 687 { 688 // rebuild cache 689 if (needRebuild) 690 processor.submit(this); 691 692 // just repaint in the meantime 693 getViewComponent().repaint(); 694 } 695 696 /** 697 * @deprecated Caching is done as tiles now so it's better to use {@link #getImageAsTiles()} 698 */ 699 @Deprecated 700 public BufferedImage getImage() 701 { 702 // get original image 703 final IcyBufferedImage icyImage = Canvas2D.this.getImage(getPositionT(), getPositionZ(), 704 getPositionC()); 705 706 return IcyBufferedImageUtil.toBufferedImage(icyImage, null); 707 } 708 709 public List<ImageCacheTile> getImageAsTiles() 710 { 711 synchronized (tiles) 712 { 713 // duplicate list 714 return new ArrayList<ImageCacheTile>(tiles); 715 } 716 } 717 718 public boolean getNotEnoughMemory() 719 { 720 return notEnoughMemory; 721 } 722 723 @Override 724 public void run() 725 { 726 // important to set it to false at beginning 727 needRebuild = false; 728 729 // get original image 730 final IcyBufferedImage icyImage = Canvas2D.this.getImage(getPositionT(), getPositionZ(), 731 getPositionC()); 732 733 // clear cache so we know we don't have any image at this position 734 if (icyImage == null) 735 tiles.clear(); 736 else 737 { 738 try 739 { 740 { 741 final Rectangle imgRect = icyImage.getBounds(); 742 // get tiles list 743 final List<Rectangle> newTiles = ImageUtil.getTileList(icyImage.getSizeX(), 744 icyImage.getSizeY(), ImageCacheTile.TILE_SIZE, ImageCacheTile.TILE_SIZE); 745 final int len = newTiles.size(); 746 747 int indNewTiles = 0; 748 // compare with previous tile list 749 for (ImageCacheTile tile : tiles) 750 { 751 if (indNewTiles < len) 752 { 753 final Rectangle oldRect = tile.rect; 754 final Rectangle newRect = newTiles.get(indNewTiles).intersection(imgRect); 755 756 // size changed ? --> re alloc image 757 if ((oldRect.width != newRect.width) || (oldRect.height != newRect.height)) 758 tile.image = new BufferedImage(newRect.width, newRect.height, 759 BufferedImage.TYPE_INT_ARGB); 760 // adjust rect (position) if needed 761 tile.rect = newRect; 762 } 763 764 indNewTiles++; 765 } 766 767 // remove extras tiles 768 while (tiles.size() > len) 769 tiles.remove(tiles.size() - 1); 770 // add extras tiles 771 while (indNewTiles < len) 772 tiles.add(new ImageCacheTile(newTiles.get(indNewTiles++))); 773 774 // rebuild images 775 final LUT l = getLut(); 776 for (ImageCacheTile tile : tiles) 777 tile.image = IcyBufferedImageUtil.toBufferedImage( 778 IcyBufferedImageUtil.getSubImage(icyImage, tile.rect), tile.image, l); 779 } 780 781 notEnoughMemory = false; 782 } 783 catch (OutOfMemoryError e) 784 { 785 notEnoughMemory = true; 786 } 787 } 788 789 // repaint now 790 getViewComponent().repaint(); 791 } 792 } 793 794 /** 795 * Image cache 796 */ 797 final ImageCache imageCache; 798 799 /** 800 * internals 801 */ 802 final Font font; 803 private final Timer refreshTimer; 804 private final Timer zoomInfoTimer; 805 private final Timer rotationInfoTimer; 806 private final SmoothMover zoomInfoAlphaMover; 807 private final SmoothMover rotationInfoAlphaMover; 808 private String zoomMessage; 809 private String rotationMessage; 810 Dimension lastSize; 811 boolean actived; 812 boolean handlingMouseMoveEvent; 813 private Point startDragPosition; 814 private Point startOffset; 815 double curScaleX; 816 double curScaleY; 817 private double startRotationZ; 818 // private Cursor previousCursor; 819 boolean moving; 820 boolean rotating; 821 boolean hasMouseFocus; 822 boolean areaSelection; 823 824 public CanvasView() 825 { 826 super(); 827 828 imageCache = new ImageCache(); 829 actived = false; 830 handlingMouseMoveEvent = false; 831 startDragPosition = null; 832 startOffset = null; 833 curScaleX = -1; 834 curScaleY = -1; 835 // previousCursor = getCursor(); 836 moving = false; 837 rotating = false; 838 hasMouseFocus = false; 839 areaSelection = false; 840 lastSize = getSize(); 841 842 font = new Font("Arial", Font.BOLD, 16); 843 844 zoomInfoAlphaMover = new SmoothMover(0); 845 zoomInfoAlphaMover.setMoveTime(500); 846 zoomInfoAlphaMover.setUpdateDelay(20); 847 zoomInfoAlphaMover.addListener(new SmoothMoverAdapter() 848 { 849 @Override 850 public void valueChanged(SmoothMover source, double newValue, int pourcent) 851 { 852 // just repaint 853 repaint(); 854 } 855 }); 856 rotationInfoAlphaMover = new SmoothMover(0); 857 rotationInfoAlphaMover.setMoveTime(500); 858 rotationInfoAlphaMover.setUpdateDelay(20); 859 rotationInfoAlphaMover.addListener(new SmoothMoverAdapter() 860 { 861 @Override 862 public void valueChanged(SmoothMover source, double newValue, int pourcent) 863 { 864 // just repaint 865 repaint(); 866 } 867 }); 868 869 refreshTimer = new Timer(100, this); 870 refreshTimer.setRepeats(false); 871 zoomInfoTimer = new Timer(1000, this); 872 zoomInfoTimer.setRepeats(false); 873 rotationInfoTimer = new Timer(1000, this); 874 rotationInfoTimer.setRepeats(false); 875 876 addComponentListener(new ComponentAdapter() 877 { 878 @Override 879 public void componentResized(ComponentEvent e) 880 { 881 final Dimension newSize = getSize(); 882 int extX = 0; 883 int extY = 0; 884 885 // first time component is displayed ? 886 if (!actived) 887 { 888 // by default we adapt image to canvas size 889 fitImageToCanvas(false); 890 // center image (if cannot fit to canvas size) 891 centerImage(); 892 actived = true; 893 } 894 else 895 { 896 // auto FIT enabled 897 if (zoomFitCanvasButton.isSelected()) 898 fitImageToCanvas(true); 899 else 900 { 901 // re-center 902 final int dx = newSize.width - lastSize.width; 903 final int dy = newSize.height - lastSize.height; 904 final int dx2 = dx / 2; 905 final int dy2 = dy / 2; 906 // keep trace of lost bit 907 extX = (2 * dx2) - dx; 908 extY = (2 * dy2) - dy; 909 910 setOffset((int) smoothTransform.getDestValue(TRANS_X) + dx2, 911 (int) smoothTransform.getDestValue(TRANS_Y) + dy2, true); 912 } 913 } 914 915 // keep trace of size plus lost part 916 lastSize.width = newSize.width + extX; 917 lastSize.height = newSize.height + extY; 918 } 919 }); 920 921 addMouseListener(this); 922 addMouseMotionListener(this); 923 addMouseWheelListener(this); 924 } 925 926 /** 927 * Release some stuff 928 */ 929 void shutDown() 930 { 931 // stop timer and movers 932 refreshTimer.stop(); 933 zoomInfoTimer.stop(); 934 rotationInfoTimer.stop(); 935 refreshTimer.removeActionListener(this); 936 zoomInfoTimer.removeActionListener(this); 937 rotationInfoTimer.removeActionListener(this); 938 zoomInfoAlphaMover.shutDown(); 939 rotationInfoAlphaMover.shutDown(); 940 } 941 942 /** 943 * Returns the internal {@link ImageCache} object. 944 */ 945 public ImageCache getImageCache() 946 { 947 return imageCache; 948 } 949 950 protected void updateDrag(boolean control, boolean shift) 951 { 952 if (!moving) 953 return; 954 955 final Point mousePos = getMousePos(); 956 final Point delta = new Point(mousePos.x - startDragPosition.x, mousePos.y - startDragPosition.y); 957 958 // shift action --> limit to one direction 959 if (shift) 960 { 961 // X drag 962 if (Math.abs(delta.x) > Math.abs(delta.y)) 963 delta.y = 0; 964 // Y drag 965 else 966 delta.x = 0; 967 } 968 969 translate(startOffset, delta, control); 970 } 971 972 protected void translate(Point startPos, Point delta, boolean control) 973 { 974 final Point2D.Double deltaD; 975 976 // control button down 977 if (control) 978 // drag is scaled by current scales factor 979 // deltaD = canvasToImageDelta(delta.x, delta.y, 1d / getScaleX(), 1d / getScaleY(), 980 // getRotationZ()); 981 deltaD = canvasToImageDelta(delta.x * 3, delta.y * 3, 1d, 1d, getRotationZ()); 982 else 983 // just get rid of rotation factor 984 deltaD = canvasToImageDelta(delta.x, delta.y, 1d, 1d, getRotationZ()); 985 986 // modify offset with smooth mover 987 setOffset((int) Math.round(startPos.x + deltaD.x), (int) Math.round(startPos.y + deltaD.y), true); 988 } 989 990 protected void updateRot(boolean control, boolean shift) 991 { 992 if (!rotating) 993 return; 994 995 final Point mousePos = getMousePos(); 996 997 // get canvas center 998 final int canvasCenterX = getCanvasSizeX() / 2; 999 final int canvasCenterY = getCanvasSizeY() / 2; 1000 1001 // get last and current mouse position delta with center 1002 final int lastMouseDeltaPosX = startDragPosition.x - canvasCenterX; 1003 final int lastMouseDeltaPosY = startDragPosition.y - canvasCenterY; 1004 final int newMouseDeltaPosX = mousePos.x - canvasCenterX; 1005 final int newMouseDeltaPosY = mousePos.y - canvasCenterY; 1006 1007 // get angle in radian between last and current mouse position 1008 // relative to image center 1009 double newAngle = Math.atan2(newMouseDeltaPosY, newMouseDeltaPosX); 1010 double lastAngle = Math.atan2(lastMouseDeltaPosY, lastMouseDeltaPosX); 1011 1012 double angle = newAngle - lastAngle; 1013 1014 // control button down --> rotation is enforced 1015 if (control) 1016 angle *= 3; 1017 1018 final double destAngle; 1019 1020 // shift action --> limit to 45° rotation 1021 if (shift) 1022 destAngle = Math.rint((startRotationZ + angle) * (8d / (2 * Math.PI))) * ((2 * Math.PI) / 8d); 1023 else 1024 destAngle = startRotationZ + angle; 1025 1026 // modify rotation with smooth mover 1027 setRotation(destAngle, true); 1028 } 1029 1030 /** 1031 * Internal canvas process on mouseClicked event.<br> 1032 * Return true if event should be consumed. 1033 */ 1034 boolean onMouseClicked(boolean consumed, int clickCount, boolean left, boolean right, boolean control) 1035 { 1036 if (!consumed) 1037 { 1038 // nothing yet 1039 } 1040 1041 return false; 1042 } 1043 1044 /** 1045 * Internal canvas process on mousePressed event.<br> 1046 * Return true if event should be consumed. 1047 */ 1048 boolean onMousePressed(boolean consumed, boolean left, boolean right, boolean control) 1049 { 1050 // not yet consumed 1051 if (!consumed) 1052 { 1053 final ROITask toolTask = Icy.getMainInterface().getROIRibbonTask(); 1054 final Sequence seq = getSequence(); 1055 1056 // left button press ? 1057 if (left) 1058 { 1059 // ROI tool selected --> ROI creation 1060 if ((toolTask != null) && toolTask.isROITool()) 1061 { 1062 // get the ROI plugin class name 1063 final String roiClassName = toolTask.getSelected(); 1064 1065 // unselect tool before ROI creation unless 1066 // control modifier is used for multiple ROI creation 1067 if (!control) 1068 Icy.getMainInterface().setSelectedTool(null); 1069 1070 // only if sequence still live 1071 if (seq != null) 1072 { 1073 // try to create ROI from current selected ROI tool 1074 final ROI roi = ROI.create(roiClassName, getMouseImagePos5D()); 1075 // roi created ? --> it becomes the selected ROI 1076 if (roi != null) 1077 { 1078 roi.setCreating(true); 1079 1080 // attach to sequence (hacky method to avoid undoing ROI cutting) 1081 seq.addROI(roi, !roiClassName.equals(ROILineCutterPlugin.class.getName())); 1082 // then do exclusive selection 1083 seq.setSelectedROI(roi); 1084 } 1085 1086 // consume event 1087 return true; 1088 } 1089 } 1090 1091 // start area selection 1092 if (control) 1093 areaSelection = true; 1094 } 1095 1096 // start drag mouse position 1097 startDragPosition = getMousePos(); 1098 // store canvas parameters 1099 startOffset = new Point(getOffsetX(), getOffsetY()); 1100 startRotationZ = getRotationZ(); 1101 1102 // repaint 1103 refresh(); 1104 updateCursor(); 1105 1106 // consume event to activate drag 1107 return true; 1108 } 1109 1110 return false; 1111 } 1112 1113 /** 1114 * Internal canvas process on mouseReleased event.<br> 1115 * Return true if event should be consumed. 1116 */ 1117 boolean onMouseReleased(boolean consumed, boolean left, boolean right, boolean control) 1118 { 1119 // area selection ? 1120 if (areaSelection) 1121 { 1122 final Sequence seq = getSequence(); 1123 1124 if (seq != null) 1125 { 1126 final List<ROI> rois = seq.getROIs(); 1127 1128 // we have some rois ? 1129 if (rois.size() > 0) 1130 { 1131 final Rectangle2D area = canvasToImage(getAreaSelection()); 1132 // 5D area 1133 final Rectangle5D area5d = new Rectangle5D.Double(area.getX(), area.getY(), getPositionZ(), 1134 getPositionT(), Double.NEGATIVE_INFINITY, area.getWidth(), area.getHeight(), 1d, 1d, 1135 Double.POSITIVE_INFINITY); 1136 1137 seq.beginUpdate(); 1138 try 1139 { 1140 for (ROI roi : rois) 1141 roi.setSelected(roi.intersects(area5d)); 1142 } 1143 finally 1144 { 1145 seq.endUpdate(); 1146 } 1147 } 1148 } 1149 } 1150 1151 // assume end dragging 1152 startDragPosition = null; 1153 moving = false; 1154 rotating = false; 1155 areaSelection = false; 1156 1157 // repaint 1158 refresh(); 1159 updateCursor(); 1160 1161 // consume event 1162 return true; 1163 } 1164 1165 /** 1166 * Internal canvas process on mouseMove event.<br> 1167 * Always processed, no consume here. 1168 */ 1169 void onMousePositionChanged(Point pos) 1170 { 1171 handlingMouseMoveEvent = true; 1172 try 1173 { 1174 // update mouse position 1175 setMousePos(pos); 1176 } 1177 finally 1178 { 1179 handlingMouseMoveEvent = false; 1180 } 1181 } 1182 1183 /** 1184 * Internal canvas process on mouseDragged event.<br> 1185 * Return true if event should be consumed. 1186 */ 1187 boolean onMouseDragged(boolean consumed, Point pos, boolean left, boolean right, boolean control, boolean shift) 1188 { 1189 if (!consumed) 1190 { 1191 // canvas get the drag event ? 1192 if (isDragging()) 1193 { 1194 // left mouse button action : translation 1195 if (left) 1196 { 1197 moving = true; 1198 if (rotating) 1199 { 1200 rotating = false; 1201 // force repaint so the cross is no more visible 1202 canvasView.repaint(); 1203 } 1204 1205 updateDrag(control, shift); 1206 } 1207 // right mouse button action : rotation 1208 else if (right) 1209 { 1210 moving = false; 1211 if (!rotating) 1212 { 1213 rotating = true; 1214 // force repaint so the cross is visible 1215 canvasView.repaint(); 1216 } 1217 1218 updateRot(control, shift); 1219 } 1220 1221 // dragging --> consume event 1222 return true; 1223 } 1224 // repaint area selection 1225 else if (areaSelection) 1226 repaint(); 1227 1228 // no dragging --> no consume 1229 return false; 1230 } 1231 1232 return false; 1233 } 1234 1235 /** 1236 * Internal canvas process on mouseWheelMoved event.<br> 1237 * Return true if event should be consumed. 1238 */ 1239 boolean onMouseWheelMoved(boolean consumed, int wheelRotation, boolean left, boolean right, boolean control, 1240 boolean shift) 1241 { 1242 if (!consumed) 1243 { 1244 if (!isDragging()) 1245 { 1246 // as soon we manipulate the image with mouse, we want to be focused 1247 if (!viewer.hasFocus()) 1248 viewer.requestFocus(); 1249 1250 double sx, sy; 1251 1252 // adjust mouse wheel depending preference 1253 double wr = wheelRotation * CanvasPreferences.getMouseWheelSensitivity(); 1254 if (CanvasPreferences.getInvertMouseWheelAxis()) 1255 wr = -wr; 1256 1257 sx = 1d + (wr / 100d); 1258 sy = 1d + (wr / 100d); 1259 1260 // if (wr > 0d) 1261 // { 1262 // sx = 20d / 19d; 1263 // sy = 20d / 19d; 1264 // } 1265 // else 1266 // { 1267 // sx = 19d / 20d; 1268 // sy = 19d / 20d; 1269 // } 1270 1271 // control button down --> fast zoom 1272 if (control) 1273 { 1274 sx *= sx; 1275 sy *= sy; 1276 } 1277 1278 // reload current value 1279 if (curScaleX == -1) 1280 curScaleX = smoothTransform.getDestValue(SCALE_X); 1281 if (curScaleY == -1) 1282 curScaleY = smoothTransform.getDestValue(SCALE_Y); 1283 1284 curScaleX = Math.max(0.01d, Math.min(100d, curScaleX * sx)); 1285 curScaleY = Math.max(0.01d, Math.min(100d, curScaleY * sy)); 1286 1287 double newScaleX = curScaleX; 1288 double newScaleY = curScaleY; 1289 1290 // shift key down --> adjust to closest "round" number 1291 if (shift) 1292 { 1293 newScaleX = MathUtil.closest(newScaleX, zoomRoundedFactors); 1294 newScaleY = MathUtil.closest(newScaleY, zoomRoundedFactors); 1295 } 1296 1297 setScale(newScaleX, newScaleY, false, true); 1298 1299 // consume event 1300 return true; 1301 } 1302 } 1303 1304 // don't consume this event 1305 return false; 1306 } 1307 1308 @Override 1309 public void mouseClicked(MouseEvent e) 1310 { 1311 // send mouse event to overlays first 1312 Canvas2D.this.mouseClick(e); 1313 1314 // process 1315 if (onMouseClicked(e.isConsumed(), e.getClickCount(), EventUtil.isLeftMouseButton(e), 1316 EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e))) 1317 e.consume(); 1318 } 1319 1320 @Override 1321 public void mousePressed(MouseEvent e) 1322 { 1323 // send mouse event to overlays first 1324 Canvas2D.this.mousePressed(e); 1325 1326 // process 1327 if (onMousePressed(e.isConsumed(), EventUtil.isLeftMouseButton(e), EventUtil.isRightMouseButton(e), 1328 EventUtil.isControlDown(e))) 1329 e.consume(); 1330 } 1331 1332 @Override 1333 public void mouseReleased(MouseEvent e) 1334 { 1335 // send mouse event to overlays first 1336 Canvas2D.this.mouseReleased(e); 1337 1338 // process 1339 if (onMouseReleased(e.isConsumed(), EventUtil.isLeftMouseButton(e), EventUtil.isRightMouseButton(e), 1340 EventUtil.isControlDown(e))) 1341 e.consume(); 1342 } 1343 1344 @Override 1345 public void mouseEntered(MouseEvent e) 1346 { 1347 hasMouseFocus = true; 1348 1349 // send mouse event to overlays 1350 Canvas2D.this.mouseEntered(e); 1351 // and refresh 1352 refresh(); 1353 } 1354 1355 @Override 1356 public void mouseExited(MouseEvent e) 1357 { 1358 hasMouseFocus = false; 1359 1360 // send mouse event to overlays 1361 Canvas2D.this.mouseExited(e); 1362 // and refresh 1363 refresh(); 1364 } 1365 1366 @Override 1367 public void mouseMoved(MouseEvent e) 1368 { 1369 // process first without consume (update mouse canvas position) 1370 onMousePositionChanged(e.getPoint()); 1371 1372 // send mouse event to overlays after so mouse canvas position is ok 1373 Canvas2D.this.mouseMove(e); 1374 } 1375 1376 @Override 1377 public void mouseDragged(MouseEvent e) 1378 { 1379 // process first without consume (update mouse canvas position) 1380 onMousePositionChanged(e.getPoint()); 1381 1382 // send mouse event to overlays after so mouse canvas position is ok 1383 Canvas2D.this.mouseDrag(e); 1384 1385 // process 1386 if (onMouseDragged(e.isConsumed(), e.getPoint(), EventUtil.isLeftMouseButton(e), 1387 EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e), EventUtil.isShiftDown(e))) 1388 e.consume(); 1389 } 1390 1391 @Override 1392 public void mouseWheelMoved(MouseWheelEvent e) 1393 { 1394 // send mouse event to overlays 1395 Canvas2D.this.mouseWheelMoved(e); 1396 1397 // process 1398 if (onMouseWheelMoved(e.isConsumed(), e.getWheelRotation(), EventUtil.isLeftMouseButton(e), 1399 EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e), EventUtil.isShiftDown(e))) 1400 e.consume(); 1401 } 1402 1403 public void keyPressed(KeyEvent e) 1404 { 1405 final boolean control = EventUtil.isControlDown(e); 1406 final boolean shift = EventUtil.isShiftDown(e); 1407 1408 // just for modifiers key state change 1409 updateDrag(control, shift); 1410 updateRot(control, shift); 1411 } 1412 1413 public void keyReleased(KeyEvent e) 1414 { 1415 final boolean control = EventUtil.isControlDown(e); 1416 final boolean shift = EventUtil.isShiftDown(e); 1417 1418 // just for modifiers key state change 1419 updateDrag(control, shift); 1420 updateRot(control, shift); 1421 } 1422 1423 /** 1424 * Draw specified image layer and others layers on specified {@link Graphics2D} object. 1425 */ 1426 void drawLayer(Graphics2D g, Sequence seq, Layer layer) 1427 { 1428 if (layer.isVisible()) 1429 { 1430 final float opacity = layer.getOpacity(); 1431 1432 if (opacity != 1f) 1433 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity)); 1434 else 1435 g.setComposite(AlphaComposite.SrcOver); 1436 1437 layer.getOverlay().paint(g, seq, Canvas2D.this); 1438 } 1439 } 1440 1441 /** 1442 * Draw specified image layer and others layers on specified {@link Graphics2D} object. 1443 */ 1444 void drawImageAndLayers(Graphics2D g, Layer imageLayer) 1445 { 1446 final Sequence seq = getSequence(); 1447 final Layer defaultImageLayer = getImageLayer(); 1448 1449 // global layer visible switch for canvas 1450 if (isLayersVisible()) 1451 { 1452 final List<Layer> layers = getLayers(true); 1453 1454 // draw them in inverse order to have first painter event at top 1455 for (int i = layers.size() - 1; i >= 0; i--) 1456 { 1457 final Layer layer = layers.get(i); 1458 1459 // replace the default image layer by the specified one 1460 if (layer == defaultImageLayer) 1461 drawLayer(g, seq, imageLayer); 1462 else 1463 drawLayer(g, seq, layer); 1464 } 1465 } 1466 else 1467 // display image layer only 1468 drawLayer(g, seq, imageLayer); 1469 } 1470 1471 @Override 1472 protected void paintComponent(Graphics g) 1473 { 1474 super.paintComponent(g); 1475 1476 final int w = getCanvasSizeX(); 1477 final int h = getCanvasSizeY(); 1478 final int canvasCenterX = w / 2; 1479 final int canvasCenterY = h / 2; 1480 1481 // background and layers 1482 { 1483 final Graphics2D g2 = (Graphics2D) g.create(); 1484 1485 // background 1486 if (isBackgroundColorEnabled()) 1487 { 1488 g2.setBackground(getBackgroundColor()); 1489 g2.clearRect(0, 0, w, h); 1490 } 1491 1492 // apply filtering 1493 if (CanvasPreferences.getFiltering() && ((getScaleX() < 4d) && (getScaleY() < 4d))) 1494 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 1495 else 1496 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 1497 RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 1498 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 1499 1500 // apply transformation 1501 g2.transform(getTransform()); 1502 1503 // draw image and layers 1504 drawImageAndLayers(g2, getImageLayer()); 1505 1506 g2.dispose(); 1507 } 1508 1509 // area selection 1510 if (areaSelection) 1511 { 1512 final Rectangle area = getAreaSelection(); 1513 final Graphics2D g2 = (Graphics2D) g.create(); 1514 1515 g2.setStroke(new BasicStroke(1)); 1516 g2.setColor(Color.darkGray); 1517 g2.drawRect(area.x + 1, area.y + 1, area.width, area.height); 1518 g2.setColor(Color.lightGray); 1519 g2.drawRect(area.x, area.y, area.width, area.height); 1520 1521 g2.dispose(); 1522 } 1523 1524 // synchronized canvas ? display external cursor 1525 if (!hasMouseFocus) 1526 { 1527 final Graphics2D g2 = (Graphics2D) g.create(); 1528 1529 final Point mousePos = getMousePos(); 1530 final int x = mousePos.x - (ICON_TARGET_SIZE / 2); 1531 final int y = mousePos.y - (ICON_TARGET_SIZE / 2); 1532 1533 // display cursor at mouse pos 1534 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f)); 1535 g2.drawImage(ICON_TARGET_LIGHT, x + 1, y + 1, null); 1536 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f)); 1537 g2.drawImage(ICON_TARGET_BLACK, x, y, null); 1538 1539 g2.dispose(); 1540 } 1541 1542 // display zoom info 1543 if (zoomInfoAlphaMover.getValue() > 0) 1544 { 1545 final Graphics2D g2 = (Graphics2D) g.create(); 1546 1547 g2.setFont(font); 1548 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 1549 drawTextBottomRight(g2, zoomMessage, (float) zoomInfoAlphaMover.getValue()); 1550 1551 g2.dispose(); 1552 } 1553 1554 // display rotation info 1555 if (rotationInfoAlphaMover.getValue() > 0) 1556 { 1557 final Graphics2D g2 = (Graphics2D) g.create(); 1558 1559 g2.setFont(font); 1560 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 1561 drawTextTopRight(g2, rotationMessage, (float) rotationInfoAlphaMover.getValue()); 1562 1563 g2.dispose(); 1564 } 1565 1566 // rotation helper 1567 if (rotating) 1568 { 1569 final Graphics2D g2 = (Graphics2D) g.create(); 1570 1571 final BasicStroke blackStr = new BasicStroke(5); 1572 final BasicStroke greenStr = new BasicStroke(3); 1573 1574 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 1575 1576 g2.setStroke(blackStr); 1577 g2.setColor(Color.black); 1578 g2.drawLine(canvasCenterX - 5, canvasCenterY - 5, canvasCenterX + 5, canvasCenterY + 5); 1579 g2.drawLine(canvasCenterX - 5, canvasCenterY + 5, canvasCenterX + 5, canvasCenterY - 5); 1580 1581 g2.setStroke(greenStr); 1582 g2.setColor(Color.green); 1583 g2.drawLine(canvasCenterX - 5, canvasCenterY - 5, canvasCenterX + 5, canvasCenterY + 5); 1584 g2.drawLine(canvasCenterX - 5, canvasCenterY + 5, canvasCenterX + 5, canvasCenterY - 5); 1585 1586 g2.dispose(); 1587 } 1588 1589 // image or layers changed during repaint --> refresh again 1590 if (!isCacheValid()) 1591 refresh(); 1592 // cache is being rebuild --> refresh to show progression 1593 else if (imageCache.isProcessing()) 1594 refreshLater(100); 1595 1596 // repaint minimap to reflect change (simplest way to refresh minimap) 1597 canvasMap.repaint(); 1598 } 1599 1600 public void drawTextBottomRight(Graphics2D g, String text, float alpha) 1601 { 1602 final Rectangle2D rect = GraphicsUtil.getStringBounds(g, text); 1603 final int w = (int) rect.getWidth(); 1604 final int h = (int) rect.getHeight(); 1605 final int x = getWidth() - (w + 8 + 2); 1606 final int y = getHeight() - (h + 8 + 2); 1607 1608 g.setColor(Color.gray); 1609 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 1610 g.fillRoundRect(x, y, w + 8, h + 8, 8, 8); 1611 1612 g.setColor(Color.white); 1613 g.drawString(text, x + 4, y + 2 + h); 1614 } 1615 1616 public void drawTextTopRight(Graphics2D g, String text, float alpha) 1617 { 1618 final Rectangle2D rect = GraphicsUtil.getStringBounds(g, text); 1619 final int w = (int) rect.getWidth(); 1620 final int h = (int) rect.getHeight(); 1621 final int x = getWidth() - (w + 8 + 2); 1622 final int y = 2; 1623 1624 g.setColor(Color.gray); 1625 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 1626 g.fillRoundRect(x, y, w + 8, h + 8, 8, 8); 1627 1628 g.setColor(Color.white); 1629 g.drawString(text, x + 4, y + 2 + h); 1630 } 1631 1632 public void drawTextCenter(Graphics2D g, String text, float alpha) 1633 { 1634 final Rectangle2D rect = GraphicsUtil.getStringBounds(g, text); 1635 final int w = (int) rect.getWidth(); 1636 final int h = (int) rect.getHeight(); 1637 final int x = (getWidth() - (w + 8 + 2)) / 2; 1638 final int y = (getHeight() - (h + 8 + 2)) / 2; 1639 1640 g.setColor(Color.gray); 1641 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 1642 g.fillRoundRect(x, y, w + 8, h + 8, 8, 8); 1643 1644 g.setColor(Color.white); 1645 g.drawString(text, x + 4, y + 2 + h); 1646 } 1647 1648 /** 1649 * Update mouse cursor 1650 */ 1651 protected void updateCursor() 1652 { 1653 // final Cursor cursor = getCursor(); 1654 // 1655 // // save previous cursor if different from HAND 1656 // if (cursor.getType() != Cursor.HAND_CURSOR) 1657 // previousCursor = cursor; 1658 // 1659 if (isDragging()) 1660 { 1661 GuiUtil.setCursor(this, Cursor.HAND_CURSOR); 1662 return; 1663 } 1664 1665 if (areaSelection) 1666 { 1667 GuiUtil.setCursor(this, Cursor.CROSSHAIR_CURSOR); 1668 return; 1669 } 1670 1671 final Sequence seq = getSequence(); 1672 1673 if (seq != null) 1674 { 1675 final ROI overlappedRoi = seq.getFocusedROI(); 1676 1677 // overlapping an ROI ? 1678 if (overlappedRoi != null) 1679 { 1680 final Layer layer = getLayer(overlappedRoi); 1681 1682 if ((layer != null) && layer.isVisible()) 1683 { 1684 GuiUtil.setCursor(this, Cursor.HAND_CURSOR); 1685 return; 1686 } 1687 } 1688 1689 final List<ROI> selectedRois = seq.getSelectedROIs(); 1690 1691 // search if we are overriding ROI control points 1692 for (ROI selectedRoi : selectedRois) 1693 { 1694 final Layer layer = getLayer(selectedRoi); 1695 1696 if ((layer != null) && layer.isVisible() && selectedRoi.hasSelectedPoint()) 1697 { 1698 GuiUtil.setCursor(this, Cursor.HAND_CURSOR); 1699 return; 1700 } 1701 } 1702 } 1703 1704 // setCursor(previousCursor); 1705 GuiUtil.setCursor(this, Cursor.DEFAULT_CURSOR); 1706 } 1707 1708 public void refresh() 1709 { 1710 imageCache.refresh(); 1711 } 1712 1713 /** 1714 * Refresh in sometime 1715 */ 1716 public void refreshLater(int milli) 1717 { 1718 refreshTimer.setInitialDelay(milli); 1719 refreshTimer.start(); 1720 } 1721 1722 /** 1723 * Display zoom message for the specified amount of time (in ms) 1724 */ 1725 public void setZoomMessage(String value, int delay) 1726 { 1727 zoomMessage = value; 1728 1729 if (StringUtil.isEmpty(value)) 1730 { 1731 zoomInfoTimer.stop(); 1732 zoomInfoAlphaMover.setValue(0d); 1733 } 1734 else 1735 { 1736 zoomInfoAlphaMover.setValue(0.8d); 1737 zoomInfoTimer.setInitialDelay(delay); 1738 zoomInfoTimer.restart(); 1739 } 1740 } 1741 1742 /** 1743 * Display rotation message for the specified amount of time (in ms) 1744 */ 1745 public void setRotationMessage(String value, int delay) 1746 { 1747 rotationMessage = value; 1748 1749 if (StringUtil.isEmpty(value)) 1750 { 1751 rotationInfoTimer.stop(); 1752 rotationInfoAlphaMover.setValue(0d); 1753 } 1754 else 1755 { 1756 rotationInfoAlphaMover.setValue(0.8d); 1757 rotationInfoTimer.setInitialDelay(delay); 1758 rotationInfoTimer.restart(); 1759 } 1760 } 1761 1762 public void imageChanged() 1763 { 1764 imageCache.invalidCache(); 1765 } 1766 1767 public void layersChanged() 1768 { 1769 // nothing here 1770 } 1771 1772 public boolean isDragging() 1773 { 1774 return !areaSelection && (startDragPosition != null); 1775 } 1776 1777 public boolean isCacheValid() 1778 { 1779 return imageCache.isValid(); 1780 } 1781 1782 /** 1783 * Returns the current Rectangle region of the area selection.<br> 1784 * It returns <code>null</code> if we are not in area selection mode 1785 */ 1786 public Rectangle getAreaSelection() 1787 { 1788 if (!areaSelection) 1789 return null; 1790 1791 final int x, y; 1792 final int w, h; 1793 final Point mp = getMousePos(); 1794 1795 if (mp.x > startDragPosition.x) 1796 { 1797 x = startDragPosition.x; 1798 w = mp.x - x; 1799 } 1800 else 1801 { 1802 x = mp.x; 1803 w = startDragPosition.x - x; 1804 } 1805 if (mp.y > startDragPosition.y) 1806 { 1807 y = startDragPosition.y; 1808 h = mp.y - y; 1809 } 1810 else 1811 { 1812 y = mp.y; 1813 h = startDragPosition.y - y; 1814 } 1815 1816 return new Rectangle(x, y, w, h); 1817 } 1818 1819 @Override 1820 public void actionPerformed(ActionEvent e) 1821 { 1822 final Object source = e.getSource(); 1823 1824 if (source == refreshTimer) 1825 refresh(); 1826 else if (source == zoomInfoTimer) 1827 zoomInfoAlphaMover.moveTo(0); 1828 else if (source == rotationInfoTimer) 1829 rotationInfoAlphaMover.moveTo(0); 1830 } 1831 } 1832 1833 /** 1834 * * index 0 : translation X (int) index 1 : translation Y (int) index 2 : 1835 * scale X (double) index 3 : scale Y (double) index 4 : rotation angle 1836 * (double) 1837 * 1838 * @author Stephane 1839 */ 1840 static class Canvas2DSmoothMover extends MultiSmoothMover 1841 { 1842 public Canvas2DSmoothMover(int size, SmoothMoveType type) 1843 { 1844 super(size, type); 1845 } 1846 1847 public Canvas2DSmoothMover(int size) 1848 { 1849 super(size); 1850 } 1851 1852 @Override 1853 public void moveTo(int index, double value) 1854 { 1855 final double v; 1856 1857 // format value for radian 0..2PI range 1858 if (index == ROT) 1859 v = MathUtil.formatRadianAngle(value); 1860 else 1861 v = value; 1862 1863 if (destValues[index] != v) 1864 { 1865 destValues[index] = v; 1866 // start movement 1867 start(index, System.currentTimeMillis()); 1868 } 1869 } 1870 1871 @Override 1872 public void moveTo(double[] values) 1873 { 1874 final int maxInd = Math.min(values.length, destValues.length); 1875 1876 // first we check we have at least one value which had changed 1877 boolean changed = false; 1878 for (int index = 0; index < maxInd; index++) 1879 { 1880 final double value; 1881 1882 // format value for radian 0..2PI range 1883 if (index == ROT) 1884 value = MathUtil.formatRadianAngle(values[index]); 1885 else 1886 value = values[index]; 1887 1888 if (destValues[index] != value) 1889 { 1890 changed = true; 1891 break; 1892 } 1893 } 1894 1895 // value changed ? 1896 if (changed) 1897 { 1898 // better synchronization for multiple changes 1899 final long time = System.currentTimeMillis(); 1900 1901 for (int index = 0; index < maxInd; index++) 1902 { 1903 final double value; 1904 1905 // format value for radian 0..2PI range 1906 if (index == ROT) 1907 value = MathUtil.formatRadianAngle(values[index]); 1908 else 1909 value = values[index]; 1910 1911 destValues[index] = value; 1912 // start movement 1913 start(index, time); 1914 } 1915 } 1916 } 1917 1918 @Override 1919 public void setValue(int index, double value) 1920 { 1921 final double v; 1922 1923 // format value for radian 0..2PI range 1924 if (index == ROT) 1925 v = MathUtil.formatRadianAngle(value); 1926 else 1927 v = value; 1928 1929 // stop current movement 1930 stop(index); 1931 // directly set value 1932 destValues[index] = v; 1933 setCurrentValue(index, v, 100); 1934 } 1935 1936 @Override 1937 public void setValues(double[] values) 1938 { 1939 final int maxInd = Math.min(values.length, destValues.length); 1940 1941 for (int index = 0; index < maxInd; index++) 1942 { 1943 final double value; 1944 1945 // format value for radian 0..2PI range 1946 if (index == ROT) 1947 value = MathUtil.formatRadianAngle(values[index]); 1948 else 1949 value = values[index]; 1950 1951 // stop current movement 1952 stop(index); 1953 // directly set value 1954 destValues[index] = value; 1955 setCurrentValue(index, value, 100); 1956 } 1957 } 1958 1959 @Override 1960 protected void setCurrentValue(int index, double value, int pourcent) 1961 { 1962 final double v; 1963 1964 // format value for radian 0..2PI range 1965 if (index == ROT) 1966 v = MathUtil.formatRadianAngle(value); 1967 else 1968 v = value; 1969 1970 if (currentValues[index] != v) 1971 { 1972 currentValues[index] = v; 1973 // notify value changed 1974 changed(index, v, pourcent); 1975 } 1976 } 1977 1978 @Override 1979 protected void start(int index, long time) 1980 { 1981 final double current = currentValues[index]; 1982 final double dest; 1983 1984 if (index == ROT) 1985 { 1986 double d = destValues[index]; 1987 1988 // choose shorter path 1989 if (Math.abs(d - current) > Math.PI) 1990 { 1991 if (d > Math.PI) 1992 dest = d - (Math.PI * 2); 1993 else 1994 dest = d + (Math.PI * 2); 1995 } 1996 else 1997 dest = d; 1998 } 1999 else 2000 dest = destValues[index]; 2001 2002 // number of step to reach final value 2003 final int size = Math.max(moveTime / getUpdateDelay(), 1); 2004 2005 // calculate interpolation 2006 switch (type) 2007 { 2008 case NONE: 2009 stepValues[index] = new double[2]; 2010 stepValues[index][0] = current; 2011 stepValues[index][1] = dest; 2012 break; 2013 2014 case LINEAR: 2015 stepValues[index] = Interpolator.doLinearInterpolation(current, dest, size); 2016 break; 2017 2018 case LOG: 2019 stepValues[index] = Interpolator.doLogInterpolation(current, dest, size); 2020 break; 2021 2022 case EXP: 2023 stepValues[index] = Interpolator.doExpInterpolation(current, dest, size); 2024 break; 2025 } 2026 2027 // notify and start 2028 if (!isMoving(index)) 2029 { 2030 moveStarted(index, time); 2031 moving[index] = true; 2032 } 2033 else 2034 moveModified(index, time); 2035 } 2036 } 2037 2038 /** 2039 * pref ID 2040 */ 2041 static final String PREF_CANVAS2D_ID = "Canvas2D"; 2042 2043 static final String ID_FIT_CANVAS = "fitCanvas"; 2044 static final String ID_BG_COLOR_ENABLED = "bgColorEnabled"; 2045 static final String ID_BG_COLOR = "bgColor"; 2046 2047 final static int TRANS_X = 0; 2048 final static int TRANS_Y = 1; 2049 final static int SCALE_X = 2; 2050 final static int SCALE_Y = 3; 2051 final static int ROT = 4; 2052 2053 /** 2054 * view where we draw 2055 */ 2056 final CanvasView canvasView; 2057 2058 /** 2059 * minimap in canvas panel 2060 */ 2061 final CanvasMap canvasMap; 2062 2063 /** 2064 * GUI & setting 2065 */ 2066 IcyToggleButton zoomFitCanvasButton; 2067 Color bgColor; 2068 2069 /** 2070 * preferences 2071 */ 2072 final XMLPreferences preferences; 2073 2074 /** 2075 * The smoothTransform object contains all transform informations<br> 2076 */ 2077 final Canvas2DSmoothMover smoothTransform; 2078 2079 // internal 2080 String textInfos; 2081 Dimension previousImageSize; 2082 boolean modifyingZoom; 2083 boolean modifyingRotation; 2084 2085 public Canvas2D(Viewer viewer) 2086 { 2087 super(viewer); 2088 2089 // all channel visible at once 2090 posC = -1; 2091 2092 // view panel 2093 canvasView = new CanvasView(); 2094 // mini map 2095 canvasMap = new CanvasMap(); 2096 2097 // variables initialization 2098 preferences = CanvasPreferences.getPreferences().node(PREF_CANVAS2D_ID); 2099 2100 // init transform (5 values, log transition type) 2101 smoothTransform = new Canvas2DSmoothMover(5, SmoothMoveType.LOG); 2102 // initials transform values 2103 smoothTransform.setValues(new double[] {0d, 0d, 1d, 1d, 0d}); 2104 textInfos = null; 2105 modifyingZoom = false; 2106 modifyingRotation = false; 2107 previousImageSize = new Dimension(getImageSizeX(), getImageSizeY()); 2108 2109 smoothTransform.addListener(new MultiSmoothMoverAdapter() 2110 { 2111 @Override 2112 public void valueChanged(MultiSmoothMover source, int index, double newValue, int pourcent) 2113 { 2114 // notify canvas transformation has changed 2115 switch (index) 2116 { 2117 case TRANS_X: 2118 offsetChanged(DimensionId.X); 2119 break; 2120 2121 case TRANS_Y: 2122 offsetChanged(DimensionId.Y); 2123 break; 2124 2125 case SCALE_X: 2126 scaleChanged(DimensionId.X); 2127 break; 2128 2129 case SCALE_Y: 2130 scaleChanged(DimensionId.Y); 2131 break; 2132 2133 case ROT: 2134 rotationChanged(DimensionId.Z); 2135 break; 2136 } 2137 } 2138 2139 @Override 2140 public void moveEnded(MultiSmoothMover source, int index, double value) 2141 { 2142 // scale move ended, we can fix notify canvas transformation has changed 2143 switch (index) 2144 { 2145 case SCALE_X: 2146 canvasView.curScaleX = -1; 2147 break; 2148 2149 case SCALE_Y: 2150 canvasView.curScaleY = -1; 2151 } 2152 } 2153 }); 2154 2155 // want fast transition 2156 smoothTransform.setMoveTime(400); 2157 // and very smooth refresh if possible 2158 smoothTransform.setUpdateDelay(20); 2159 2160 // build inspector canvas panel & GUI stuff 2161 buildSettingGUI(); 2162 2163 // set view in center 2164 add(canvasView, BorderLayout.CENTER); 2165 2166 // mouse infos panel setting: we want to see values for X/Y only (2D view) 2167 mouseInfPanel.setInfoXVisible(true); 2168 mouseInfPanel.setInfoYVisible(true); 2169 // Z and T values are already visible in Z/T navigator bar 2170 mouseInfPanel.setInfoZVisible(false); 2171 mouseInfPanel.setInfoTVisible(false); 2172 // no C navigation with this canvas (all channels visible) 2173 mouseInfPanel.setInfoCVisible(false); 2174 // data and color information visible 2175 mouseInfPanel.setInfoDataVisible(true); 2176 mouseInfPanel.setInfoColorVisible(true); 2177 2178 updateZNav(); 2179 updateTNav(); 2180 2181 final ROITask trt = Icy.getMainInterface().getROIRibbonTask(); 2182 if (trt != null) 2183 trt.addListener(this); 2184 } 2185 2186 @Override 2187 public void shutDown() 2188 { 2189 super.shutDown(); 2190 2191 canvasView.shutDown(); 2192 2193 // shutdown mover object (else internal timer keep a reference to Canvas2D) 2194 smoothTransform.shutDown(); 2195 2196 final ROITask trt = Icy.getMainInterface().getROIRibbonTask(); 2197 if (trt != null) 2198 trt.removeListener(this); 2199 } 2200 2201 @Override 2202 protected Overlay createImageOverlay() 2203 { 2204 return new Canvas2DImageOverlay(); 2205 } 2206 2207 public Canvas2DSettingPanel getCanvasSettingPanel() 2208 { 2209 return (Canvas2DSettingPanel) panel; 2210 } 2211 2212 /** 2213 * Build canvas panel for inspector 2214 */ 2215 private void buildSettingGUI() 2216 { 2217 // canvas setting panel (for inspector) 2218 panel = new Canvas2DSettingPanel(this); 2219 // add the map to it 2220 panel.add(canvasMap, BorderLayout.CENTER); 2221 2222 // fit canvas toggle 2223 zoomFitCanvasButton = new IcyToggleButton(new IcyIcon(ICON_FIT_CANVAS)); 2224 zoomFitCanvasButton.setSelected(preferences.getBoolean(ID_FIT_CANVAS, false)); 2225 zoomFitCanvasButton.setFocusable(false); 2226 zoomFitCanvasButton.setToolTipText("Keep image fitting to window size"); 2227 zoomFitCanvasButton.addActionListener(new ActionListener() 2228 { 2229 @Override 2230 public void actionPerformed(ActionEvent e) 2231 { 2232 final boolean selected = zoomFitCanvasButton.isSelected(); 2233 2234 preferences.putBoolean(ID_FIT_CANVAS, selected); 2235 2236 // fit if enabled 2237 if (selected) 2238 fitImageToCanvas(true); 2239 } 2240 }); 2241 } 2242 2243 @Override 2244 public Component getViewComponent() 2245 { 2246 return canvasView; 2247 } 2248 2249 /** 2250 * Return the {@link CanvasView} component of Canvas2D. 2251 */ 2252 public CanvasView getCanvasView() 2253 { 2254 return canvasView; 2255 } 2256 2257 /** 2258 * Return the {@link CanvasMap} component of Canvas2D. 2259 */ 2260 public CanvasMap getCanvasMap() 2261 { 2262 return canvasMap; 2263 } 2264 2265 @Override 2266 public void customizeToolbar(JToolBar toolBar) 2267 { 2268 toolBar.addSeparator(); 2269 toolBar.add(zoomFitCanvasButton); 2270 // toolBar.addSeparator(); 2271 // toolBar.add(zoomFitImageButton); 2272 // toolBar.add(centerImageButton); 2273 } 2274 2275 @Override 2276 public void fitImageToCanvas() 2277 { 2278 fitImageToCanvas(false); 2279 } 2280 2281 /** 2282 * Change zoom so image fit in canvas view dimension 2283 */ 2284 public void fitImageToCanvas(boolean smooth) 2285 { 2286 // search best ratio 2287 final Point2D.Double s = getFitImageToCanvasScale(); 2288 2289 if (s != null) 2290 { 2291 final double scale = Math.min(s.x, s.y); 2292 2293 // set mouse position on image center 2294 centerMouseOnImage(); 2295 // apply scale 2296 setScale(scale, scale, true, smooth); 2297 } 2298 } 2299 2300 @Override 2301 public void fitCanvasToImage() 2302 { 2303 // center image first 2304 centerImage(); 2305 2306 super.fitCanvasToImage(); 2307 } 2308 2309 @Override 2310 public void centerOnImage(double x, double y) 2311 { 2312 // get point on canvas 2313 final Point pt = imageToCanvas(x, y); 2314 final int canvasCenterX = getCanvasSizeX() / 2; 2315 final int canvasCenterY = getCanvasSizeY() / 2; 2316 2317 final Point2D.Double newTrans = canvasToImageDelta(canvasCenterX - pt.x, canvasCenterY - pt.y, 1d, 1d, 2318 getRotationZ()); 2319 2320 setOffset((int) (smoothTransform.getDestValue(TRANS_X) + Math.round(newTrans.x)), 2321 (int) (smoothTransform.getDestValue(TRANS_Y) + Math.round(newTrans.y)), false); 2322 } 2323 2324 /** 2325 * Set mouse position on image center 2326 */ 2327 protected void centerMouseOnImage() 2328 { 2329 setMouseImagePos(getImageSizeX() / 2, getImageSizeY() / 2); 2330 } 2331 2332 /** 2333 * Set mouse position on current view center 2334 */ 2335 protected void centerMouseOnView() 2336 { 2337 setMousePos(getCanvasSizeX() >> 1, getCanvasSizeY() >> 1); 2338 } 2339 2340 @Override 2341 public void centerOn(Rectangle region) 2342 { 2343 final Rectangle2D imageRectMax = Rectangle2DUtil 2344 .getScaledRectangle(new Rectangle(getImageSizeX(), getImageSizeY()), 1.5d, true); 2345 2346 Rectangle2D adjusted = Rectangle2DUtil.getScaledRectangle(region, 2d, true); 2347 2348 // get undersize 2349 double wu = Math.max(0, 100d - adjusted.getWidth()); 2350 double hu = Math.max(0, 100d - adjusted.getHeight()); 2351 2352 // enlarge a bit to have at least a 100x100 rectangle 2353 if ((wu > 0) || (hu > 0)) 2354 ShapeUtil.enlarge(adjusted, wu, hu, true); 2355 2356 // get overflow on original image size 2357 double wo = Math.max(0, adjusted.getWidth() - imageRectMax.getWidth()); 2358 double ho = Math.max(0, adjusted.getHeight() - imageRectMax.getHeight()); 2359 2360 // reduce a bit to clip on max image size 2361 if ((wo > 0) || (ho > 0)) 2362 ShapeUtil.enlarge(adjusted, -wo, -ho, true); 2363 2364 final Rectangle viewRect = new Rectangle(getViewComponent().getSize()); 2365 2366 // calculate new scale factors 2367 final double scaleX = viewRect.width / adjusted.getWidth(); 2368 final double scaleY = viewRect.height / adjusted.getHeight(); 2369 2370 // get point on canvas 2371 final int offX; 2372 final int offY; 2373 final double newScale; 2374 2375 if (scaleX < scaleY) 2376 { 2377 newScale = scaleX; 2378 // use scale X, adapt offset Y 2379 offX = (int) (adjusted.getX() * newScale); 2380 offY = (int) ((adjusted.getY() * newScale) - ((viewRect.height - (adjusted.getHeight() * newScale)) / 2d)); 2381 } 2382 else 2383 { 2384 newScale = scaleY; 2385 // use scale Y, adapt offset X 2386 offX = (int) ((adjusted.getX() * newScale) - ((viewRect.width - (adjusted.getWidth() * newScale)) / 2d)); 2387 offY = (int) (adjusted.getY() * newScale); 2388 } 2389 2390 // apply new position and scaling 2391 setTransform(-offX, -offY, newScale, newScale, smoothTransform.getDestValue(ROT), true); 2392 } 2393 2394 /** 2395 * Set transform 2396 */ 2397 protected void setTransform(int tx, int ty, double sx, double sy, double rot, boolean smooth) 2398 { 2399 final double[] values = new double[] {tx, ty, sx, sy, rot}; 2400 2401 // modify all at once for synchronized change events 2402 if (smooth) 2403 smoothTransform.moveTo(values); 2404 else 2405 smoothTransform.setValues(values); 2406 } 2407 2408 /** 2409 * Set offset X and Y.<br> 2410 * 2411 * @param smooth 2412 * use smooth transition 2413 */ 2414 public void setOffset(int x, int y, boolean smooth) 2415 { 2416 final int adjX = Math.min(getMaxOffsetX(), Math.max(getMinOffsetX(), x)); 2417 final int adjY = Math.min(getMaxOffsetY(), Math.max(getMinOffsetY(), y)); 2418 2419 setTransform(adjX, adjY, smoothTransform.getDestValue(SCALE_X), smoothTransform.getDestValue(SCALE_Y), 2420 smoothTransform.getDestValue(ROT), smooth); 2421 } 2422 2423 /** 2424 * Set zoom factor (this use the smart zoom position and smooth transition). 2425 * 2426 * @param center 2427 * if true then zoom is centered to current view else zoom is 2428 * centered using current mouse position 2429 * @param smooth 2430 * use smooth transition 2431 */ 2432 public void setScale(double factor, boolean center, boolean smooth) 2433 { 2434 // first we center mouse position if requested 2435 if (center) 2436 centerMouseOnImage(); 2437 2438 setScale(factor, factor, true, smooth); 2439 } 2440 2441 /** 2442 * Set zoom X and Y factor.<br> 2443 * This use the smart zoom position and smooth transition. 2444 * 2445 * @param mouseCentered 2446 * if true the current mouse image position will becomes the 2447 * center of viewport else the current mouse image position will 2448 * keep its place. 2449 * @param smooth 2450 * use smooth transition 2451 */ 2452 public void setScale(double x, double y, boolean mouseCentered, boolean smooth) 2453 { 2454 final Sequence seq = getSequence(); 2455 // there is no way of changing scale if no sequence 2456 if (seq == null) 2457 return; 2458 2459 // get destination rot 2460 final double rot = smoothTransform.getDestValue(ROT); 2461 // limit min and max zoom ratio 2462 final double newScaleX = Math.max(0.01d, Math.min(100d, x)); 2463 final double newScaleY = Math.max(0.01d, Math.min(100d, y)); 2464 2465 // get new mouse position on canvas pixel 2466 final Point newMouseCanvasPos = imageToCanvas(mouseImagePos.x, mouseImagePos.y, 0, 0, newScaleX, newScaleY, 2467 rot); 2468 // new image size 2469 final int newImgSizeX = (int) Math.ceil(getImageSizeX() * newScaleX); 2470 final int newImgSizeY = (int) Math.ceil(getImageSizeY() * newScaleY); 2471 // canvas center 2472 final int canvasCenterX = getCanvasSizeX() / 2; 2473 final int canvasCenterY = getCanvasSizeY() / 2; 2474 2475 final Point2D.Double newTrans; 2476 2477 if (mouseCentered) 2478 { 2479 // we want the mouse image point to becomes the canvas center (take rotation in account) 2480 newTrans = canvasToImageDelta(canvasCenterX - newMouseCanvasPos.x, canvasCenterY - newMouseCanvasPos.y, 1d, 2481 1d, rot); 2482 } 2483 else 2484 { 2485 final Point mousePos = getMousePos(); 2486 // we want the mouse image point to keep its place (take rotation in account) 2487 newTrans = canvasToImageDelta(mousePos.x - newMouseCanvasPos.x, mousePos.y - newMouseCanvasPos.y, 1d, 1d, 2488 rot); 2489 } 2490 2491 // limit translation to min / max offset 2492 final int newTransX = Math.min(canvasCenterX, 2493 Math.max(canvasCenterX - newImgSizeX, (int) Math.round(newTrans.x))); 2494 final int newTransY = Math.min(canvasCenterY, 2495 Math.max(canvasCenterY - newImgSizeY, (int) Math.round(newTrans.y))); 2496 2497 setTransform(newTransX, newTransY, newScaleX, newScaleY, rot, smooth); 2498 } 2499 2500 /** 2501 * Set zoom X and Y factor.<br> 2502 * This is direct affectation method without position modification. 2503 * 2504 * @param smooth 2505 * use smooth transition 2506 */ 2507 public void setScale(double x, double y, boolean smooth) 2508 { 2509 setTransform((int) smoothTransform.getDestValue(TRANS_X), (int) smoothTransform.getDestValue(TRANS_Y), x, y, 2510 smoothTransform.getDestValue(ROT), smooth); 2511 } 2512 2513 /** 2514 * Set zoom factor.<br> 2515 * Only here for backward compatibility with ICY4IJ.<br> 2516 * Zoom is center on image. 2517 * 2518 * @deprecated use setScale(...) instead 2519 */ 2520 @Deprecated 2521 public void setZoom(float zoom) 2522 { 2523 // set mouse position on image center 2524 centerMouseOnImage(); 2525 // then apply zoom 2526 setScale(zoom, zoom, true, false); 2527 } 2528 2529 /** 2530 * Get destination image size X in canvas pixel coordinate 2531 */ 2532 public int getDestImageCanvasSizeX() 2533 { 2534 return (int) Math.ceil(getImageSizeX() * smoothTransform.getDestValue(SCALE_X)); 2535 } 2536 2537 /** 2538 * Get destination image size Y in canvas pixel coordinate 2539 */ 2540 public int getDestImageCanvasSizeY() 2541 { 2542 return (int) Math.ceil(getImageSizeY() * smoothTransform.getDestValue(SCALE_Y)); 2543 } 2544 2545 void backgroundColorEnabledChanged() 2546 { 2547 // save to preference 2548 preferences.putBoolean(ID_BG_COLOR_ENABLED, isBackgroundColorEnabled()); 2549 // and refresh view 2550 canvasView.refresh(); 2551 } 2552 2553 void backgroundColorChanged() 2554 { 2555 // save to preference 2556 preferences.putInt(ID_BG_COLOR, getBackgroundColor().getRGB()); 2557 // and refresh view 2558 canvasView.refresh(); 2559 } 2560 2561 /** 2562 * Returns the background color enabled state 2563 */ 2564 public boolean isBackgroundColorEnabled() 2565 { 2566 return getCanvasSettingPanel().isBackgroundColorEnabled(); 2567 } 2568 2569 /** 2570 * Sets the background color enabled state 2571 */ 2572 public void setBackgroundColorEnabled(boolean value) 2573 { 2574 getCanvasSettingPanel().setBackgroundColorEnabled(value); 2575 } 2576 2577 /** 2578 * Returns the background color 2579 */ 2580 public Color getBackgroundColor() 2581 { 2582 return getCanvasSettingPanel().getBackgroundColor(); 2583 } 2584 2585 /** 2586 * Sets the background color 2587 */ 2588 public void setBackgroundColor(Color color) 2589 { 2590 getCanvasSettingPanel().setBackgroundColor(color); 2591 } 2592 2593 /** 2594 * @return the automatic 'fit to canvas' state 2595 */ 2596 public boolean getFitToCanvas() 2597 { 2598 if (zoomFitCanvasButton != null) 2599 return zoomFitCanvasButton.isSelected(); 2600 2601 return false; 2602 } 2603 2604 /** 2605 * Sets the automatic 'fit to canvas' state 2606 */ 2607 public void setFitToCanvas(boolean value) 2608 { 2609 if (zoomFitCanvasButton != null) 2610 zoomFitCanvasButton.setSelected(value); 2611 } 2612 2613 @Override 2614 public boolean isSynchronizationSupported() 2615 { 2616 return true; 2617 } 2618 2619 protected int getMinOffsetX() 2620 { 2621 return (getCanvasSizeX() / 2) - getDestImageCanvasSizeX(); 2622 } 2623 2624 protected int getMaxOffsetX() 2625 { 2626 return (getCanvasSizeX() / 2); 2627 } 2628 2629 protected int getMinOffsetY() 2630 { 2631 return (getCanvasSizeY() / 2) - getDestImageCanvasSizeY(); 2632 } 2633 2634 protected int getMaxOffsetY() 2635 { 2636 return (getCanvasSizeY() / 2); 2637 } 2638 2639 @Override 2640 public int getOffsetX() 2641 { 2642 // can be called before constructor ended 2643 if (smoothTransform == null) 2644 return 0; 2645 2646 return (int) smoothTransform.getValue(TRANS_X); 2647 } 2648 2649 @Override 2650 public int getOffsetY() 2651 { 2652 // can be called before constructor ended 2653 if (smoothTransform == null) 2654 return 0; 2655 2656 return (int) smoothTransform.getValue(TRANS_Y); 2657 } 2658 2659 @Override 2660 public double getScaleX() 2661 { 2662 // can be called before constructor ended 2663 if (smoothTransform == null) 2664 return 0d; 2665 2666 return smoothTransform.getValue(SCALE_X); 2667 } 2668 2669 @Override 2670 public double getScaleY() 2671 { 2672 // can be called before constructor ended 2673 if (smoothTransform == null) 2674 return 0d; 2675 2676 return smoothTransform.getValue(SCALE_Y); 2677 } 2678 2679 @Override 2680 public double getRotationZ() 2681 { 2682 // can be called before constructor ended 2683 if (smoothTransform == null) 2684 return 0d; 2685 2686 return smoothTransform.getValue(ROT); 2687 } 2688 2689 /** 2690 * Only here for backward compatibility with ICY4IJ plugin. 2691 * 2692 * @deprecated use getScaleX() or getScaleY() instead 2693 */ 2694 @Deprecated 2695 public double getZoomFactor() 2696 { 2697 return getScaleX(); 2698 } 2699 2700 /** 2701 * We want angle to be in [0..2*PI] 2702 */ 2703 public double getRotation() 2704 { 2705 return MathUtil.formatRadianAngle(getRotationZ()); 2706 } 2707 2708 @Override 2709 protected void setPositionCInternal(int c) 2710 { 2711 // not supported in this canvas, C should stay at -1 2712 } 2713 2714 @Override 2715 protected void setOffsetXInternal(int value) 2716 { 2717 // this will automatically call the offsetChanged() event 2718 smoothTransform.setValue(TRANS_X, Math.min(getMaxOffsetX(), Math.max(getMinOffsetX(), value))); 2719 } 2720 2721 @Override 2722 protected void setOffsetYInternal(int value) 2723 { 2724 // this will automatically call the offsetChanged() event 2725 smoothTransform.setValue(TRANS_Y, Math.min(getMaxOffsetY(), Math.max(getMinOffsetY(), value))); 2726 } 2727 2728 @Override 2729 protected void setScaleXInternal(double value) 2730 { 2731 // this will automatically call the scaledChanged() event 2732 smoothTransform.setValue(SCALE_X, value); 2733 canvasView.curScaleX = value; 2734 } 2735 2736 @Override 2737 protected void setScaleYInternal(double value) 2738 { 2739 // this will automatically call the scaledChanged() event 2740 smoothTransform.setValue(SCALE_Y, value); 2741 canvasView.curScaleY = value; 2742 } 2743 2744 @Override 2745 protected void setRotationZInternal(double value) 2746 { 2747 // this will automatically call the rotationChanged() event 2748 smoothTransform.setValue(ROT, value); 2749 } 2750 2751 /** 2752 * Set rotation angle (radian).<br> 2753 * 2754 * @param smooth 2755 * use smooth transition 2756 */ 2757 public void setRotation(double value, boolean smooth) 2758 { 2759 setTransform((int) smoothTransform.getDestValue(TRANS_X), (int) smoothTransform.getDestValue(TRANS_Y), 2760 smoothTransform.getDestValue(SCALE_X), smoothTransform.getDestValue(SCALE_Y), value, smooth); 2761 } 2762 2763 @Override 2764 public void keyPressed(KeyEvent e) 2765 { 2766 // send to overlays 2767 super.keyPressed(e); 2768 2769 if (!e.isConsumed()) 2770 { 2771 switch (e.getKeyCode()) 2772 { 2773 case KeyEvent.VK_R: 2774 // reset zoom and rotation 2775 setRotation(0, false); 2776 fitImageToCanvas(true); 2777 2778 // also reset LUT 2779 if (EventUtil.isShiftDown(e, true)) 2780 { 2781 final Sequence sequence = getSequence(); 2782 if ((viewer != null) && (sequence != null)) 2783 viewer.setLut(sequence.createCompatibleLUT()); 2784 } 2785 2786 e.consume(); 2787 break; 2788 2789 case KeyEvent.VK_LEFT: 2790 if (EventUtil.isMenuControlDown(e, true)) 2791 setPositionT(Math.max(getPositionT() - 5, 0)); 2792 else 2793 setPositionT(Math.max(getPositionT() - 1, 0)); 2794 e.consume(); 2795 break; 2796 2797 case KeyEvent.VK_RIGHT: 2798 if (EventUtil.isMenuControlDown(e, true)) 2799 setPositionT(getPositionT() + 5); 2800 else 2801 setPositionT(getPositionT() + 1); 2802 e.consume(); 2803 break; 2804 2805 case KeyEvent.VK_UP: 2806 if (EventUtil.isMenuControlDown(e, true)) 2807 setPositionZ(getPositionZ() + 5); 2808 else 2809 setPositionZ(getPositionZ() + 1); 2810 e.consume(); 2811 break; 2812 2813 case KeyEvent.VK_DOWN: 2814 if (EventUtil.isMenuControlDown(e, true)) 2815 setPositionZ(Math.max(getPositionZ() - 5, 0)); 2816 else 2817 setPositionZ(Math.max(getPositionZ() - 1, 0)); 2818 e.consume(); 2819 break; 2820 2821 case KeyEvent.VK_NUMPAD2: 2822 if (!canvasView.moving) 2823 { 2824 final Point startPos = new Point(getOffsetX(), getOffsetY()); 2825 final Point delta = new Point(0, -getCanvasSizeY() / 4); 2826 canvasView.translate(startPos, delta, EventUtil.isControlDown(e)); 2827 e.consume(); 2828 } 2829 break; 2830 case KeyEvent.VK_NUMPAD4: 2831 if (!canvasView.moving) 2832 { 2833 final Point startPos = new Point(getOffsetX(), getOffsetY()); 2834 final Point delta = new Point(getCanvasSizeX() / 4, 0); 2835 canvasView.translate(startPos, delta, EventUtil.isControlDown(e)); 2836 e.consume(); 2837 } 2838 break; 2839 2840 case KeyEvent.VK_NUMPAD6: 2841 if (!canvasView.moving) 2842 { 2843 final Point startPos = new Point(getOffsetX(), getOffsetY()); 2844 final Point delta = new Point(-getCanvasSizeX() / 4, 0); 2845 canvasView.translate(startPos, delta, EventUtil.isControlDown(e)); 2846 e.consume(); 2847 } 2848 break; 2849 2850 case KeyEvent.VK_NUMPAD8: 2851 if (!canvasView.moving) 2852 { 2853 final Point startPos = new Point(getOffsetX(), getOffsetY()); 2854 final Point delta = new Point(0, getCanvasSizeY() / 4); 2855 canvasView.translate(startPos, delta, EventUtil.isControlDown(e)); 2856 e.consume(); 2857 } 2858 break; 2859 } 2860 } 2861 2862 // forward to view 2863 canvasView.keyPressed(e); 2864 // forward to map 2865 canvasMap.keyPressed(e); 2866 } 2867 2868 @Override 2869 public void keyReleased(KeyEvent e) 2870 { 2871 // send to overlays 2872 super.keyReleased(e); 2873 2874 // forward to view 2875 canvasView.keyReleased(e); 2876 // forward to map 2877 canvasMap.keyReleased(e); 2878 } 2879 2880 @Override 2881 public void refresh() 2882 { 2883 canvasView.imageChanged(); 2884 canvasView.layersChanged(); 2885 canvasView.refresh(); 2886 } 2887 2888 /** 2889 * Return an ARGB BufferedImage form of the image located at position [T, Z, C].<br> 2890 * If the 'out' image is not compatible with wanted image, a new image is returned. 2891 */ 2892 public BufferedImage getARGBImage(int t, int z, int c, BufferedImage out) 2893 { 2894 final IcyBufferedImage img = Canvas2D.this.getImage(t, z, c); 2895 2896 if (img != null) 2897 { 2898 final BufferedImage result; 2899 2900 if ((out != null) && ImageUtil.sameSize(img, out)) 2901 result = out; 2902 else 2903 result = new BufferedImage(img.getSizeX(), img.getSizeY(), BufferedImage.TYPE_INT_ARGB); 2904 2905 return IcyBufferedImageUtil.toBufferedImage(img, result, getLut()); 2906 } 2907 2908 return null; 2909 } 2910 2911 @Override 2912 public BufferedImage getRenderedImage(int t, int z, int c, boolean cv) 2913 { 2914 final Sequence seq = getSequence(); 2915 if (seq == null) 2916 return null; 2917 2918 // save position 2919 final int prevT = getPositionT(); 2920 final int prevZ = getPositionZ(); 2921 final boolean dl = isLayersVisible(); 2922 2923 if (dl) 2924 { 2925 // set wanted position (needed for correct overlay drawing) 2926 // we have to fire events else some stuff can miss the change 2927 setPositionT(t); 2928 setPositionZ(z); 2929 } 2930 try 2931 { 2932 final Dimension size; 2933 2934 if (cv) 2935 size = getCanvasSize(); 2936 else 2937 size = seq.getDimension2D(); 2938 2939 // get result image and graphics object 2940 final BufferedImage result = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB); 2941 final Graphics2D g = result.createGraphics(); 2942 2943 // set default clip region 2944 g.setClip(0, 0, size.width, size.height); 2945 2946 if (cv) 2947 { 2948 // apply filtering 2949 if (CanvasPreferences.getFiltering() && ((getScaleX() < 4d) && (getScaleY() < 4d))) 2950 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 2951 else 2952 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 2953 RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 2954 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 2955 2956 // apply transformation 2957 g.transform(getTransform()); 2958 } 2959 else 2960 { 2961 // apply filtering 2962 if (CanvasPreferences.getFiltering()) 2963 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 2964 else 2965 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 2966 RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 2967 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 2968 } 2969 2970 // create temporary image, overlay and layer so we can choose the correct image 2971 // (not optimal for memory and performance) 2972 final BufferedImage img = getARGBImage(t, z, c, null); 2973 final Overlay imgOverlay = new ImageOverlay("Image", img); 2974 final Layer imgLayer = new Layer(imgOverlay); 2975 2976 // keep visibility and priority information 2977 imgLayer.setVisible(getImageLayer().isVisible()); 2978 imgLayer.setPriority(getImageLayer().getPriority()); 2979 2980 // draw image and layers 2981 canvasView.drawImageAndLayers(g, imgLayer); 2982 2983 g.dispose(); 2984 2985 return result; 2986 } 2987 finally 2988 { 2989 if (dl) 2990 { 2991 // restore position 2992 setPositionT(prevT); 2993 setPositionZ(prevZ); 2994 } 2995 } 2996 } 2997 2998 /** 2999 * @deprecated Use <code>getRenderedImage(t, z, -1, true)</code> instead. 3000 */ 3001 @Deprecated 3002 public BufferedImage getRenderedImage(int t, int z) 3003 { 3004 return getRenderedImage(t, z, -1, true); 3005 } 3006 3007 /** 3008 * @deprecated Use <code>getRenderedImage(t, z, -1, canvasView)</code> instead. 3009 */ 3010 @Deprecated 3011 public BufferedImage getRenderedImage(int t, int z, boolean canvasView) 3012 { 3013 return getRenderedImage(t, z, -1, canvasView); 3014 } 3015 3016 /** 3017 * Synchronize views of specified list of canvas 3018 */ 3019 @Override 3020 protected void synchronizeCanvas(List<IcyCanvas> canvasList, IcyCanvasEvent event, boolean processAll) 3021 { 3022 final IcyCanvasEventType type = event.getType(); 3023 final DimensionId dim = event.getDim(); 3024 3025 // position synchronization 3026 if (isSynchOnSlice()) 3027 { 3028 if (processAll || (type == IcyCanvasEventType.POSITION_CHANGED)) 3029 { 3030 // no information about dimension --> set all 3031 if (processAll || (dim == DimensionId.NULL)) 3032 { 3033 // only support T and Z positioning 3034 final int z = getPositionZ(); 3035 final int t = getPositionT(); 3036 3037 for (IcyCanvas cnv : canvasList) 3038 { 3039 if (z != -1) 3040 cnv.setPositionZ(z); 3041 if (t != -1) 3042 cnv.setPositionT(t); 3043 } 3044 } 3045 else 3046 { 3047 for (IcyCanvas cnv : canvasList) 3048 { 3049 final int pos = getPosition(dim); 3050 if (pos != -1) 3051 cnv.setPosition(dim, pos); 3052 } 3053 } 3054 } 3055 } 3056 3057 // view synchronization 3058 if (isSynchOnView()) 3059 { 3060 if (processAll || (type == IcyCanvasEventType.SCALE_CHANGED)) 3061 { 3062 // no information about dimension --> set all 3063 if (processAll || (dim == DimensionId.NULL)) 3064 { 3065 final double sX = getScaleX(); 3066 final double sY = getScaleY(); 3067 3068 for (IcyCanvas cnv : canvasList) 3069 ((Canvas2D) cnv).setScale(sX, sY, false); 3070 } 3071 else 3072 { 3073 for (IcyCanvas cnv : canvasList) 3074 cnv.setScale(dim, getScale(dim)); 3075 } 3076 } 3077 3078 if (processAll || (type == IcyCanvasEventType.ROTATION_CHANGED)) 3079 { 3080 // no information about dimension --> set all 3081 if (processAll || (dim == DimensionId.NULL)) 3082 { 3083 final double rot = getRotationZ(); 3084 3085 for (IcyCanvas cnv : canvasList) 3086 ((Canvas2D) cnv).setRotation(rot, false); 3087 } 3088 else 3089 { 3090 for (IcyCanvas cnv : canvasList) 3091 cnv.setRotation(dim, getRotation(dim)); 3092 } 3093 } 3094 3095 // process offset in last as it can be limited depending destination scale value 3096 if (processAll || (type == IcyCanvasEventType.OFFSET_CHANGED)) 3097 { 3098 // no information about dimension --> set all 3099 if (processAll || (dim == DimensionId.NULL)) 3100 { 3101 final int offX = getOffsetX(); 3102 final int offY = getOffsetY(); 3103 3104 for (IcyCanvas cnv : canvasList) 3105 ((Canvas2D) cnv).setOffset(offX, offY, false); 3106 } 3107 else 3108 { 3109 for (IcyCanvas cnv : canvasList) 3110 cnv.setOffset(dim, getOffset(dim)); 3111 } 3112 } 3113 3114 } 3115 3116 // cursor synchronization 3117 if (isSynchOnCursor()) 3118 { // mouse synchronization 3119 if (processAll || (type == IcyCanvasEventType.MOUSE_IMAGE_POSITION_CHANGED)) 3120 { 3121 // no information about dimension --> set all 3122 if (processAll || (dim == DimensionId.NULL)) 3123 { 3124 final double mouseImagePosX = getMouseImagePosX(); 3125 final double mouseImagePosY = getMouseImagePosY(); 3126 3127 for (IcyCanvas cnv : canvasList) 3128 ((Canvas2D) cnv).setMouseImagePos(mouseImagePosX, mouseImagePosY); 3129 } 3130 else 3131 { 3132 for (IcyCanvas cnv : canvasList) 3133 cnv.setMouseImagePos(dim, getMouseImagePos(dim)); 3134 } 3135 } 3136 } 3137 } 3138 3139 @Override 3140 public void changed(IcyCanvasEvent event) 3141 { 3142 super.changed(event); 3143 3144 // not yet initialized 3145 if (canvasView == null) 3146 return; 3147 3148 final IcyCanvasEventType type = event.getType(); 3149 3150 switch (type) 3151 { 3152 case POSITION_CHANGED: 3153 // image has changed 3154 canvasView.imageChanged(); 3155 3156 case OFFSET_CHANGED: 3157 case SCALE_CHANGED: 3158 case ROTATION_CHANGED: 3159 // update mouse image position from mouse canvas position 3160 setMouseImagePos(canvasToImage(getMousePos())); 3161 3162 // display info message 3163 if (type == IcyCanvasEventType.SCALE_CHANGED) 3164 { 3165 final String zoomInfo = Integer.toString((int) (getScaleX() * 100)); 3166 3167 ThreadUtil.invokeLater(new Runnable() 3168 { 3169 @Override 3170 public void run() 3171 { 3172 // in panel 3173 modifyingZoom = true; 3174 try 3175 { 3176 getCanvasSettingPanel().updateZoomState(zoomInfo); 3177 } 3178 finally 3179 { 3180 modifyingZoom = false; 3181 } 3182 } 3183 }); 3184 3185 // and in canvas 3186 canvasView.setZoomMessage("Zoom : " + zoomInfo + " %", 500); 3187 } 3188 else if (type == IcyCanvasEventType.ROTATION_CHANGED) 3189 { 3190 final String rotInfo = Integer.toString((int) Math.round(getRotation() * 180d / Math.PI)); 3191 3192 ThreadUtil.invokeLater(new Runnable() 3193 { 3194 @Override 3195 public void run() 3196 { 3197 // in panel 3198 modifyingRotation = true; 3199 try 3200 { 3201 getCanvasSettingPanel().updateRotationState(rotInfo); 3202 } 3203 finally 3204 { 3205 modifyingRotation = false; 3206 } 3207 } 3208 }); 3209 3210 // and in canvas 3211 canvasView.setRotationMessage("Rotation : " + rotInfo + " °", 500); 3212 } 3213 3214 // refresh canvas 3215 canvasView.refresh(); 3216 break; 3217 3218 case MOUSE_IMAGE_POSITION_CHANGED: 3219 // mouse position changed outside mouse move event ? 3220 if (!canvasView.handlingMouseMoveEvent && !canvasView.isDragging() && !isSynchSlave()) 3221 { 3222 // mouse position in canvas 3223 final Point mousePos = getMousePos(); 3224 final Point mouseAbsolutePos = getMousePos(); 3225 // absolute mouse position 3226 SwingUtilities.convertPointToScreen(mouseAbsolutePos, canvasView); 3227 3228 // simulate a mouse move event so overlays can handle position change 3229 final MouseEvent mouseEvent = new MouseEvent(this, MouseEvent.MOUSE_MOVED, 3230 System.currentTimeMillis(), 0, mousePos.x, mousePos.y, mouseAbsolutePos.x, 3231 mouseAbsolutePos.y, 0, false, 0); 3232 3233 // send mouse move event to overlays 3234 mouseMove(mouseEvent, getMouseImagePos5D()); 3235 } 3236 3237 // update mouse cursor 3238 canvasView.updateCursor(); 3239 3240 // needed to refresh custom cursor 3241 if (!canvasView.hasMouseFocus) 3242 canvasView.refresh(); 3243 break; 3244 } 3245 } 3246 3247 @Override 3248 protected void lutChanged(int component) 3249 { 3250 super.lutChanged(component); 3251 3252 // refresh image 3253 if (canvasView != null) 3254 { 3255 canvasView.imageChanged(); 3256 canvasView.refresh(); 3257 } 3258 } 3259 3260 @Override 3261 protected void layerChanged(CanvasLayerEvent event) 3262 { 3263 super.layerChanged(event); 3264 3265 // layer visibility property modified ? 3266 if ((event.getType() == LayersEventType.CHANGED) && Layer.isPaintProperty(event.getProperty())) 3267 { 3268 // layer refresh 3269 if (canvasView != null) 3270 { 3271 canvasView.layersChanged(); 3272 canvasView.refresh(); 3273 } 3274 } 3275 } 3276 3277 @Override 3278 protected void sequenceOverlayChanged(Overlay overlay, SequenceEventType type) 3279 { 3280 super.sequenceOverlayChanged(overlay, type); 3281 3282 // layer refresh 3283 if (canvasView != null) 3284 { 3285 canvasView.layersChanged(); 3286 canvasView.refresh(); 3287 } 3288 } 3289 3290 @Override 3291 protected void sequenceDataChanged(IcyBufferedImage image, SequenceEventType type) 3292 { 3293 super.sequenceDataChanged(image, type); 3294 3295 // refresh image 3296 if (canvasView != null) 3297 { 3298 canvasView.imageChanged(); 3299 canvasView.refresh(); 3300 } 3301 } 3302 3303 @Override 3304 protected void sequenceTypeChanged() 3305 { 3306 super.sequenceTypeChanged(); 3307 3308 // sequence XY dimension changed ? 3309 if ((previousImageSize.width != getImageSizeX()) || (previousImageSize.height != getImageSizeY())) 3310 { 3311 // fit to canvas enabled ? --> adapt zoom to new sequence XY dimension 3312 if (getFitToCanvas()) 3313 fitImageToCanvas(true); 3314 } 3315 } 3316 3317 @Override 3318 public void toolChanged(String command) 3319 { 3320 final Sequence seq = getSequence(); 3321 3322 final ROITask toolTask = Icy.getMainInterface().getROIRibbonTask(); 3323 3324 if (toolTask != null) 3325 { 3326 // if we selected a ROI tool we force layers to be visible 3327 if (toolTask.isROITool()) 3328 setLayersVisible(true); 3329 } 3330 3331 // unselected all ROI 3332 if (seq != null) 3333 seq.setSelectedROI(null); 3334 } 3335 3336}