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 plugins.kernel.roi.roi2d; 020 021import java.awt.AlphaComposite; 022import java.awt.BasicStroke; 023import java.awt.Color; 024import java.awt.Graphics2D; 025import java.awt.Point; 026import java.awt.RenderingHints; 027import java.awt.Shape; 028import java.awt.event.InputEvent; 029import java.awt.event.KeyEvent; 030import java.awt.event.MouseEvent; 031import java.awt.geom.AffineTransform; 032import java.awt.geom.PathIterator; 033import java.awt.geom.Point2D; 034import java.awt.geom.Rectangle2D; 035import java.awt.image.BufferedImage; 036import java.awt.image.DataBufferByte; 037import java.lang.ref.WeakReference; 038import java.util.ArrayList; 039import java.util.Arrays; 040import java.util.HashSet; 041import java.util.List; 042import java.util.Set; 043 044import org.w3c.dom.Node; 045 046import icy.canvas.IcyCanvas; 047import icy.canvas.IcyCanvas2D; 048import icy.common.CollapsibleEvent; 049import icy.painter.Anchor2D; 050import icy.painter.Anchor2D.Anchor2DPositionListener; 051import icy.painter.OverlayEvent; 052import icy.painter.OverlayEvent.OverlayEventType; 053import icy.painter.OverlayListener; 054import icy.painter.PainterEvent; 055import icy.painter.PathAnchor2D; 056import icy.roi.ROI; 057import icy.roi.ROI2D; 058import icy.roi.ROIEvent; 059import icy.roi.edit.Point2DAddedROIEdit; 060import icy.roi.edit.Point2DMovedROIEdit; 061import icy.roi.edit.Point2DRemovedROIEdit; 062import icy.sequence.Sequence; 063import icy.system.thread.ThreadUtil; 064import icy.type.point.Point2DUtil; 065import icy.type.point.Point5D; 066import icy.util.EventUtil; 067import icy.util.GraphicsUtil; 068import icy.util.ShapeUtil; 069import icy.util.StringUtil; 070import icy.vtk.IcyVtkPanel; 071import icy.vtk.VtkUtil; 072import plugins.kernel.canvas.VtkCanvas; 073import vtk.vtkActor; 074import vtk.vtkCellArray; 075import vtk.vtkInformation; 076import vtk.vtkPoints; 077import vtk.vtkPolyData; 078import vtk.vtkPolyDataMapper; 079import vtk.vtkProp; 080import vtk.vtkProperty; 081import vtk.vtkRenderer; 082 083/** 084 * @author Stephane 085 */ 086public abstract class ROI2DShape extends ROI2D implements Shape 087{ 088 public class ROI2DShapePainter extends ROI2DPainter implements Runnable 089 { 090 // VTK 3D objects 091 protected vtkPolyData outline; 092 protected vtkPolyDataMapper outlineMapper; 093 protected vtkActor outlineActor; 094 protected vtkInformation vtkInfo; 095 protected vtkCellArray vCells; 096 protected vtkPoints vPoints; 097 protected vtkPolyData polyData; 098 protected vtkPolyDataMapper polyMapper; 099 protected vtkActor actor; 100 // 3D internal 101 protected boolean needRebuild; 102 protected double scaling[]; 103 protected WeakReference<VtkCanvas> canvas3d; 104 protected Set<Anchor2D> actorsToAdd; 105 protected Set<Anchor2D> actorsToRemove; 106 107 public ROI2DShapePainter() 108 { 109 super(); 110 111 // don't create VTK object on constructor 112 outline = null; 113 outlineMapper = null; 114 outlineActor = null; 115 vtkInfo = null; 116 vCells = null; 117 vPoints = null; 118 polyData = null; 119 polyMapper = null; 120 actor = null; 121 122 scaling = new double[3]; 123 Arrays.fill(scaling, 1d); 124 125 actorsToAdd = new HashSet<Anchor2D>(); 126 actorsToRemove = new HashSet<Anchor2D>(); 127 128 needRebuild = true; 129 canvas3d = new WeakReference<VtkCanvas>(null); 130 } 131 132 @Override 133 protected void finalize() throws Throwable 134 { 135 super.finalize(); 136 137 // release allocated VTK resources 138 if (actor != null) 139 actor.Delete(); 140 if (polyMapper != null) 141 polyMapper.Delete(); 142 if (polyData != null) 143 polyData.Delete(); 144 if (vPoints != null) 145 vPoints.Delete(); 146 if (vCells != null) 147 vCells.Delete(); 148 if (outlineActor != null) 149 { 150 outlineActor.SetPropertyKeys(null); 151 outlineActor.Delete(); 152 } 153 if (vtkInfo != null) 154 { 155 vtkInfo.Remove(VtkCanvas.visibilityKey); 156 vtkInfo.Delete(); 157 } 158 if (outlineMapper != null) 159 outlineMapper.Delete(); 160 if (outline != null) 161 { 162 outline.GetPointData().GetScalars().Delete(); 163 outline.GetPointData().Delete(); 164 outline.Delete(); 165 } 166 }; 167 168 protected void initVtkObjects() 169 { 170 outline = VtkUtil.getOutline(0d, 1d, 0d, 1d, 0d, 1d); 171 outlineMapper = new vtkPolyDataMapper(); 172 outlineMapper.SetInputData(outline); 173 outlineActor = new vtkActor(); 174 outlineActor.SetMapper(outlineMapper); 175 // disable picking on the outline 176 outlineActor.SetPickable(0); 177 // and set it to wireframe representation 178 outlineActor.GetProperty().SetRepresentationToWireframe(); 179 // use vtkInformations to store outline visibility state (hacky) 180 vtkInfo = new vtkInformation(); 181 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 182 // VtkCanvas use this to restore correctly outline visibility flag 183 outlineActor.SetPropertyKeys(vtkInfo); 184 185 // init poly data object 186 polyData = new vtkPolyData(); 187 polyMapper = new vtkPolyDataMapper(); 188 polyMapper.SetInputData(polyData); 189 actor = new vtkActor(); 190 actor.SetMapper(polyMapper); 191 192 // initialize color and stroke 193 final Color col = getColor(); 194 final double r = col.getRed() / 255d; 195 final double g = col.getGreen() / 255d; 196 final double b = col.getBlue() / 255d; 197 198 outlineActor.GetProperty().SetColor(r, g, b); 199 final vtkProperty property = actor.GetProperty(); 200 property.SetPointSize(getStroke()); 201 property.SetColor(r, g, b); 202 } 203 204 /** 205 * update 3D painter for 3D canvas (called only when VTK is loaded). 206 */ 207 protected void rebuildVtkObjects() 208 { 209 final VtkCanvas canvas = canvas3d.get(); 210 // canvas was closed 211 if (canvas == null) 212 return; 213 214 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 215 // canvas was closed 216 if (vtkPanel == null) 217 return; 218 219 final Sequence seq = canvas.getSequence(); 220 // nothing to update 221 if (seq == null) 222 return; 223 224 // get bounds 225 final double xs = scaling[0]; 226 final double ys = scaling[1]; 227 final double zs = scaling[2]; 228 double z0, z1; 229 final double curZ = getZ(); 230 231 // all slices ? 232 if (curZ == -1d) 233 { 234 // set object depth on whole volume 235 z0 = 0; 236 z1 = seq.getSizeZ() * zs; 237 } 238 // fixed Z position 239 else 240 { 241 // set Z position 242 z0 = curZ * zs; 243 z1 = (curZ + 1d) * zs; 244 // z0 = (curZ - 0.5) * scaling[2]; 245 // z1 = (curZ + 0.5) * scaling[2]; 246 } 247 248 // update polydata object 249 final List<double[]> point3DList = new ArrayList<double[]>(); 250 final List<int[]> polyList = new ArrayList<int[]>(); 251 final double[] coords = new double[6]; 252 253 // starting position 254 double xm = 0d; 255 double ym = 0d; 256 double x0 = 0d; 257 double y0 = 0d; 258 double x1 = 0d; 259 double y1 = 0d; 260 int ind; 261 262 // use flat path 263 final PathIterator path = getPathIterator(null, 0.5d); 264 265 // build point data 266 while (!path.isDone()) 267 { 268 switch (path.currentSegment(coords)) 269 { 270 case PathIterator.SEG_MOVETO: 271 x0 = xm = coords[0] * xs; 272 y0 = ym = coords[1] * ys; 273 break; 274 275 case PathIterator.SEG_LINETO: 276 x1 = coords[0] * xs; 277 y1 = coords[1] * ys; 278 279 ind = point3DList.size(); 280 281 point3DList.add(new double[] {x0, y0, z0}); 282 point3DList.add(new double[] {x1, y1, z0}); 283 point3DList.add(new double[] {x0, y0, z1}); 284 point3DList.add(new double[] {x1, y1, z1}); 285 polyList.add(new int[] {1 + ind, 2 + ind, 0 + ind}); 286 polyList.add(new int[] {3 + ind, 2 + ind, 1 + ind}); 287 288 x0 = x1; 289 y0 = y1; 290 break; 291 292 case PathIterator.SEG_CLOSE: 293 x1 = xm; 294 y1 = ym; 295 296 ind = point3DList.size(); 297 298 point3DList.add(new double[] {x0, y0, z0}); 299 point3DList.add(new double[] {x1, y1, z0}); 300 point3DList.add(new double[] {x0, y0, z1}); 301 point3DList.add(new double[] {x1, y1, z1}); 302 polyList.add(new int[] {1 + ind, 2 + ind, 0 + ind}); 303 polyList.add(new int[] {3 + ind, 2 + ind, 1 + ind}); 304 305 x0 = x1; 306 y0 = y1; 307 break; 308 } 309 310 path.next(); 311 } 312 313 // convert to array 314 final double[][] vertices = new double[point3DList.size()][3]; 315 final int[][] indexes = new int[polyList.size()][3]; 316 317 ind = 0; 318 for (double[] pt3D : point3DList) 319 vertices[ind++] = pt3D; 320 321 ind = 0; 322 for (int[] poly : polyList) 323 indexes[ind++] = poly; 324 325 final vtkCellArray previousCells = vCells; 326 final vtkPoints previousPoints = vPoints; 327 vCells = VtkUtil.getCells(polyList.size(), VtkUtil.prepareCells(indexes)); 328 vPoints = VtkUtil.getPoints(vertices); 329 330 final Rectangle2D bounds = getBounds2D(); 331 332 // actor can be accessed in canvas3d for rendering so we need to synchronize access 333 vtkPanel.lock(); 334 try 335 { 336 // update outline data 337 VtkUtil.setOutlineBounds(outline, bounds.getMinX() * xs, bounds.getMaxX() * xs, bounds.getMinY() * ys, 338 bounds.getMaxY() * ys, z0, z1, canvas); 339 outlineMapper.Update(); 340 // update polygon data from cell and points 341 polyData.SetPolys(vCells); 342 polyData.SetPoints(vPoints); 343 polyMapper.Update(); 344 345 // release previous allocated VTK objects 346 if (previousCells != null) 347 previousCells.Delete(); 348 if (previousPoints != null) 349 previousPoints.Delete(); 350 } 351 finally 352 { 353 vtkPanel.unlock(); 354 } 355 356 // update color and others properties 357 updateVtkDisplayProperties(); 358 } 359 360 protected void updateVtkDisplayProperties() 361 { 362 if (actor == null) 363 return; 364 365 final VtkCanvas cnv = canvas3d.get(); 366 final vtkProperty vtkProperty = actor.GetProperty(); 367 final Color col = getDisplayColor(); 368 final double r = col.getRed() / 255d; 369 final double g = col.getGreen() / 255d; 370 final double b = col.getBlue() / 255d; 371 final double strk = getStroke(); 372 // final float opacity = getOpacity(); 373 374 final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null; 375 376 // we need to lock canvas as actor can be accessed during rendering 377 if (vtkPanel != null) 378 vtkPanel.lock(); 379 try 380 { 381 // set actors color 382 outlineActor.GetProperty().SetColor(r, g, b); 383 if (isSelected()) 384 { 385 outlineActor.GetProperty().SetRepresentationToWireframe(); 386 outlineActor.SetVisibility(1); 387 vtkInfo.Set(VtkCanvas.visibilityKey, 1); 388 } 389 else 390 { 391 outlineActor.GetProperty().SetRepresentationToPoints(); 392 outlineActor.SetVisibility(0); 393 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 394 } 395 vtkProperty.SetColor(r, g, b); 396 vtkProperty.SetPointSize(strk); 397 // opacity here is about ROI content, global opacity is handled by Layer 398 // vtkProperty.SetOpacity(opacity); 399 setVtkObjectsColor(col); 400 } 401 finally 402 { 403 if (vtkPanel != null) 404 vtkPanel.unlock(); 405 } 406 407 // need to repaint 408 painterChanged(); 409 } 410 411 protected void setVtkObjectsColor(Color color) 412 { 413 if (outline != null) 414 VtkUtil.setPolyDataColor(outline, color, canvas3d.get()); 415 if (polyData != null) 416 VtkUtil.setPolyDataColor(polyData, color, canvas3d.get()); 417 } 418 419 @Override 420 protected boolean updateFocus(InputEvent e, Point5D imagePoint, IcyCanvas canvas) 421 { 422 // specific VTK canvas processing 423 if (canvas instanceof VtkCanvas) 424 { 425 // mouse is over the ROI actor ? --> focus the ROI 426 final boolean focused = (actor != null) && (actor == ((VtkCanvas) canvas).getPickedObject()); 427 428 setFocused(focused); 429 430 return focused; 431 } 432 433 return super.updateFocus(e, imagePoint, canvas); 434 } 435 436 @Override 437 public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 438 { 439 if (isSelected() && !isReadOnly()) 440 { 441 if (isActiveFor(canvas)) 442 { 443 ROI2DShape.this.beginUpdate(); 444 try 445 { 446 // get control points list 447 final List<Anchor2D> controlPoints = getControlPoints(); 448 449 // send event to controls points first 450 for (Anchor2D pt : controlPoints) 451 pt.keyPressed(e, imagePoint, canvas); 452 453 // specific action for ROI2DShape 454 if (!e.isConsumed()) 455 { 456 final Sequence sequence = canvas.getSequence(); 457 458 switch (e.getKeyCode()) 459 { 460 case KeyEvent.VK_DELETE: 461 case KeyEvent.VK_BACK_SPACE: 462 final Anchor2D selectedPoint = getSelectedPoint(); 463 464 // try to remove selected point 465 if (removeSelectedPoint(canvas)) 466 { 467 // consume event 468 e.consume(); 469 470 // add undo operation 471 if (sequence != null) 472 sequence.addUndoableEdit(new Point2DRemovedROIEdit(ROI2DShape.this, 473 controlPoints, selectedPoint)); 474 } 475 break; 476 } 477 } 478 } 479 finally 480 { 481 ROI2DShape.this.endUpdate(); 482 } 483 } 484 } 485 486 // then send event to parent 487 super.keyPressed(e, imagePoint, canvas); 488 } 489 490 @Override 491 public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 492 { 493 if (isSelected() && !isReadOnly()) 494 { 495 if (isActiveFor(canvas)) 496 { 497 ROI2DShape.this.beginUpdate(); 498 try 499 { 500 // send event to controls points first 501 synchronized (controlPoints) 502 { 503 for (Anchor2D pt : controlPoints) 504 pt.keyReleased(e, imagePoint, canvas); 505 } 506 } 507 finally 508 { 509 ROI2DShape.this.endUpdate(); 510 } 511 } 512 } 513 514 // then send event to parent 515 super.keyReleased(e, imagePoint, canvas); 516 } 517 518 @Override 519 public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 520 { 521 if (isActiveFor(canvas)) 522 { 523 // check we can do the action 524 if (isSelected() && !isReadOnly()) 525 { 526 ROI2DShape.this.beginUpdate(); 527 try 528 { 529 // send event to controls points first 530 synchronized (controlPoints) 531 { 532 for (Anchor2D pt : controlPoints) 533 pt.mousePressed(e, imagePoint, canvas); 534 } 535 536 // specific action for this ROI 537 if (!e.isConsumed()) 538 { 539 // add point operation not supported on VtkCanvas (it could be but we don't want it) 540 if (canvas instanceof VtkCanvas) 541 return; 542 // we need it 543 if (imagePoint == null) 544 return; 545 546 // left button action 547 if (EventUtil.isLeftMouseButton(e)) 548 { 549 // ROI should not be focused to add point (for multi selection) 550 if (!isFocused()) 551 { 552 final boolean insertMode = EventUtil.isControlDown(e); 553 554 // insertion mode or creating the ROI ? --> add a new point 555 if (insertMode || isCreating()) 556 { 557 // try to add point 558 final Anchor2D point = addNewPoint(imagePoint.toPoint2D(), insertMode); 559 560 // point added ? 561 if (point != null) 562 { 563 // consume event 564 e.consume(); 565 566 final Sequence sequence = canvas.getSequence(); 567 568 // add undo operation 569 if (sequence != null) 570 sequence.addUndoableEdit( 571 new Point2DAddedROIEdit(ROI2DShape.this, point)); 572 } 573 } 574 } 575 } 576 } 577 } 578 finally 579 { 580 ROI2DShape.this.endUpdate(); 581 } 582 } 583 } 584 585 // then send event to parent 586 super.mousePressed(e, imagePoint, canvas); 587 } 588 589 @Override 590 public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 591 { 592 // not anymore the first move 593 firstMove = false; 594 595 if (isSelected() && !isReadOnly()) 596 { 597 // send event to controls points first 598 if (isActiveFor(canvas)) 599 { 600 final Sequence sequence = canvas.getSequence(); 601 602 ROI2DShape.this.beginUpdate(); 603 try 604 { 605 // default anchor action on mouse release 606 synchronized (controlPoints) 607 { 608 for (Anchor2D pt : controlPoints) 609 pt.mouseReleased(e, imagePoint, canvas); 610 } 611 } 612 finally 613 { 614 ROI2DShape.this.endUpdate(); 615 } 616 617 // prevent undo operation merging 618 if (sequence != null) 619 sequence.getUndoManager().noMergeForNextEdit(); 620 } 621 } 622 623 // then send event to parent 624 super.mouseReleased(e, imagePoint, canvas); 625 } 626 627 @Override 628 public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 629 { 630 if (isSelected() && !isReadOnly()) 631 { 632 // send event to controls points first 633 if (isActiveFor(canvas)) 634 { 635 ROI2DShape.this.beginUpdate(); 636 try 637 { 638 // default anchor action on mouse click 639 synchronized (controlPoints) 640 { 641 for (Anchor2D pt : controlPoints) 642 pt.mouseClick(e, imagePoint, canvas); 643 } 644 } 645 finally 646 { 647 ROI2DShape.this.endUpdate(); 648 } 649 } 650 } 651 652 // then send event to parent 653 super.mouseClick(e, imagePoint, canvas); 654 } 655 656 @Override 657 public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 658 { 659 if (isActiveFor(canvas)) 660 { 661 // check we can do the action 662 if (isSelected() && !isReadOnly()) 663 { 664 final Sequence sequence = canvas.getSequence(); 665 666 // send event to controls points first 667 ROI2DShape.this.beginUpdate(); 668 try 669 { 670 // default anchor action on mouse drag 671 synchronized (controlPoints) 672 { 673 for (Anchor2D pt : controlPoints) 674 { 675 final Point2D savedPosition; 676 677 // don't want to undo position change on first creation movement 678 if ((sequence != null) && (!isCreating() || !firstMove)) 679 savedPosition = pt.getPosition(); 680 else 681 savedPosition = null; 682 683 pt.mouseDrag(e, imagePoint, canvas); 684 685 // position changed and undo supported --> add undo operation 686 if ((sequence != null) && (savedPosition != null) 687 && !savedPosition.equals(pt.getPosition())) 688 sequence.addUndoableEdit( 689 new Point2DMovedROIEdit(ROI2DShape.this, pt, savedPosition)); 690 } 691 } 692 } 693 finally 694 { 695 ROI2DShape.this.endUpdate(); 696 } 697 } 698 } 699 700 // then send event to parent 701 super.mouseDrag(e, imagePoint, canvas); 702 } 703 704 @Override 705 public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 706 { 707 if (isActiveFor(canvas)) 708 { 709 // check we can do the action 710 if (isSelected() && !isReadOnly()) 711 { 712 // send event to controls points first 713 ROI2DShape.this.beginUpdate(); 714 try 715 { 716 // refresh control point state 717 synchronized (controlPoints) 718 { 719 for (Anchor2D pt : controlPoints) 720 pt.mouseMove(e, imagePoint, canvas); 721 } 722 } 723 finally 724 { 725 ROI2DShape.this.endUpdate(); 726 } 727 } 728 } 729 730 // then send event to parent 731 super.mouseMove(e, imagePoint, canvas); 732 } 733 734 /** 735 * Draw the ROI 736 */ 737 @Override 738 public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas) 739 { 740 if (canvas instanceof IcyCanvas2D) 741 { 742 // not supported 743 if (g == null) 744 return; 745 746 final Rectangle2D bounds = shape.getBounds2D(); 747 748 // enlarge bounds with stroke 749 final double over = getAdjustedStroke(canvas) * 2; 750 ShapeUtil.enlarge(bounds, over, over, true); 751 752 // define LOD level 753 final boolean shapeVisible = isVisible(bounds, g, canvas); 754 755 if (shapeVisible) 756 { 757 final boolean small = isSmall(bounds, g, canvas); 758 final boolean tiny = isTiny(bounds, g, canvas); 759 760 // draw shape 761 drawShape(g, sequence, canvas, small); 762 763 // draw control points (only if not tiny) 764 if (!tiny && isSelected() && !isReadOnly()) 765 { 766 // draw control point if selected 767 synchronized (controlPoints) 768 { 769 for (Anchor2D pt : controlPoints) 770 pt.paint(g, sequence, canvas, small); 771 } 772 } 773 } 774 } 775 776 if (canvas instanceof VtkCanvas) 777 { 778 // 3D canvas 779 final VtkCanvas cnv = (VtkCanvas) canvas; 780 // update reference if needed 781 if (canvas3d.get() != cnv) 782 canvas3d = new WeakReference<VtkCanvas>(cnv); 783 784 // initialize VTK objects if not yet done 785 if (actor == null) 786 initVtkObjects(); 787 788 // FIXME : need a better implementation 789 final double[] s = cnv.getVolumeScale(); 790 791 // scaling changed ? 792 if (!Arrays.equals(scaling, s)) 793 { 794 // update scaling 795 scaling = s; 796 // need rebuild 797 needRebuild = true; 798 } 799 800 // need to rebuild 3D data structures ? 801 if (needRebuild) 802 { 803 // request rebuild 3D objects 804 ThreadUtil.runSingle(this); 805 needRebuild = false; 806 } 807 808 final vtkRenderer renderer = cnv.getRenderer(); 809 810 // need to remove control points actor ? 811 synchronized (actorsToRemove) 812 { 813 for (Anchor2D anchor : actorsToRemove) 814 for (vtkProp prop : anchor.getProps()) 815 VtkUtil.removeProp(renderer, prop); 816 817 // done 818 actorsToRemove.clear(); 819 } 820 // need to add control points actor ? 821 synchronized (actorsToAdd) 822 { 823 for (Anchor2D anchor : actorsToAdd) 824 for (vtkProp prop : anchor.getProps()) 825 VtkUtil.addProp(renderer, prop); 826 827 // done 828 actorsToAdd.clear(); 829 } 830 831 // needed to forward paint event to control point 832 synchronized (controlPoints) 833 { 834 for (Anchor2D pt : controlPoints) 835 pt.paint(null, sequence, canvas); 836 } 837 } 838 } 839 840 /** 841 * Draw the shape 842 */ 843 protected void drawShape(Graphics2D g, Sequence sequence, IcyCanvas canvas, boolean simplified) 844 { 845 drawShape(g, sequence, canvas, shape, simplified); 846 } 847 848 /** 849 * Draw the shape 850 */ 851 protected void drawShape(Graphics2D g, Sequence sequence, IcyCanvas canvas, Shape shape, boolean simplified) 852 { 853 final Graphics2D g2 = (Graphics2D) g.create(); 854 855 // simplified draw 856 if (simplified) 857 { 858 g2.setColor(getDisplayColor()); 859 860 // fill content if selected 861 if (isSelected()) 862 g2.fill(shape); 863 864 // then draw shape 865 g2.setStroke(new BasicStroke((float) ROI.getAdjustedStroke(canvas, stroke))); 866 g2.draw(shape); 867 } 868 // normal draw 869 else 870 { 871 // ROI selected and has content to draw ? 872 if (isSelected()) 873 { 874 final AlphaComposite prevAlpha = (AlphaComposite) g2.getComposite(); 875 876 float newAlpha = prevAlpha.getAlpha() * getOpacity(); 877 newAlpha = Math.min(1f, newAlpha); 878 newAlpha = Math.max(0f, newAlpha); 879 880 // show content with an alpha factor 881 g2.setComposite(prevAlpha.derive(newAlpha)); 882 g2.setColor(getDisplayColor()); 883 884 // only fill closed shape 885 g2.fill(ShapeUtil.getClosedPath(shape)); 886 887 // restore composite and set stroke 888 g2.setComposite(prevAlpha); 889 g2.setStroke(new BasicStroke((float) ROI.getAdjustedStroke(canvas, stroke + 1d))); 890 891 // then draw object shape without border 892 g2.draw(shape); 893 } 894 else 895 { 896 // draw border 897 g2.setStroke(new BasicStroke((float) ROI.getAdjustedStroke(canvas, stroke + 1d))); 898 g2.setColor(Color.black); 899 g2.draw(shape); 900 // draw shape 901 g2.setStroke(new BasicStroke((float) ROI.getAdjustedStroke(canvas, stroke))); 902 g2.setColor(getDisplayColor()); 903 g2.draw(shape); 904 } 905 } 906 907 g2.dispose(); 908 } 909 910 /** 911 * Returns <code>true</code> if the specified bounds should be considered as "tiny" in the 912 * specified canvas / graphics context. 913 */ 914 protected boolean isVisible(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas) 915 { 916 return GraphicsUtil.isVisible(g, bounds); 917 } 918 919 /** 920 * Returns <code>true</code> if the specified bounds should be considered as "tiny" in the 921 * specified canvas / graphics context. 922 */ 923 protected boolean isSmall(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas) 924 { 925 if (isCreating()) 926 return false; 927 928 final double scale = Math.max(Math.abs(canvas.getScaleX()), Math.abs(canvas.getScaleY())); 929 final double size = Math.max(scale * bounds.getWidth(), scale * bounds.getHeight()); 930 931 return size < LOD_SMALL; 932 } 933 934 /** 935 * Returns <code>true</code> if the specified bounds should be considered as "tiny" in the 936 * specified canvas / graphics context. 937 */ 938 protected boolean isTiny(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas) 939 { 940 if (isCreating()) 941 return false; 942 943 final double scale = Math.max(Math.abs(canvas.getScaleX()), Math.abs(canvas.getScaleY())); 944 final double size = Math.max(scale * bounds.getWidth(), scale * bounds.getHeight()); 945 946 return size < LOD_TINY; 947 } 948 949 @Override 950 public void setColor(Color value) 951 { 952 beginUpdate(); 953 try 954 { 955 super.setColor(value); 956 957 // also change colors of controls points 958 final Color focusedColor = getFocusedColor(); 959 960 synchronized (controlPoints) 961 { 962 for (Anchor2D anchor : controlPoints) 963 { 964 anchor.setColor(value); 965 anchor.setSelectedColor(focusedColor); 966 } 967 } 968 } 969 finally 970 { 971 endUpdate(); 972 } 973 } 974 975 @Override 976 public vtkProp[] getProps() 977 { 978 // initialize VTK objects if not yet done 979 if (actor == null) 980 initVtkObjects(); 981 982 final List<vtkProp> result = new ArrayList<vtkProp>(); 983 984 // add VTK objects from ROI shape 985 result.add(actor); 986 result.add(outlineActor); 987 988 // then add VTK objects from controls points 989 synchronized (controlPoints) 990 { 991 for (Anchor2D pt : controlPoints) 992 for (vtkProp prop : pt.getProps()) 993 result.add(prop); 994 } 995 996 return result.toArray(new vtkProp[result.size()]); 997 } 998 999 @Override 1000 public void run() 1001 { 1002 rebuildVtkObjects(); 1003 } 1004 } 1005 1006 /** 1007 * ROI shape (in image coordinates) 1008 */ 1009 protected final Shape shape; 1010 /** 1011 * control points 1012 */ 1013 protected final List<Anchor2D> controlPoints; 1014 1015 /** 1016 * internals 1017 */ 1018 protected final Anchor2DPositionListener anchor2DPositionListener; 1019 protected final OverlayListener anchor2DOverlayListener; 1020 protected boolean firstMove; 1021 1022 public ROI2DShape(Shape shape) 1023 { 1024 super(); 1025 1026 this.shape = shape; 1027 controlPoints = new ArrayList<Anchor2D>(); 1028 firstMove = true; 1029 1030 anchor2DPositionListener = new Anchor2DPositionListener() 1031 { 1032 @Override 1033 public void positionChanged(Anchor2D source) 1034 { 1035 controlPointPositionChanged(source); 1036 } 1037 }; 1038 1039 anchor2DOverlayListener = new OverlayListener() 1040 { 1041 @Override 1042 public void overlayChanged(OverlayEvent event) 1043 { 1044 controlPointOverlayChanged(event); 1045 } 1046 }; 1047 } 1048 1049 @Override 1050 public String getDefaultName() 1051 { 1052 return "Shape2D"; 1053 } 1054 1055 @Override 1056 protected ROI2DShapePainter createPainter() 1057 { 1058 return new ROI2DShapePainter(); 1059 } 1060 1061 /** 1062 * build a new anchor with specified position 1063 */ 1064 protected Anchor2D createAnchor(Point2D pos) 1065 { 1066 return new Anchor2D(pos.getX(), pos.getY(), getColor(), getFocusedColor()); 1067 } 1068 1069 /** 1070 * build a new anchor with specified position 1071 */ 1072 protected Anchor2D createAnchor(double x, double y) 1073 { 1074 return createAnchor(new Point2D.Double(x, y)); 1075 } 1076 1077 /** 1078 * @return the shape 1079 */ 1080 public Shape getShape() 1081 { 1082 return shape; 1083 } 1084 1085 /** 1086 * Rebuild shape.<br> 1087 * This method should be overridden by derived classes which<br> 1088 * have to call the super.updateShape() method at end. 1089 */ 1090 protected void updateShape() 1091 { 1092 final int z = getZ(); 1093 1094 beginUpdate(); 1095 try 1096 { 1097 // fix control points Z position if needed 1098 synchronized (controlPoints) 1099 { 1100 for (Anchor2D points : controlPoints) 1101 points.setZ(z); 1102 } 1103 } 1104 finally 1105 { 1106 endUpdate(); 1107 } 1108 1109 // the shape should have been rebuilt here 1110 ((ROI2DShapePainter) painter).needRebuild = true; 1111 } 1112 1113 /** 1114 * Return true if this ROI support adding new point 1115 */ 1116 public boolean canAddPoint() 1117 { 1118 return true; 1119 } 1120 1121 /** 1122 * Return true if this ROI support removing point 1123 */ 1124 public boolean canRemovePoint() 1125 { 1126 return true; 1127 } 1128 1129 /** 1130 * Internal use only 1131 */ 1132 protected void addPoint(Anchor2D pt) 1133 { 1134 addPoint(pt, -1); 1135 } 1136 1137 /** 1138 * Internal use only, use {@link #addNewPoint(Point2D, boolean)} instead. 1139 */ 1140 public void addPoint(Anchor2D pt, int index) 1141 { 1142 // set Z position 1143 pt.setZ(getZ()); 1144 // set visible state 1145 pt.setVisible(isSelected()); 1146 // add listeners 1147 pt.addPositionListener(anchor2DPositionListener); 1148 pt.addOverlayListener(anchor2DOverlayListener); 1149 1150 synchronized (controlPoints) 1151 { 1152 if (index == -1) 1153 controlPoints.add(pt); 1154 else 1155 controlPoints.add(index, pt); 1156 } 1157 1158 synchronized (((ROI2DShapePainter) getOverlay()).actorsToAdd) 1159 { 1160 // store it in the "actor to add" list 1161 ((ROI2DShapePainter) getOverlay()).actorsToAdd.add(pt); 1162 } 1163 synchronized (((ROI2DShapePainter) getOverlay()).actorsToRemove) 1164 { 1165 // and remove it from the "actor to remove" list 1166 ((ROI2DShapePainter) getOverlay()).actorsToRemove.remove(pt); 1167 } 1168 1169 roiChanged(true); 1170 } 1171 1172 /** 1173 * @deprecated Use {@link #addNewPoint(Point2D, boolean)} instead. 1174 */ 1175 @Deprecated 1176 public boolean addPoint(Point2D pos, boolean insert) 1177 { 1178 return (addNewPoint(pos, insert) != null); 1179 } 1180 1181 /** 1182 * @deprecated Use {@link #addNewPoint(Point2D, boolean)} instead. 1183 */ 1184 @Deprecated 1185 public boolean addPointAt(Point2D pos, boolean insert) 1186 { 1187 return (addNewPoint(pos, insert) != null); 1188 } 1189 1190 /** 1191 * Add a new point to this shape ROI. 1192 * 1193 * @param pos 1194 * position of the new point 1195 * @param insert 1196 * if set to <code>true</code> the new point will be inserted between the 2 closest points (in pixels 1197 * distance) else the new point is inserted at the end of the point list 1198 * @param select 1199 * select the new created point 1200 * @return the new created Anchor2D point if the operation succeed or <code>null</code> otherwise (if the ROI does 1201 * not support this operation for instance) 1202 */ 1203 public Anchor2D addNewPoint(Point2D pos, boolean insert, boolean select) 1204 { 1205 if (!canAddPoint()) 1206 return null; 1207 1208 final Anchor2D pt = createAnchor(pos); 1209 1210 if (insert) 1211 // insert mode ? --> place the new point with closest points 1212 addPoint(pt, getInsertPointPosition(pos)); 1213 else 1214 // just add the new point at last position 1215 addPoint(pt); 1216 1217 // always select 1218 if (select) 1219 pt.setSelected(true); 1220 1221 return pt; 1222 } 1223 1224 /** 1225 * Add a new point to this shape ROI. 1226 * 1227 * @param pos 1228 * position of the new point 1229 * @param insert 1230 * if set to <code>true</code> the new point will be inserted between the 2 closest 1231 * points (in pixels distance) else the new point is inserted at the end of the point 1232 * list 1233 * @return the new created Anchor2D point if the operation succeed or <code>null</code> otherwise (if the ROI does 1234 * not support this operation for instance) 1235 */ 1236 public Anchor2D addNewPoint(Point2D pos, boolean insert) 1237 { 1238 return addNewPoint(pos, insert, true); 1239 } 1240 1241 /** 1242 * internal use only 1243 */ 1244 protected boolean removePoint(IcyCanvas canvas, Anchor2D pt) 1245 { 1246 boolean empty; 1247 1248 pt.removeOverlayListener(anchor2DOverlayListener); 1249 pt.removePositionListener(anchor2DPositionListener); 1250 1251 synchronized (controlPoints) 1252 { 1253 controlPoints.remove(pt); 1254 empty = controlPoints.isEmpty(); 1255 } 1256 1257 synchronized (((ROI2DShapePainter) getOverlay()).actorsToRemove) 1258 { 1259 // store it in the "actor to remove" list 1260 ((ROI2DShapePainter) getOverlay()).actorsToRemove.add(pt); 1261 } 1262 synchronized (((ROI2DShapePainter) getOverlay()).actorsToAdd) 1263 { 1264 // and remove it from the "actor to add" list 1265 ((ROI2DShapePainter) getOverlay()).actorsToAdd.remove(pt); 1266 } 1267 1268 // empty ROI ? --> remove from all sequence 1269 if (empty) 1270 remove(); 1271 else 1272 roiChanged(true); 1273 1274 return true; 1275 } 1276 1277 /** 1278 * This method give you lower level access on point remove operation but can be unsafe.<br/> 1279 * Use {@link #removeSelectedPoint(IcyCanvas)} when possible. 1280 */ 1281 public boolean removePoint(Anchor2D pt) 1282 { 1283 return removePoint(null, pt); 1284 } 1285 1286 /** 1287 * internal use only (used for fast clear) 1288 */ 1289 protected void removeAllPoint() 1290 { 1291 synchronized (controlPoints) 1292 { 1293 synchronized (((ROI2DShapePainter) getOverlay()).actorsToRemove) 1294 { 1295 // store all points in the "actor to remove" list 1296 ((ROI2DShapePainter) getOverlay()).actorsToRemove.addAll(controlPoints); 1297 } 1298 synchronized (((ROI2DShapePainter) getOverlay()).actorsToAdd) 1299 { 1300 // and remove them from the "actor to add" list 1301 ((ROI2DShapePainter) getOverlay()).actorsToAdd.removeAll(controlPoints); 1302 } 1303 1304 for (Anchor2D pt : controlPoints) 1305 { 1306 pt.removeOverlayListener(anchor2DOverlayListener); 1307 pt.removePositionListener(anchor2DPositionListener); 1308 } 1309 1310 controlPoints.clear(); 1311 } 1312 } 1313 1314 /** 1315 * @deprecated Use {@link #removeSelectedPoint(IcyCanvas)} instead. 1316 */ 1317 @Deprecated 1318 public boolean removePointAt(IcyCanvas canvas, Point2D imagePoint) 1319 { 1320 if (!canRemovePoint()) 1321 return false; 1322 1323 // first we try to remove selected point 1324 if (!removeSelectedPoint(canvas)) 1325 { 1326 // if no point selected, try to select and remove a point at specified position 1327 if (selectPointAt(canvas, imagePoint)) 1328 return removeSelectedPoint(canvas); 1329 1330 return false; 1331 } 1332 1333 return true; 1334 } 1335 1336 /** 1337 * @deprecated Use {@link #removeSelectedPoint(IcyCanvas)} instead. 1338 */ 1339 @Deprecated 1340 protected boolean removeSelectedPoint(IcyCanvas canvas, Point2D imagePoint) 1341 { 1342 return removeSelectedPoint(canvas); 1343 } 1344 1345 /** 1346 * Remove the current selected point. 1347 */ 1348 public boolean removeSelectedPoint(IcyCanvas canvas) 1349 { 1350 if (!canRemovePoint()) 1351 return false; 1352 1353 final Anchor2D selectedPoint = getSelectedPoint(); 1354 1355 if (selectedPoint == null) 1356 return false; 1357 1358 synchronized (controlPoints) 1359 { 1360 final int index = controlPoints.indexOf(selectedPoint); 1361 1362 // try to remove point 1363 if (!removePoint(canvas, selectedPoint)) 1364 return false; 1365 1366 // still have control points 1367 if (!controlPoints.isEmpty()) 1368 { 1369 // save the point position 1370 final Point2D imagePoint = selectedPoint.getPosition(); 1371 1372 // we are using PathAnchor2D ? 1373 if (selectedPoint instanceof PathAnchor2D) 1374 { 1375 final PathAnchor2D selectedPathPoint = (PathAnchor2D) selectedPoint; 1376 1377 switch (selectedPathPoint.getType()) 1378 { 1379 // we removed a MOVETO point ? 1380 case PathIterator.SEG_MOVETO: 1381 // try to set next point to MOVETO state 1382 if (index < controlPoints.size()) 1383 { 1384 final PathAnchor2D nextPoint = (PathAnchor2D) controlPoints.get(index); 1385 1386 // next point is a CLOSE one ? 1387 if (nextPoint.getType() == PathIterator.SEG_CLOSE) 1388 { 1389 // delete it 1390 if (removePoint(canvas, nextPoint)) 1391 { 1392 // it was the last control point --> delete ROI 1393 if (controlPoints.size() == 0) 1394 remove(); 1395 } 1396 } 1397 else 1398 // whatever is next point, set it to MOVETO 1399 nextPoint.setType(PathIterator.SEG_MOVETO); 1400 } 1401 break; 1402 1403 // we removed a CLOSE point ? 1404 case PathIterator.SEG_CLOSE: 1405 // try to set previous point to CLOSE state 1406 if (index > 0) 1407 { 1408 final PathAnchor2D prevPoint = (PathAnchor2D) controlPoints.get(index - 1); 1409 1410 // next point is a MOVETO one ? 1411 if (prevPoint.getType() == PathIterator.SEG_MOVETO) 1412 { 1413 // delete it 1414 if (removePoint(canvas, prevPoint)) 1415 { 1416 // it was the last control point --> delete ROI 1417 if (controlPoints.size() == 0) 1418 remove(); 1419 } 1420 } 1421 else 1422 // whatever is previous point, set it to CLOSE 1423 prevPoint.setType(PathIterator.SEG_CLOSE); 1424 } 1425 break; 1426 } 1427 } 1428 1429 // select a new point if possible 1430 if (controlPoints.size() > 0) 1431 selectPointAt(canvas, imagePoint); 1432 } 1433 } 1434 1435 return true; 1436 } 1437 1438 protected Anchor2D getSelectedPoint() 1439 { 1440 synchronized (controlPoints) 1441 { 1442 for (Anchor2D pt : controlPoints) 1443 if (pt.isSelected()) 1444 return pt; 1445 } 1446 1447 return null; 1448 } 1449 1450 /** 1451 * @deprecated Use {@link #getSelectedPoint()} instead. 1452 */ 1453 @Deprecated 1454 protected Anchor2D getSelectedControlPoint() 1455 { 1456 return getSelectedPoint(); 1457 } 1458 1459 @Override 1460 public boolean hasSelectedPoint() 1461 { 1462 return (getSelectedPoint() != null); 1463 } 1464 1465 protected boolean selectPointAt(IcyCanvas canvas, Point2D imagePoint) 1466 { 1467 synchronized (controlPoints) 1468 { 1469 // find the new selected control point 1470 for (Anchor2D pt : controlPoints) 1471 { 1472 // control point is overlapped ? 1473 if (pt.isOver(canvas, imagePoint)) 1474 { 1475 // select it 1476 pt.setSelected(true); 1477 return true; 1478 } 1479 } 1480 } 1481 1482 return false; 1483 } 1484 1485 @Override 1486 public void unselectAllPoints() 1487 { 1488 beginUpdate(); 1489 try 1490 { 1491 synchronized (controlPoints) 1492 { 1493 // unselect all point 1494 for (Anchor2D pt : controlPoints) 1495 pt.setSelected(false); 1496 } 1497 } 1498 finally 1499 { 1500 endUpdate(); 1501 } 1502 }; 1503 1504 @SuppressWarnings("static-method") 1505 protected double getTotalDistance(List<Point2D> points, double factorX, double factorY) 1506 { 1507 // default implementation of total length connect the last point 1508 return Point2DUtil.getTotalDistance(points, factorX, factorY, true); 1509 } 1510 1511 // default implementation for ROI2DShape 1512 @Override 1513 public double computeNumberOfContourPoints() 1514 { 1515 return getTotalDistance(getPointsInternal(), 1d, 1d); 1516 } 1517 1518 @Override 1519 public double getLength(Sequence sequence) throws UnsupportedOperationException 1520 { 1521 // cannot be cached because dependent from Sequence metadata 1522 return getTotalDistance(getPointsInternal(), sequence.getPixelSizeX(), sequence.getPixelSizeY()); 1523 } 1524 1525 /** 1526 * Find best insert position for specified point 1527 */ 1528 protected int getInsertPointPosition(Point2D pos) 1529 { 1530 final List<Point2D> points = getPointsInternal(); 1531 1532 final int size = points.size(); 1533 // by default we use last position 1534 int result = size; 1535 double minDistance = Double.MAX_VALUE; 1536 1537 // we try all cases 1538 for (int i = size; i >= 0; i--) 1539 { 1540 // add point at current position 1541 points.add(i, pos); 1542 1543 // calculate total distance 1544 final double d = getTotalDistance(points, 1d, 1d); 1545 // minimum distance ? 1546 if (d < minDistance) 1547 { 1548 // save index 1549 minDistance = d; 1550 result = i; 1551 } 1552 1553 // remove point from current position 1554 points.remove(i); 1555 } 1556 1557 return result; 1558 } 1559 1560 /** 1561 * Returns true if specified point coordinates overlap the ROI edge. 1562 */ 1563 @Override 1564 public boolean isOverEdge(IcyCanvas canvas, double x, double y) 1565 { 1566 // use bigger stroke for isOver test for easier intersection 1567 final double strk = painter.getAdjustedStroke(canvas) * 3; 1568 final Rectangle2D rect = new Rectangle2D.Double(x - (strk * 0.5), y - (strk * 0.5), strk, strk); 1569 final Rectangle2D roiBounds = getBounds2D(); 1570 1571 // special test for empty object (point or orthogonal line) 1572 if (roiBounds.isEmpty()) 1573 return rect.intersectsLine(roiBounds.getMinX(), roiBounds.getMinY(), roiBounds.getMaxX(), 1574 roiBounds.getMaxY()); 1575 1576 // fast intersect test to start with 1577 if (roiBounds.intersects(rect)) 1578 // use flatten path, intersects on curved shape return incorrect result 1579 return ShapeUtil.pathIntersects(getPathIterator(null, 0.1), rect); 1580 1581 return false; 1582 } 1583 1584 // @Override 1585 // public boolean isOverPoint(IcyCanvas canvas, double x, double y) 1586 // { 1587 // if (isSelected()) 1588 // { 1589 // for (Anchor2D pt : controlPoints) 1590 // if (pt.isOver(canvas, x, y)) 1591 // return true; 1592 // } 1593 // 1594 // return false; 1595 // } 1596 1597 /** 1598 * Return the list of control points for this ROI. 1599 */ 1600 public List<Anchor2D> getControlPoints() 1601 { 1602 synchronized (controlPoints) 1603 { 1604 return new ArrayList<Anchor2D>(controlPoints); 1605 } 1606 } 1607 1608 /** 1609 * Return the list of positions of control points for this ROI. 1610 */ 1611 public ArrayList<Point2D> getPoints() 1612 { 1613 final ArrayList<Point2D> result = new ArrayList<Point2D>(); 1614 1615 synchronized (controlPoints) 1616 { 1617 for (Anchor2D pt : controlPoints) 1618 result.add(pt.getPosition()); 1619 } 1620 1621 return result; 1622 } 1623 1624 /** 1625 * Return the list of positions of control points for this ROI.<br> 1626 * This is the direct internal position reference, don't modify them ! 1627 */ 1628 protected ArrayList<Point2D> getPointsInternal() 1629 { 1630 final ArrayList<Point2D> result = new ArrayList<Point2D>(); 1631 1632 synchronized (controlPoints) 1633 { 1634 for (Anchor2D pt : controlPoints) 1635 result.add(pt.getPositionInternal()); 1636 } 1637 1638 return result; 1639 } 1640 1641 @Override 1642 public PathIterator getPathIterator(AffineTransform at) 1643 { 1644 return shape.getPathIterator(at); 1645 } 1646 1647 @Override 1648 public PathIterator getPathIterator(AffineTransform at, double flatness) 1649 { 1650 return shape.getPathIterator(at, flatness); 1651 } 1652 1653 @Override 1654 public boolean[] getBooleanMask(int x, int y, int width, int height, boolean inclusive) 1655 { 1656 if ((width <= 0) || (height <= 0)) 1657 return new boolean[0]; 1658 1659 // special case 1660 if (inclusive && (width == 1) && (height == 1) && getPosition().equals(new Point(x, y))) 1661 return new boolean[] {true}; 1662 1663 final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); 1664 final Graphics2D g = img.createGraphics(); 1665 1666 // we want accurate rendering as we use the image for the mask 1667 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); 1668 g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); 1669 1670 // translate back to origin and pixel center 1671 g.translate(-(x - 0.5d), -(y - 0.5d)); 1672 1673 // fill content 1674 g.setColor(Color.white); 1675 // only fill closed shapes 1676 g.fill(ShapeUtil.getClosedPath(shape)); 1677 // we want edge as well 1678 if (inclusive) 1679 g.draw(shape); 1680 // TODO: do we really need that ?? 1681 else 1682 { 1683 // remove edge from content as fill operation may be a bit off 1684 g.setColor(Color.black); 1685 g.draw(shape); 1686 } 1687 1688 g.dispose(); 1689 1690 final byte[] buffer = ((DataBufferByte) img.getRaster().getDataBuffer()).getData(); 1691 final boolean[] result = new boolean[width * height]; 1692 1693 // compute mask from image 1694 for (int i = 0; i < result.length; i++) 1695 result[i] = (buffer[i] != 0); 1696 1697 return result; 1698 } 1699 1700 @Override 1701 public boolean contains(Point2D p) 1702 { 1703 return shape.contains(p); 1704 } 1705 1706 @Override 1707 public boolean contains(Rectangle2D r) 1708 { 1709 return shape.contains(r); 1710 } 1711 1712 @Override 1713 public boolean contains(double x, double y) 1714 { 1715 return shape.contains(x, y); 1716 } 1717 1718 @Override 1719 public boolean contains(double x, double y, double w, double h) 1720 { 1721 return shape.contains(x, y, w, h); 1722 } 1723 1724 @Override 1725 public boolean intersects(Rectangle2D r) 1726 { 1727 return shape.intersects(r); 1728 } 1729 1730 @Override 1731 public boolean intersects(double x, double y, double w, double h) 1732 { 1733 return shape.intersects(x, y, w, h); 1734 } 1735 1736 @Override 1737 public Rectangle2D computeBounds2D() 1738 { 1739 final Rectangle2D result = shape.getBounds2D(); 1740 1741 // shape shouldn't be empty (even for single Point) --> always use a minimal bounds 1742 if (result.isEmpty()) 1743 { 1744 result.setFrame(result.getX(), result.getY(), Math.max(result.getWidth(), 0.001d), 1745 Math.max(result.getHeight(), 0.001d)); 1746 } 1747 1748 return result; 1749 } 1750 1751 @Override 1752 public ROI getUnion(ROI roi) throws UnsupportedOperationException 1753 { 1754 if (roi instanceof ROI2DShape) 1755 { 1756 final ROI2DShape roiShape = (ROI2DShape) roi; 1757 1758 // only if on same position 1759 if ((getZ() == roiShape.getZ()) && (getT() == roiShape.getT()) && (getC() == roiShape.getC())) 1760 { 1761 final ROI2DPath result = new ROI2DPath(ShapeUtil.union(this, roiShape)); 1762 1763 // don't forget to restore 5D position 1764 result.setZ(getZ()); 1765 result.setT(getT()); 1766 result.setC(getC()); 1767 1768 return result; 1769 } 1770 } 1771 1772 return super.getUnion(roi); 1773 } 1774 1775 @Override 1776 public ROI getIntersection(ROI roi) throws UnsupportedOperationException 1777 { 1778 if (roi instanceof ROI2DShape) 1779 { 1780 final ROI2DShape roiShape = (ROI2DShape) roi; 1781 1782 // only if on same position 1783 if ((getZ() == roiShape.getZ()) && (getT() == roiShape.getT()) && (getC() == roiShape.getC())) 1784 { 1785 final ROI2DPath result = new ROI2DPath(ShapeUtil.intersect(this, roiShape)); 1786 1787 // don't forget to restore 5D position 1788 result.setZ(getZ()); 1789 result.setT(getT()); 1790 result.setC(getC()); 1791 1792 return result; 1793 } 1794 } 1795 1796 return super.getIntersection(roi); 1797 } 1798 1799 @Override 1800 public ROI getExclusiveUnion(ROI roi) throws UnsupportedOperationException 1801 { 1802 if (roi instanceof ROI2DShape) 1803 { 1804 final ROI2DShape roiShape = (ROI2DShape) roi; 1805 1806 // only if on same position 1807 if ((getZ() == roiShape.getZ()) && (getT() == roiShape.getT()) && (getC() == roiShape.getC())) 1808 { 1809 final ROI2DPath result = new ROI2DPath(ShapeUtil.exclusiveUnion(this, roiShape)); 1810 1811 // don't forget to restore 5D position 1812 result.setZ(getZ()); 1813 result.setT(getT()); 1814 result.setC(getC()); 1815 1816 return result; 1817 } 1818 } 1819 1820 return super.getExclusiveUnion(roi); 1821 } 1822 1823 @Override 1824 public ROI getSubtraction(ROI roi) throws UnsupportedOperationException 1825 { 1826 if (roi instanceof ROI2DShape) 1827 { 1828 final ROI2DShape roiShape = (ROI2DShape) roi; 1829 1830 // only if on same position 1831 if ((getZ() == roiShape.getZ()) && (getT() == roiShape.getT()) && (getC() == roiShape.getC())) 1832 { 1833 final ROI2DPath result = new ROI2DPath(ShapeUtil.subtract(this, roiShape)); 1834 1835 // don't forget to restore 5D position 1836 result.setZ(getZ()); 1837 result.setT(getT()); 1838 result.setC(getC()); 1839 1840 return result; 1841 } 1842 } 1843 1844 return super.getSubtraction(roi); 1845 } 1846 1847 @Override 1848 public boolean canTranslate() 1849 { 1850 return true; 1851 } 1852 1853 @Override 1854 public void translate(double dx, double dy) 1855 { 1856 beginUpdate(); 1857 try 1858 { 1859 synchronized (controlPoints) 1860 { 1861 for (Anchor2D pt : controlPoints) 1862 pt.translate(dx, dy); 1863 } 1864 } 1865 finally 1866 { 1867 endUpdate(); 1868 } 1869 } 1870 1871 /** 1872 * Called when anchor position changed 1873 */ 1874 public void controlPointPositionChanged(Anchor2D source) 1875 { 1876 // anchor(s) position changed --> ROI changed 1877 roiChanged(true); 1878 } 1879 1880 /** 1881 * Called when anchor overlay changed 1882 */ 1883 public void controlPointOverlayChanged(OverlayEvent event) 1884 { 1885 // we only mind about painter change from anchor... 1886 if (event.getType() == OverlayEventType.PAINTER_CHANGED) 1887 { 1888 // we have a control point selected --> remove focus on ROI 1889 if (hasSelectedPoint()) 1890 setFocused(false); 1891 1892 // anchor changed --> ROI painter changed 1893 getOverlay().painterChanged(); 1894 } 1895 } 1896 1897 /** 1898 * Called when anchor painter changed, provided only for backward compatibility.<br> 1899 * Don't use it. 1900 */ 1901 @SuppressWarnings({"deprecation"}) 1902 public void painterChanged(PainterEvent event) 1903 { 1904 // ignore it now 1905 } 1906 1907 /** 1908 * roi changed 1909 */ 1910 @Override 1911 public void onChanged(CollapsibleEvent object) 1912 { 1913 final ROIEvent event = (ROIEvent) object; 1914 1915 // do here global process on ROI change 1916 switch (event.getType()) 1917 { 1918 case ROI_CHANGED: 1919 // refresh shape 1920 updateShape(); 1921 break; 1922 1923 case FOCUS_CHANGED: 1924 ((ROI2DShapePainter) getOverlay()).updateVtkDisplayProperties(); 1925 break; 1926 1927 case SELECTION_CHANGED: 1928 final boolean s = isSelected(); 1929 1930 beginUpdate(); 1931 try 1932 { 1933 // set control points visible or not 1934 synchronized (controlPoints) 1935 { 1936 for (Anchor2D pt : controlPoints) 1937 pt.setVisible(s); 1938 } 1939 1940 // unselect if not visible 1941 if (!s) 1942 unselectAllPoints(); 1943 } 1944 finally 1945 { 1946 endUpdate(); 1947 } 1948 1949 ((ROI2DShapePainter) getOverlay()).updateVtkDisplayProperties(); 1950 break; 1951 1952 case PROPERTY_CHANGED: 1953 final String property = event.getPropertyName(); 1954 1955 if (StringUtil.equals(property, PROPERTY_STROKE) || StringUtil.equals(property, PROPERTY_COLOR) 1956 || StringUtil.equals(property, PROPERTY_OPACITY)) 1957 ((ROI2DShapePainter) getOverlay()).updateVtkDisplayProperties(); 1958 break; 1959 1960 default: 1961 break; 1962 } 1963 1964 super.onChanged(object); 1965 } 1966 1967 @Override 1968 public boolean loadFromXML(Node node) 1969 { 1970 beginUpdate(); 1971 try 1972 { 1973 if (!super.loadFromXML(node)) 1974 return false; 1975 1976 firstMove = false; 1977 // unselect all control points 1978 unselectAllPoints(); 1979 } 1980 finally 1981 { 1982 endUpdate(); 1983 } 1984 1985 return true; 1986 } 1987}