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.Rectangle; 027import java.awt.RenderingHints; 028import java.awt.Shape; 029import java.awt.event.InputEvent; 030import java.awt.event.KeyEvent; 031import java.awt.event.MouseEvent; 032import java.awt.geom.Ellipse2D; 033import java.awt.geom.Point2D; 034import java.awt.geom.Rectangle2D; 035import java.awt.image.BufferedImage; 036import java.awt.image.DataBufferByte; 037import java.awt.image.IndexColorModel; 038import java.lang.ref.WeakReference; 039import java.util.Arrays; 040 041import org.w3c.dom.Node; 042 043import icy.canvas.IcyCanvas; 044import icy.canvas.IcyCanvas2D; 045import icy.common.CollapsibleEvent; 046import icy.gui.inspector.RoisPanel; 047import icy.image.ImageUtil; 048import icy.main.Icy; 049import icy.painter.VtkPainter; 050import icy.resource.ResourceUtil; 051import icy.roi.BooleanMask2D; 052import icy.roi.ROI; 053import icy.roi.ROI2D; 054import icy.roi.ROIEvent; 055import icy.roi.edit.Area2DChangeROIEdit; 056import icy.sequence.Sequence; 057import icy.system.thread.ThreadUtil; 058import icy.type.point.Point5D; 059import icy.type.point.Point5D.Double; 060import icy.type.rectangle.Rectangle3D; 061import icy.util.EventUtil; 062import icy.util.GraphicsUtil; 063import icy.util.ShapeUtil; 064import icy.util.StringUtil; 065import icy.util.XMLUtil; 066import icy.vtk.IcyVtkPanel; 067import icy.vtk.VtkUtil; 068import plugins.kernel.canvas.VtkCanvas; 069import vtk.vtkActor; 070import vtk.vtkImageData; 071import vtk.vtkInformation; 072import vtk.vtkPolyData; 073import vtk.vtkPolyDataMapper; 074import vtk.vtkProp; 075 076/** 077 * ROI Area type.<br> 078 * Use a bitmap mask internally for fast boolean mask operation.<br> 079 * 080 * @author Stephane 081 */ 082public class ROI2DArea extends ROI2D 083{ 084 protected static final float DEFAULT_CURSOR_SIZE = 15f; 085 086 // we want to keep a static brush 087 protected static final Ellipse2D brush = new Ellipse2D.Double(); 088 // protected static final Point2D.Double cursorPosition = new Point2D.Double(); 089 protected static Color brushColor = Color.red; 090 protected static float brushSize = DEFAULT_CURSOR_SIZE; 091 092 public class ROI2DAreaPainter extends ROI2DPainter implements Runnable 093 { 094 /** 095 * @deprecated Use {@link #getOpacity()} instead. 096 */ 097 @Deprecated 098 public static final float CONTENT_ALPHA = 0.3f; 099 100 private static final float MIN_CURSOR_SIZE = 0.3f; 101 private static final float MAX_CURSOR_SIZE = 500f; 102 103 // VTK 3D objects 104 protected vtkPolyData outline; 105 protected vtkPolyDataMapper outlineMapper; 106 protected vtkActor outlineActor; 107 protected vtkInformation vtkInfo; 108 protected vtkPolyData polyData; 109 protected vtkPolyDataMapper polyMapper; 110 protected vtkActor surfaceActor; 111 // 3D internal 112 protected boolean needRebuild; 113 protected double scaling[]; 114 protected WeakReference<VtkCanvas> canvas3d; 115 protected int lastBuildPosZ; 116 117 // internal 118 protected final Point2D brushPosition; 119 120 public ROI2DAreaPainter() 121 { 122 super(); 123 124 brushPosition = new Point2D.Double(); 125 126 outline = null; 127 outlineMapper = null; 128 outlineActor = null; 129 vtkInfo = null; 130 polyData = null; 131 polyMapper = null; 132 surfaceActor = null; 133 134 scaling = new double[3]; 135 Arrays.fill(scaling, 1d); 136 137 needRebuild = true; 138 canvas3d = new WeakReference<VtkCanvas>(null); 139 lastBuildPosZ = getZ(); 140 } 141 142 @Override 143 protected void finalize() throws Throwable 144 { 145 super.finalize(); 146 147 // release allocated VTK resources 148 if (surfaceActor != null) 149 surfaceActor.Delete(); 150 if (polyMapper != null) 151 polyMapper.Delete(); 152 if (polyData != null) 153 { 154 polyData.GetPointData().GetScalars().Delete(); 155 polyData.GetPointData().Delete(); 156 polyData.Delete(); 157 } 158 if (outlineActor != null) 159 { 160 outlineActor.SetPropertyKeys(null); 161 outlineActor.Delete(); 162 } 163 if (vtkInfo != null) 164 { 165 vtkInfo.Remove(VtkCanvas.visibilityKey); 166 vtkInfo.Delete(); 167 } 168 if (outlineMapper != null) 169 outlineMapper.Delete(); 170 if (outline != null) 171 { 172 outline.GetPointData().GetScalars().Delete(); 173 outline.GetPointData().Delete(); 174 outline.Delete(); 175 } 176 }; 177 178 protected void initVtkObjects() 179 { 180 outline = VtkUtil.getOutline(0d, 1d, 0d, 1d, 0d, 1d); 181 outlineMapper = new vtkPolyDataMapper(); 182 outlineMapper.SetInputData(outline); 183 outlineActor = new vtkActor(); 184 outlineActor.SetMapper(outlineMapper); 185 // disable picking on the outline 186 outlineActor.SetPickable(0); 187 // and set it to wireframe representation 188 outlineActor.GetProperty().SetRepresentationToWireframe(); 189 // use vtkInformations to store outline visibility state (hacky) 190 vtkInfo = new vtkInformation(); 191 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 192 // VtkCanvas use this to restore correctly outline visibility flag 193 outlineActor.SetPropertyKeys(vtkInfo); 194 195 polyMapper = new vtkPolyDataMapper(); 196 surfaceActor = new vtkActor(); 197 surfaceActor.SetMapper(polyMapper); 198 199 final Color col = getColor(); 200 final double r = col.getRed() / 255d; 201 final double g = col.getGreen() / 255d; 202 final double b = col.getBlue() / 255d; 203 204 // set actors color 205 outlineActor.GetProperty().SetColor(r, g, b); 206 surfaceActor.GetProperty().SetColor(r, g, b); 207 } 208 209 /** 210 * rebuild VTK objects (called only when VTK canvas is selected). 211 */ 212 protected void rebuildVtkObjects() 213 { 214 final VtkCanvas canvas = canvas3d.get(); 215 // canvas was closed 216 if (canvas == null) 217 return; 218 219 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 220 // canvas was closed 221 if (vtkPanel == null) 222 return; 223 224 final Sequence seq = canvas.getSequence(); 225 // nothing to update 226 if (seq == null) 227 return; 228 229 // get previous polydata object 230 final vtkPolyData previousPolyData = polyData; 231 // get VTK binary image from ROI mask 232 final vtkImageData imageData = VtkUtil.getBinaryImageData(ROI2DArea.this, seq.getSizeZ(), 233 canvas.getPositionT()); 234 // adjust spacing 235 imageData.SetSpacing(scaling[0], scaling[1], scaling[2]); 236 // get VTK polygon data representing the surface of the binary image 237 polyData = VtkUtil.getSurfaceFromImage(imageData, 0.5d); 238 239 // get bounds 240 final Rectangle3D bounds = getBounds5D().toRectangle3D(); 241 // apply scaling on bounds 242 bounds.setX(bounds.getX() * scaling[0]); 243 bounds.setSizeX(bounds.getSizeX() * scaling[0]); 244 bounds.setY(bounds.getY() * scaling[1]); 245 bounds.setSizeY(bounds.getSizeY() * scaling[1]); 246 if (bounds.isInfiniteZ()) 247 { 248 bounds.setZ(0); 249 bounds.setSizeZ(seq.getSizeZ() * scaling[2]); 250 lastBuildPosZ = -1; 251 } 252 else 253 { 254 lastBuildPosZ = getZ(); 255 bounds.setZ(bounds.getZ() * scaling[2]); 256 bounds.setSizeZ(1d * scaling[2]); 257 } 258 259 // actor can be accessed in canvas3d for rendering so we need to synchronize access 260 vtkPanel.lock(); 261 try 262 { 263 // update outline data 264 VtkUtil.setOutlineBounds(outline, bounds.getMinX(), bounds.getMaxX(), bounds.getMinY(), 265 bounds.getMaxY(), bounds.getMinZ(), bounds.getMaxZ(), canvas); 266 outlineMapper.Update(); 267 // update polygon data from image 268 polyMapper.SetInputData(polyData); 269 polyMapper.Update(); 270 271 // update actor position 272 surfaceActor.SetPosition(bounds.getX(), bounds.getY(), bounds.getZ()); 273 274 // release image data 275 imageData.GetPointData().GetScalars().Delete(); 276 imageData.GetPointData().Delete(); 277 imageData.Delete(); 278 279 // release previous polydata 280 if (previousPolyData != null) 281 { 282 previousPolyData.GetPointData().GetScalars().Delete(); 283 previousPolyData.GetPointData().Delete(); 284 previousPolyData.Delete(); 285 } 286 } 287 finally 288 { 289 vtkPanel.unlock(); 290 } 291 292 // update color and others properties 293 updateVtkDisplayProperties(); 294 } 295 296 protected void updateVtkDisplayProperties() 297 { 298 if (surfaceActor == null) 299 return; 300 301 final VtkCanvas cnv = canvas3d.get(); 302 final Color col = getDisplayColor(); 303 final double r = col.getRed() / 255d; 304 final double g = col.getGreen() / 255d; 305 final double b = col.getBlue() / 255d; 306 // final double strk = getStroke(); 307 // final float opacity = getOpacity(); 308 309 final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null; 310 311 // we need to lock canvas as actor can be accessed during rendering 312 if (vtkPanel != null) 313 vtkPanel.lock(); 314 try 315 { 316 // set actors color 317 outlineActor.GetProperty().SetColor(r, g, b); 318 if (isSelected()) 319 { 320 outlineActor.GetProperty().SetRepresentationToWireframe(); 321 outlineActor.SetVisibility(1); 322 vtkInfo.Set(VtkCanvas.visibilityKey, 1); 323 } 324 else 325 { 326 outlineActor.GetProperty().SetRepresentationToPoints(); 327 outlineActor.SetVisibility(0); 328 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 329 } 330 surfaceActor.GetProperty().SetColor(r, g, b); 331 // opacity here is about ROI content, global opacity is handled by Layer 332 // surfaceActor.GetProperty().SetOpacity(opacity); 333 setVtkObjectsColor(col); 334 } 335 finally 336 { 337 if (vtkPanel != null) 338 vtkPanel.unlock(); 339 } 340 341 // need to repaint 342 painterChanged(); 343 } 344 345 protected void setVtkObjectsColor(Color color) 346 { 347 if (outline != null) 348 VtkUtil.setPolyDataColor(outline, color, canvas3d.get()); 349 if (polyData != null) 350 VtkUtil.setPolyDataColor(polyData, color, canvas3d.get()); 351 } 352 353 protected void updateVtkObjectsBounds() 354 { 355 final VtkCanvas canvas = canvas3d.get(); 356 // canvas was closed 357 if (canvas == null) 358 return; 359 360 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 361 // canvas was closed 362 if (vtkPanel == null) 363 return; 364 365 final Sequence seq = canvas.getSequence(); 366 // nothing to update 367 if (seq == null) 368 return; 369 370 final Rectangle3D bounds = getBounds5D().toRectangle3D(); 371 // apply scaling on bounds 372 bounds.setX(bounds.getX() * scaling[0]); 373 bounds.setSizeX(bounds.getSizeX() * scaling[0]); 374 bounds.setY(bounds.getY() * scaling[1]); 375 bounds.setSizeY(bounds.getSizeY() * scaling[1]); 376 if (bounds.isInfiniteZ()) 377 { 378 bounds.setZ(0); 379 bounds.setSizeZ(seq.getSizeZ() * scaling[2]); 380 } 381 else 382 { 383 bounds.setZ(bounds.getZ() * scaling[2]); 384 bounds.setSizeZ(1d * scaling[2]); 385 } 386 387 // actor can be accessed in canvas3d for rendering so we need to synchronize access 388 vtkPanel.lock(); 389 try 390 { 391 // update outline position 392 VtkUtil.setOutlineBounds(outline, bounds.getMinX(), bounds.getMaxX(), bounds.getMinY(), 393 bounds.getMaxY(), bounds.getMinZ(), bounds.getMaxZ(), canvas); 394 outlineMapper.Update(); 395 396 // update actor position 397 surfaceActor.SetPosition(bounds.getX(), bounds.getY(), bounds.getZ()); 398 } 399 finally 400 { 401 vtkPanel.unlock(); 402 } 403 } 404 405 void updateCursor() 406 { 407 final double x = brushPosition.getX(); 408 final double y = brushPosition.getY(); 409 410 brush.setFrameFromDiagonal(x - brushSize, y - brushSize, x + brushSize, y + brushSize); 411 412 // if roi selected (cursor displayed) --> painter changed 413 if (isSelected()) 414 painterChanged(); 415 } 416 417 /** 418 * Returns the brush position. 419 */ 420 public Point2D getBrushPosition() 421 { 422 return (Point) brushPosition.clone(); 423 } 424 425 /** 426 * Set the brush position. 427 */ 428 public void setBrushPosition(Point2D position) 429 { 430 if (!brushPosition.equals(position)) 431 { 432 brushPosition.setLocation(position); 433 updateCursor(); 434 } 435 } 436 437 /** 438 * @deprecated Use {@link #getBrushPosition()} instead. 439 */ 440 @Deprecated 441 public Point2D getCursorPosition() 442 { 443 return getBrushPosition(); 444 } 445 446 /** 447 * @deprecated Use {@link #setBrushPosition(Point2D)} instead. 448 */ 449 @Deprecated 450 public void setCursorPosition(Point2D position) 451 { 452 setBrushPosition(position); 453 } 454 455 /** 456 * Returns the brush size. 457 */ 458 public float getBrushSize() 459 { 460 return brushSize; 461 } 462 463 /** 464 * Sets the brush size. 465 */ 466 public void setBrushSize(float value) 467 { 468 final float adjValue = Math.max(Math.min(value, MAX_CURSOR_SIZE), MIN_CURSOR_SIZE); 469 470 if (brushSize != adjValue) 471 { 472 brushSize = adjValue; 473 updateCursor(); 474 } 475 } 476 477 /** 478 * @deprecated Use {@link #getBrushSize()} instead 479 */ 480 @Deprecated 481 public float getCursorSize() 482 { 483 return getBrushSize(); 484 } 485 486 /** 487 * @deprecated Use {@link #setBrushSize(float)} instead 488 */ 489 @Deprecated 490 public void setCursorSize(float value) 491 { 492 setBrushSize(value); 493 } 494 495 /** 496 * Returns the brush color 497 */ 498 public Color getBrushColor() 499 { 500 return brushColor; 501 } 502 503 /** 504 * Sets the brush color 505 */ 506 public void setBrushColor(Color value) 507 { 508 if (!brushColor.equals(value)) 509 { 510 brushColor = value; 511 painterChanged(); 512 } 513 } 514 515 /** 516 * @deprecated Use {@link #getBrushColor()} instead 517 */ 518 @Deprecated 519 public Color getCursorColor() 520 { 521 return getBrushColor(); 522 } 523 524 /** 525 * @deprecated Use {@link #setBrushColor(Color)} instead 526 */ 527 @Deprecated 528 public void setCursorColor(Color value) 529 { 530 setBrushColor(value); 531 } 532 533 public void addToMask(Point2D pos) 534 { 535 setBrushPosition(pos); 536 updateMask(brush, false); 537 } 538 539 public void removeFromMask(Point2D pos) 540 { 541 setBrushPosition(pos); 542 updateMask(brush, true); 543 } 544 545 @Override 546 public void painterChanged() 547 { 548 updateMaskColor(true); 549 550 super.painterChanged(); 551 } 552 553 @Override 554 protected boolean updateFocus(InputEvent e, Point5D imagePoint, IcyCanvas canvas) 555 { 556 // specific VTK canvas processing 557 if (canvas instanceof VtkCanvas) 558 { 559 // mouse is over the ROI actor ? --> focus the ROI 560 final boolean focused = (surfaceActor != null) 561 && (surfaceActor == ((VtkCanvas) canvas).getPickedObject()); 562 563 setFocused(focused); 564 565 return focused; 566 } 567 568 return super.updateFocus(e, imagePoint, canvas); 569 } 570 571 @Override 572 public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 573 { 574 // send event to parent first 575 super.keyPressed(e, imagePoint, canvas); 576 577 // edition not supported on VtkCanvas 578 if (canvas instanceof VtkCanvas) 579 return; 580 581 // not yet consumed and ROI editable... 582 if (!e.isConsumed() && !isReadOnly()) 583 { 584 // then process it here 585 if (isActiveFor(canvas)) 586 { 587 ROI2DArea.this.beginUpdate(); 588 try 589 { 590 switch (e.getKeyChar()) 591 { 592 case '+': 593 if (isSelected()) 594 { 595 setBrushSize(getBrushSize() * 1.1f); 596 e.consume(); 597 } 598 break; 599 600 case '-': 601 if (isSelected()) 602 { 603 setBrushSize(getBrushSize() * 0.9f); 604 e.consume(); 605 } 606 break; 607 } 608 } 609 finally 610 { 611 ROI2DArea.this.endUpdate(); 612 } 613 } 614 } 615 } 616 617 @Override 618 public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 619 { 620 // send event to parent first 621 super.mousePressed(e, imagePoint, canvas); 622 623 // edition not supported on VtkCanvas 624 if (canvas instanceof VtkCanvas) 625 return; 626 // we need it 627 if (imagePoint == null) 628 return; 629 630 // not yet consumed, ROI editable, selected and not focused... 631 if (!e.isConsumed() && !isReadOnly() && isSelected() && !isFocused()) 632 { 633 // then process it here 634 if (isActiveFor(canvas)) 635 { 636 // keep trace of roi changes from user mouse action 637 roiModifiedByMouse = false; 638 // save current ROI 639 undoSave = getBooleanMask(true); 640 641 ROI2DArea.this.beginUpdate(); 642 try 643 { 644 // left button action 645 if (EventUtil.isLeftMouseButton(e)) 646 { 647 // add point first 648 addToMask(imagePoint.toPoint2D()); 649 roiModifiedByMouse = true; 650 e.consume(); 651 } 652 // right button action 653 else if (EventUtil.isRightMouseButton(e)) 654 { 655 // remove point 656 removeFromMask(imagePoint.toPoint2D()); 657 roiModifiedByMouse = true; 658 e.consume(); 659 } 660 } 661 finally 662 { 663 ROI2DArea.this.endUpdate(); 664 } 665 } 666 } 667 } 668 669 @Override 670 public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 671 { 672 // send event to parent first 673 super.mouseReleased(e, imagePoint, canvas); 674 675 // update only on release as it can be long 676 if (!isReadOnly()) 677 { 678 if (roiModifiedByMouse) 679 { 680 if (boundsNeedUpdate) 681 { 682 if (optimizeBounds()) 683 { 684 roiChanged(true); 685 686 // empty ? delete ROI 687 if (bounds.isEmpty()) 688 { 689 ROI2DArea.this.remove(); 690 // nothing more to do 691 return; 692 } 693 } 694 } 695 696 final Sequence sequence = canvas.getSequence(); 697 698 // add undo operation 699 try 700 { 701 if ((sequence != null) && (undoSave != null)) 702 sequence.addUndoableEdit(new Area2DChangeROIEdit(ROI2DArea.this, undoSave)); 703 } 704 catch (OutOfMemoryError err) 705 { 706 // can't create undo operation, show message and clear undo manager 707 System.out.println("Warning: not enough memory to create undo point for ROI area change"); 708 sequence.clearUndoManager(); 709 } 710 711 // release save 712 undoSave = null; 713 roiModifiedByMouse = false; 714 } 715 } 716 } 717 718 @Override 719 public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 720 { 721 // provide backward compatibility 722 if (imagePoint != null) 723 mouseClick(e, imagePoint.toPoint2D(), canvas); 724 else 725 mouseClick(e, (Point2D) null, canvas); 726 727 // not yet consumed... 728 if (!e.isConsumed()) 729 { 730 // and process ROI stuff now 731 if (isActiveFor(canvas)) 732 { 733 final int clickCount = e.getClickCount(); 734 735 // double click 736 if (clickCount == 2) 737 { 738 // focused ? 739 if (isFocused()) 740 { 741 // show in ROI panel 742 final RoisPanel roiPanel = Icy.getMainInterface().getRoisPanel(); 743 744 if (roiPanel != null) 745 { 746 roiPanel.scrollTo(ROI2DArea.this); 747 // consume event 748 e.consume(); 749 } 750 } 751 } 752 } 753 } 754 755 } 756 757 @Override 758 public void mouseMove(MouseEvent e, Double imagePoint, IcyCanvas canvas) 759 { 760 // send event to parent first 761 super.mouseMove(e, imagePoint, canvas); 762 763 // edition not supported on VtkCanvas 764 if (canvas instanceof VtkCanvas) 765 return; 766 // we need it 767 if (imagePoint == null) 768 return; 769 770 // not yet consumed, ROI editable and selected... 771 if (!e.isConsumed() && !isReadOnly() && isSelected()) 772 { 773 // then process it here 774 if (isActiveFor(canvas)) 775 { 776 setBrushPosition(imagePoint.toPoint2D()); 777 } 778 } 779 } 780 781 @Override 782 public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 783 { 784 // send event to parent first 785 super.mouseDrag(e, imagePoint, canvas); 786 787 // edition not supported on VtkCanvas 788 if (canvas instanceof VtkCanvas) 789 return; 790 // we need it 791 if (imagePoint == null) 792 return; 793 794 // not yet consumed, ROI editable and selected... 795 if (!e.isConsumed() && !isReadOnly() && isSelected()) 796 { 797 // then process it here 798 if (isActiveFor(canvas)) 799 { 800 ROI2DArea.this.beginUpdate(); 801 try 802 { 803 // left button action 804 if (EventUtil.isLeftMouseButton(e)) 805 { 806 // add point first 807 addToMask(imagePoint.toPoint2D()); 808 roiModifiedByMouse = true; 809 e.consume(); 810 } 811 // right button action 812 else if (EventUtil.isRightMouseButton(e)) 813 { 814 // remove point 815 removeFromMask(imagePoint.toPoint2D()); 816 roiModifiedByMouse = true; 817 e.consume(); 818 } 819 } 820 finally 821 { 822 ROI2DArea.this.endUpdate(); 823 } 824 } 825 } 826 } 827 828 @Override 829 public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) 830 { 831 super.paint(g, sequence, canvas); 832 833 if (isActiveFor(canvas)) 834 { 835 // ROI selected ? draw cursor 836 if (isSelected() && !isFocused() && !isReadOnly()) 837 drawCursor(g, sequence, canvas); 838 } 839 } 840 841 /** 842 * Draw the ROI itself 843 */ 844 @Override 845 public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas) 846 { 847 if (canvas instanceof IcyCanvas2D) 848 { 849 // not supported 850 if (g == null) 851 return; 852 853 final Rectangle bounds = getBounds(); 854 // trivial paint optimization 855 final boolean shapeVisible = GraphicsUtil.isVisible(g, bounds); 856 857 if (shapeVisible) 858 { 859 final Graphics2D g2 = (Graphics2D) g.create(); 860 final boolean small; 861 862 // disable LOD when creating the ROI 863 if (isCreating()) 864 small = false; 865 else 866 { 867 final double scale = Math.max(Math.abs(canvas.getScaleX()), Math.abs(canvas.getScaleY())); 868 small = Math.max(scale * bounds.getWidth(), scale * bounds.getHeight()) < LOD_SMALL; 869 } 870 871 // simplified draw 872 if (small) 873 { 874 g2.setColor(getDisplayColor()); 875 g2.drawImage(imageMask, null, bounds.x, bounds.y); 876 } 877 // normal draw 878 else 879 { 880 final AlphaComposite prevAlpha = (AlphaComposite) g2.getComposite(); 881 float newAlpha = prevAlpha.getAlpha() * getOpacity(); 882 newAlpha = Math.min(1f, newAlpha); 883 newAlpha = Math.max(0f, newAlpha); 884 885 // show content with an alpha factor 886 g2.setComposite(prevAlpha.derive(newAlpha)); 887 888 // draw mask 889 g2.drawImage(imageMask, null, bounds.x, bounds.y); 890 891 // restore alpha 892 g2.setComposite(prevAlpha); 893 894 // draw border 895 if (isSelected()) 896 { 897 g2.setStroke(new BasicStroke((float) ROI.getAdjustedStroke(canvas, stroke + 1d))); 898 g2.setColor(getDisplayColor()); 899 g2.draw(bounds); 900 } 901 else 902 { 903 // outside border 904 g2.setStroke(new BasicStroke((float) ROI.getAdjustedStroke(canvas, stroke + 1d))); 905 g2.setColor(Color.black); 906 g2.draw(bounds); 907 // internal border 908 g2.setStroke(new BasicStroke((float) ROI.getAdjustedStroke(canvas, stroke))); 909 g2.setColor(getDisplayColor()); 910 g2.draw(bounds); 911 } 912 } 913 914 g2.dispose(); 915 } 916 917 // for (Point2D pt : getBooleanMask().getEdgePoints()) 918 // g2.drawRect((int) pt.getX(), (int) pt.getY(), 1, 1); 919 } 920 921 if (canvas instanceof VtkCanvas) 922 { 923 // 3D canvas 924 final VtkCanvas cnv = (VtkCanvas) canvas; 925 // update reference if needed 926 if (canvas3d.get() != cnv) 927 canvas3d = new WeakReference<VtkCanvas>(cnv); 928 929 // initialize VTK objects if not yet done 930 if (surfaceActor == null) 931 initVtkObjects(); 932 933 // FIXME : need a better implementation 934 final double[] s = cnv.getVolumeScale(); 935 936 // scaling changed ? 937 if (!Arrays.equals(scaling, s)) 938 { 939 // update scaling 940 scaling = s; 941 // need rebuild 942 needRebuild = true; 943 } 944 945 // need to rebuild 3D data structures ? 946 if (needRebuild) 947 { 948 // request rebuild 3D objects 949 ThreadUtil.runSingle(this); 950 needRebuild = false; 951 } 952 } 953 } 954 955 /** 956 * draw the ROI cursor 957 */ 958 protected void drawCursor(Graphics2D g, Sequence sequence, IcyCanvas canvas) 959 { 960 if (canvas instanceof IcyCanvas2D) 961 { 962 // not supported 963 if (g == null) 964 return; 965 966 final Rectangle bounds = brush.getBounds(); 967 // trivial paint optimization 968 final boolean shapeVisible = GraphicsUtil.isVisible(g, bounds); 969 970 if (shapeVisible) 971 { 972 final Graphics2D g2 = (Graphics2D) g.create(); 973 final boolean tiny; 974 975 // disable LOD when creating the ROI 976 if (isCreating()) 977 tiny = false; 978 else 979 { 980 final double scale = Math.max(canvas.getScaleX(), canvas.getScaleY()); 981 tiny = Math.max(scale * bounds.getWidth(), scale * bounds.getHeight()) < LOD_TINY; 982 } 983 984 // simplified draw 985 if (tiny) 986 { 987 // cursor color 988 g2.setColor(brushColor); 989 // draw cursor 990 g2.fill(brush); 991 } 992 // normal draw 993 else 994 { 995 final AlphaComposite prevAlpha = (AlphaComposite) g2.getComposite(); 996 float newAlpha = prevAlpha.getAlpha() * getOpacity() * 2f; 997 newAlpha = Math.min(1f, newAlpha); 998 newAlpha = Math.max(0f, newAlpha); 999 1000 // show cursor with an alpha factor 1001 g2.setComposite(prevAlpha.derive(newAlpha)); 1002 1003 // draw cursor border 1004 g2.setColor(Color.black); 1005 g2.setStroke(new BasicStroke((float) ROI.getAdjustedStroke(canvas, stroke))); 1006 g2.draw(brush); 1007 // draw cursor 1008 g2.setColor(brushColor); 1009 g2.fill(brush); 1010 } 1011 1012 g2.dispose(); 1013 } 1014 } 1015 } 1016 1017 @Override 1018 public vtkProp[] getProps() 1019 { 1020 // initialize VTK objects if not yet done 1021 if (surfaceActor == null) 1022 initVtkObjects(); 1023 1024 return new vtkActor[] {surfaceActor, outlineActor}; 1025 } 1026 1027 @Override 1028 public void run() 1029 { 1030 rebuildVtkObjects(); 1031 } 1032 } 1033 1034 public static final String ID_BOUNDS_X = "boundsX"; 1035 public static final String ID_BOUNDS_Y = "boundsY"; 1036 public static final String ID_BOUNDS_W = "boundsW"; 1037 public static final String ID_BOUNDS_H = "boundsH"; 1038 // protected static final String ID_BOOLMASK_LEN = "boolMaskLen"; 1039 public static final String ID_BOOLMASK_DATA = "boolMaskData"; 1040 1041 /** 1042 * image containing the mask 1043 */ 1044 protected BufferedImage imageMask; 1045 /** 1046 * rectangle bounds 1047 */ 1048 protected Rectangle bounds; 1049 1050 /** 1051 * internals 1052 */ 1053 protected final byte[] red; 1054 protected final byte[] green; 1055 protected final byte[] blue; 1056 protected IndexColorModel colorModel; 1057 protected byte[] maskData; // 0 = false, 1 = true 1058 protected double translateX, translateY; 1059 protected Color previousColor; 1060 protected boolean boundsNeedUpdate; 1061 protected boolean roiModifiedByMouse; 1062 protected BooleanMask2D undoSave; 1063 1064 /** 1065 * Create a ROI2D Area type from the specified {@link BooleanMask2D}. 1066 */ 1067 public ROI2DArea() 1068 { 1069 super(); 1070 1071 bounds = new Rectangle(); 1072 boundsNeedUpdate = false; 1073 roiModifiedByMouse = false; 1074 undoSave = null; 1075 translateX = 0d; 1076 translateY = 0d; 1077 1078 // prepare indexed image 1079 red = new byte[256]; 1080 green = new byte[256]; 1081 blue = new byte[256]; 1082 1083 // keep trace of previous color 1084 previousColor = getDisplayColor(); 1085 1086 // set colormap 1087 red[1] = (byte) previousColor.getRed(); 1088 green[1] = (byte) previousColor.getGreen(); 1089 blue[1] = (byte) previousColor.getBlue(); 1090 1091 // classic 8 bits indexed with one transparent color (index = 0) 1092 colorModel = new IndexColorModel(8, 256, red, green, blue, 0); 1093 // create default image 1094 imageMask = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, colorModel); 1095 // get data pointer 1096 maskData = ((DataBufferByte) imageMask.getRaster().getDataBuffer()).getData(); 1097 1098 // set icon (default name is defined by getDefaultName()) 1099 setIcon(ResourceUtil.ICON_ROI_AREA); 1100 } 1101 1102 /** 1103 * @deprecated Use {@link #ROI2DArea(Point5D)} instead 1104 */ 1105 @Deprecated 1106 public ROI2DArea(Point2D position, boolean cm) 1107 { 1108 this(position); 1109 } 1110 1111 /** 1112 * Create a ROI2D Area type with a single point. 1113 */ 1114 public ROI2DArea(Point2D position) 1115 { 1116 this(); 1117 1118 // add current point to mask 1119 addBrush(position); 1120 } 1121 1122 /** 1123 * Generic constructor for interactive mode. 1124 */ 1125 public ROI2DArea(Point5D position) 1126 { 1127 this(position.toPoint2D()); 1128 } 1129 1130 /** 1131 * Create a ROI2D Area type from the specified {@link BooleanMask2D}. 1132 */ 1133 public ROI2DArea(BooleanMask2D mask) 1134 { 1135 this(); 1136 1137 setAsBooleanMask(mask); 1138 } 1139 1140 /** 1141 * Create a copy of the specified 2D Area ROI 1142 */ 1143 public ROI2DArea(ROI2DArea area) 1144 { 1145 super(); 1146 1147 bounds = new Rectangle(); 1148 boundsNeedUpdate = false; 1149 roiModifiedByMouse = false; 1150 undoSave = null; 1151 translateX = 0d; 1152 translateY = 0d; 1153 1154 // prepare indexed image 1155 red = new byte[256]; 1156 green = new byte[256]; 1157 blue = new byte[256]; 1158 1159 // keep trace of previous color 1160 previousColor = getDisplayColor(); 1161 1162 // set colormap 1163 red[1] = (byte) previousColor.getRed(); 1164 green[1] = (byte) previousColor.getGreen(); 1165 blue[1] = (byte) previousColor.getBlue(); 1166 1167 // classic 8 bits indexed with one transparent color (index = 0) 1168 colorModel = new IndexColorModel(8, 256, red, green, blue, 0); 1169 imageMask = new BufferedImage(area.bounds.width, area.bounds.height, BufferedImage.TYPE_BYTE_INDEXED, 1170 colorModel); 1171 maskData = ((DataBufferByte) imageMask.getRaster().getDataBuffer()).getData(); 1172 1173 System.arraycopy(area.maskData, 0, maskData, 0, maskData.length); 1174 1175 bounds.setBounds(area.bounds); 1176 1177 // set icon (default name is defined by getDefaultName()) 1178 setIcon(ResourceUtil.ICON_ROI_AREA); 1179 } 1180 1181 @Override 1182 public String getDefaultName() 1183 { 1184 return "Area2D"; 1185 } 1186 1187 void addToBounds(Rectangle bnd) 1188 { 1189 final Rectangle newBounds; 1190 1191 if (bounds.isEmpty()) 1192 newBounds = new Rectangle(bnd); 1193 else 1194 { 1195 newBounds = new Rectangle(bounds); 1196 newBounds.add(bnd); 1197 } 1198 1199 try 1200 { 1201 // update image to the new bounds 1202 updateImage(newBounds); 1203 } 1204 catch (Error E) 1205 { 1206 // perhaps a "out of memory" error, restore back old bounds 1207 System.err.println("can't enlarge ROI, no enough memory !"); 1208 } 1209 } 1210 1211 /** 1212 * @deprecated Use {@link #optimizeBounds()} instead. 1213 */ 1214 @Deprecated 1215 public void optimizeBounds(boolean removeIfEmpty) 1216 { 1217 optimizeBounds(); 1218 if (removeIfEmpty && bounds.isEmpty()) 1219 remove(); 1220 } 1221 1222 /** 1223 * Returns true if the ROI is empty (the mask does not contains any point). 1224 */ 1225 @Override 1226 public boolean isEmpty() 1227 { 1228 if (bounds.isEmpty()) 1229 return true; 1230 1231 final byte[] data = maskData; 1232 1233 for (byte b : data) 1234 if (b != 0) 1235 return false; 1236 1237 return true; 1238 } 1239 1240 /** 1241 * Optimize the bounds size to the minimum surface which still include all mask<br> 1242 * You should call it after consecutive remove operations. 1243 */ 1244 public boolean optimizeBounds() 1245 { 1246 // bounds are being updated 1247 boundsNeedUpdate = false; 1248 1249 final byte[] data; 1250 final Rectangle bnds; 1251 1252 // recompute bound from the mask data 1253 synchronized (this) 1254 { 1255 data = maskData; 1256 bnds = bounds; 1257 } 1258 1259 final int sizeX = bnds.width; 1260 final int sizeY = bnds.height; 1261 1262 int minX, minY, maxX, maxY; 1263 minX = maxX = minY = maxY = 0; 1264 boolean empty = true; 1265 int offset = 0; 1266 1267 for (int y = 0; y < sizeY; y++) 1268 { 1269 for (int x = 0; x < sizeX; x++) 1270 { 1271 if (data[offset++] != 0) 1272 { 1273 if (empty) 1274 { 1275 minX = maxX = x; 1276 minY = maxY = y; 1277 empty = false; 1278 } 1279 else 1280 { 1281 if (x < minX) 1282 minX = x; 1283 else if (x > maxX) 1284 maxX = x; 1285 if (y < minY) 1286 minY = y; 1287 else if (y > maxY) 1288 maxY = y; 1289 } 1290 } 1291 } 1292 } 1293 1294 if (!empty) 1295 // update image to the new bounds 1296 return updateImage(new Rectangle(bnds.x + minX, bnds.y + minY, (maxX - minX) + 1, (maxY - minY) + 1)); 1297 1298 // update to empty bounds 1299 return updateImage(new Rectangle(bnds.x, bnds.y, 0, 0)); 1300 } 1301 1302 /** 1303 * @deprecated Use {@link #getDisplayColor()} instead. 1304 */ 1305 @Deprecated 1306 public Color getMaskColor() 1307 { 1308 return getOverlay().getDisplayColor(); 1309 } 1310 1311 void updateMaskColor(boolean rebuildImage) 1312 { 1313 final Color color = getOverlay().getDisplayColor(); 1314 1315 // roi color changed ? 1316 if (!previousColor.equals(color)) 1317 { 1318 // update colormap 1319 red[1] = (byte) color.getRed(); 1320 green[1] = (byte) color.getGreen(); 1321 blue[1] = (byte) color.getBlue(); 1322 1323 colorModel = new IndexColorModel(8, 256, red, green, blue, 0); 1324 1325 // recreate image (so the new colormodel takes effect) 1326 if (rebuildImage) 1327 imageMask = ImageUtil.createIndexedImage(imageMask.getWidth(), imageMask.getHeight(), colorModel, 1328 maskData); 1329 1330 // set to new color 1331 previousColor = color; 1332 } 1333 } 1334 1335 /** 1336 * Returns the internal image mask. 1337 */ 1338 public BufferedImage getImageMask() 1339 { 1340 return imageMask; 1341 } 1342 1343 boolean updateImage(Rectangle newBnd) 1344 { 1345 final byte[] data; 1346 final Rectangle bnds; 1347 1348 synchronized (this) 1349 { 1350 data = maskData; 1351 bnds = bounds; 1352 } 1353 1354 // copy rectangle 1355 final Rectangle oldBounds = new Rectangle(bnds); 1356 final Rectangle newBounds = new Rectangle(newBnd); 1357 1358 // replace to oldBounds origin 1359 oldBounds.translate(-bnds.x, -bnds.y); 1360 newBounds.translate(-bnds.x, -bnds.y); 1361 1362 // dimension changed ? 1363 if ((oldBounds.width != newBounds.width) || (oldBounds.height != newBounds.height)) 1364 { 1365 final BufferedImage newImageMask; 1366 final byte[] newMaskData; 1367 1368 if (!newBounds.isEmpty()) 1369 { 1370 // new bounds not empty 1371 newImageMask = new BufferedImage(newBounds.width, newBounds.height, BufferedImage.TYPE_BYTE_INDEXED, 1372 colorModel); 1373 newMaskData = ((DataBufferByte) newImageMask.getRaster().getDataBuffer()).getData(); 1374 1375 final Rectangle intersect = newBounds.intersection(oldBounds); 1376 1377 if (!intersect.isEmpty()) 1378 { 1379 int offSrc = 0; 1380 int offDst = 0; 1381 1382 // adjust offset in source mask 1383 if (intersect.x > 0) 1384 offSrc += intersect.x; 1385 if (intersect.y > 0) 1386 offSrc += intersect.y * oldBounds.width; 1387 // adjust offset in destination mask 1388 if (newBounds.x < 0) 1389 offDst += -newBounds.x; 1390 if (newBounds.y < 0) 1391 offDst += -newBounds.y * newBounds.width; 1392 1393 // preserve data 1394 for (int j = 0; j < intersect.height; j++) 1395 { 1396 System.arraycopy(data, offSrc, newMaskData, offDst, intersect.width); 1397 1398 offSrc += oldBounds.width; 1399 offDst += newBounds.width; 1400 } 1401 } 1402 } 1403 else 1404 { 1405 // new bounds empty --> use single pixel image to avoid NPE 1406 newImageMask = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, colorModel); 1407 newMaskData = ((DataBufferByte) newImageMask.getRaster().getDataBuffer()).getData(); 1408 } 1409 1410 synchronized (this) 1411 { 1412 // set new image and maskData 1413 imageMask = newImageMask; 1414 maskData = newMaskData; 1415 bounds = newBnd; 1416 } 1417 1418 return true; 1419 } 1420 1421 return false; 1422 } 1423 1424 /** 1425 * Set the value of the specified point.<br> 1426 * Don't forget to call optimizeBounds() after consecutive remove operation to refresh the mask 1427 * bounds. 1428 */ 1429 public void setPoint(int x, int y, boolean value) 1430 { 1431 final byte[] data; 1432 final Rectangle bnds; 1433 1434 if (value) 1435 { 1436 // set point in mask 1437 addToBounds(new Rectangle(x, y, 1, 1)); 1438 1439 synchronized (this) 1440 { 1441 data = maskData; 1442 bnds = bounds; 1443 } 1444 1445 // set color depending remove or adding to mask 1446 data[(x - bnds.x) + ((y - bnds.y) * bnds.width)] = 1; 1447 // notify roi changed 1448 roiChanged(true); 1449 } 1450 else 1451 { 1452 synchronized (this) 1453 { 1454 data = maskData; 1455 bnds = bounds; 1456 } 1457 1458 if (bnds.contains(x, y)) 1459 { 1460 // remove point from mask 1461 data[(x - bnds.x) + ((y - bnds.y) * bnds.width)] = 0; 1462 // mark that bounds need to be updated 1463 boundsNeedUpdate = true; 1464 // notify roi changed 1465 roiChanged(true); 1466 } 1467 } 1468 } 1469 1470 /** 1471 * @deprecated Use {@link #setPoint(int, int, boolean)} instead. 1472 */ 1473 @Deprecated 1474 public void updateMask(int x, int y, boolean remove) 1475 { 1476 setPoint(x, y, !remove); 1477 } 1478 1479 /** 1480 * Add the specified {@link ROI2DArea} content to this ROI2DArea 1481 */ 1482 public void add(ROI2DArea roi) 1483 { 1484 final Rectangle boundsToAdd = roi.getBounds(); 1485 final byte[] maskToAdd = roi.maskData; 1486 1487 // update bounds (this update the image dimension if needed) 1488 addToBounds(boundsToAdd); 1489 1490 int offDst, offSrc; 1491 final byte[] data; 1492 final Rectangle bnds; 1493 1494 synchronized (this) 1495 { 1496 data = maskData; 1497 bnds = bounds; 1498 } 1499 1500 // calculate offset 1501 offDst = ((boundsToAdd.y - bnds.y) * bnds.width) + (boundsToAdd.x - bnds.x); 1502 offSrc = 0; 1503 1504 for (int y = 0; y < boundsToAdd.height; y++) 1505 { 1506 for (int x = 0; x < boundsToAdd.width; x++) 1507 if (maskToAdd[offSrc++] != 0) 1508 data[offDst + x] = 1; 1509 1510 offDst += bnds.width; 1511 } 1512 1513 // notify roi changed 1514 roiChanged(true); 1515 } 1516 1517 /** 1518 * Add the specified {@link BooleanMask2D} content to this ROI2DArea 1519 */ 1520 public void add(BooleanMask2D mask) 1521 { 1522 final Rectangle boundsToAdd = mask.bounds; 1523 final boolean[] maskToAdd = mask.mask; 1524 1525 // update bounds (this update the image dimension if needed) 1526 addToBounds(boundsToAdd); 1527 1528 int offDst, offSrc; 1529 final byte[] data; 1530 final Rectangle bnds; 1531 1532 synchronized (this) 1533 { 1534 data = maskData; 1535 bnds = bounds; 1536 } 1537 1538 // calculate offset 1539 offDst = ((boundsToAdd.y - bnds.y) * bnds.width) + (boundsToAdd.x - bnds.x); 1540 offSrc = 0; 1541 1542 for (int y = 0; y < boundsToAdd.height; y++) 1543 { 1544 for (int x = 0; x < boundsToAdd.width; x++) 1545 if (maskToAdd[offSrc++]) 1546 data[offDst + x] = 1; 1547 1548 offDst += bnds.width; 1549 } 1550 1551 // notify roi changed 1552 roiChanged(true); 1553 } 1554 1555 /** 1556 * Exclusively add the specified {@link ROI2DArea} content to this ROI2DArea: 1557 * 1558 * <pre> 1559 * mask1 xor mask2 = result 1560 * 1561 * ################ ################ 1562 * ############## ############## ## ## 1563 * ############ ############ #### #### 1564 * ########## ########## ###### ###### 1565 * ######## ######## ################ 1566 * ###### ###### ###### ###### 1567 * #### #### #### #### 1568 * ## ## ## ## 1569 * </pre> 1570 */ 1571 public void exclusiveAdd(ROI2DArea roi) 1572 { 1573 final Rectangle boundsToXAdd = roi.getBounds(); 1574 final byte[] maskToXAdd = roi.maskData; 1575 1576 // update bounds (this update the image dimension if needed) 1577 addToBounds(boundsToXAdd); 1578 1579 int offDst, offSrc; 1580 final byte[] data; 1581 final Rectangle bnds; 1582 1583 synchronized (this) 1584 { 1585 data = maskData; 1586 bnds = bounds; 1587 } 1588 1589 // calculate offset 1590 offDst = ((boundsToXAdd.y - bnds.y) * bnds.width) + (boundsToXAdd.x - bnds.x); 1591 offSrc = 0; 1592 1593 for (int y = 0; y < boundsToXAdd.height; y++) 1594 { 1595 for (int x = 0; x < boundsToXAdd.width; x++) 1596 if (maskToXAdd[offSrc++] != 0) 1597 data[offDst + x] ^= 1; 1598 1599 offDst += bnds.width; 1600 } 1601 1602 // optimize bounds 1603 if (isUpdating()) 1604 boundsNeedUpdate = true; 1605 else 1606 optimizeBounds(); 1607 1608 // notify roi changed 1609 roiChanged(true); 1610 } 1611 1612 /** 1613 * Exclusively add the specified {@link BooleanMask2D} content to this ROI2DArea: 1614 * 1615 * <pre> 1616 * mask1 xor mask2 = result 1617 * 1618 * ################ ################ 1619 * ############## ############## ## ## 1620 * ############ ############ #### #### 1621 * ########## ########## ###### ###### 1622 * ######## ######## ################ 1623 * ###### ###### ###### ###### 1624 * #### #### #### #### 1625 * ## ## ## ## 1626 * </pre> 1627 */ 1628 public void exclusiveAdd(BooleanMask2D mask) 1629 { 1630 final Rectangle boundsToXAdd = mask.bounds; 1631 final boolean[] maskToXAdd = mask.mask; 1632 1633 // update bounds (this update the image dimension if needed) 1634 addToBounds(boundsToXAdd); 1635 1636 int offDst, offSrc; 1637 final byte[] data; 1638 final Rectangle bnds; 1639 1640 synchronized (this) 1641 { 1642 data = maskData; 1643 bnds = bounds; 1644 } 1645 1646 // calculate offset 1647 offDst = ((boundsToXAdd.y - bnds.y) * bnds.width) + (boundsToXAdd.x - bnds.x); 1648 offSrc = 0; 1649 1650 for (int y = 0; y < boundsToXAdd.height; y++) 1651 { 1652 for (int x = 0; x < boundsToXAdd.width; x++) 1653 if (maskToXAdd[offSrc++]) 1654 data[offDst + x] ^= 1; 1655 1656 offDst += bnds.width; 1657 } 1658 1659 // optimize bounds 1660 if (isUpdating()) 1661 boundsNeedUpdate = true; 1662 else 1663 optimizeBounds(); 1664 1665 // notify roi changed 1666 roiChanged(true); 1667 } 1668 1669 /** 1670 * Subtract the specified {@link ROI2DArea} from this ROI2DArea 1671 */ 1672 public void subtract(ROI2DArea roi) 1673 { 1674 final Rectangle boundsToRemove = roi.getBounds(); 1675 final byte[] maskToRemove = roi.maskData; 1676 final byte[] data; 1677 final Rectangle bnds; 1678 1679 synchronized (this) 1680 { 1681 data = maskData; 1682 bnds = bounds; 1683 } 1684 1685 // compute intersection 1686 final Rectangle intersection = bnds.intersection(boundsToRemove); 1687 1688 // nothing to remove so nothing to do... 1689 if (intersection.isEmpty()) 1690 return; 1691 1692 // calculate offset 1693 int offDst = ((intersection.y - bnds.y) * bnds.width) + (intersection.x - bnds.x); 1694 int offSrc = ((intersection.y - boundsToRemove.y) * boundsToRemove.width) + (intersection.x - boundsToRemove.x); 1695 1696 for (int y = 0; y < intersection.height; y++) 1697 { 1698 for (int x = 0; x < intersection.width; x++) 1699 if (maskToRemove[offSrc + x] != 0) 1700 data[offDst + x] = 0; 1701 1702 offDst += bnds.width; 1703 offSrc += boundsToRemove.width; 1704 } 1705 1706 // optimize bounds 1707 if (isUpdating()) 1708 boundsNeedUpdate = true; 1709 else 1710 optimizeBounds(); 1711 1712 // notify roi changed 1713 roiChanged(true); 1714 } 1715 1716 /** 1717 * Subtract the specified {@link BooleanMask2D} from this ROI2DArea 1718 */ 1719 public void subtract(BooleanMask2D mask) 1720 { 1721 final Rectangle boundsToRemove = mask.bounds; 1722 final boolean[] maskToRemove = mask.mask; 1723 final byte[] data; 1724 final Rectangle bnds; 1725 1726 synchronized (this) 1727 { 1728 data = maskData; 1729 bnds = bounds; 1730 } 1731 1732 // compute intersection 1733 final Rectangle intersection = bnds.intersection(boundsToRemove); 1734 1735 // nothing to remove so nothing to do... 1736 if (intersection.isEmpty()) 1737 return; 1738 1739 // calculate offset 1740 int offDst = ((intersection.y - bnds.y) * bnds.width) + (intersection.x - bnds.x); 1741 int offSrc = ((intersection.y - boundsToRemove.y) * boundsToRemove.width) + (intersection.x - boundsToRemove.x); 1742 1743 for (int y = 0; y < intersection.height; y++) 1744 { 1745 for (int x = 0; x < intersection.width; x++) 1746 if (maskToRemove[offSrc + x]) 1747 data[offDst + x] = 0; 1748 1749 offDst += bnds.width; 1750 offSrc += boundsToRemove.width; 1751 } 1752 1753 // optimize bounds 1754 if (isUpdating()) 1755 boundsNeedUpdate = true; 1756 else 1757 optimizeBounds(); 1758 1759 // notify roi changed 1760 roiChanged(true); 1761 } 1762 1763 /** 1764 * @deprecated Use {@link #subtract(ROI2DArea)} instead 1765 */ 1766 @Deprecated 1767 public void remove(ROI2DArea roi) 1768 { 1769 subtract(roi); 1770 } 1771 1772 /** 1773 * @deprecated Use {@link #subtract(BooleanMask2D)} instead 1774 */ 1775 @Deprecated 1776 public void remove(BooleanMask2D mask) 1777 { 1778 subtract(mask); 1779 } 1780 1781 /** 1782 * Update mask by adding/removing the specified shape to/from it. 1783 * 1784 * @param shape 1785 * the shape to add in or remove from the mask 1786 * @param remove 1787 * if set to <code>true</code> the shape will be removed from the mask 1788 * @param inclusive 1789 * if we should also consider the edge of the shape to update the mask 1790 * @param accurate 1791 * if set to <code>true</code> the operation will be done to be as pixel accurate as 1792 * possible 1793 * @param immediateUpdate 1794 * if set to <code>true</code> the bounds of the mask will be immediately recomputed 1795 * (only meaningful for a 1796 * remove operation) 1797 */ 1798 public void updateMask(Shape shape, boolean remove, boolean inclusive, boolean accurate, boolean immediateUpdate) 1799 { 1800 if (remove) 1801 { 1802 // outside bounds ? --> nothing to remove so nothing to do... 1803 if (!bounds.intersects(shape.getBounds2D())) 1804 return; 1805 1806 // mark that bounds need to be updated 1807 if (isUpdating() || !immediateUpdate) 1808 boundsNeedUpdate = true; 1809 } 1810 else 1811 // update bounds (this update the image dimension if needed) 1812 addToBounds(shape.getBounds()); 1813 1814 // get image graphics object 1815 final Graphics2D g = imageMask.createGraphics(); 1816 1817 // we don't need anti aliasing here 1818 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); 1819 // force accurate stroke rendering 1820 if (accurate) 1821 g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); 1822 1823 g.setComposite(AlphaComposite.Src); 1824 // set color depending remove or adding to mask 1825 if (remove) 1826 g.setColor(new Color(colorModel.getRGB(0), true)); 1827 else 1828 g.setColor(new Color(colorModel.getRGB(1), true)); 1829 // translate to origin of image and pixel center 1830 g.translate(-(bounds.x + 0.5d), -(bounds.y + 0.5d)); 1831 // draw shape into the mask 1832 g.fill(ShapeUtil.getClosedPath(shape)); 1833 // we want edge as well 1834 if (inclusive) 1835 g.draw(shape); 1836 1837 g.dispose(); 1838 1839 // need to optimize bounds 1840 if (remove && !isUpdating() && immediateUpdate) 1841 optimizeBounds(); 1842 1843 // notify roi changed 1844 roiChanged(true); 1845 } 1846 1847 /** 1848 * Update mask from specified shape 1849 */ 1850 public void updateMask(Shape shape, boolean remove) 1851 { 1852 updateMask(shape, remove, true, false, false); 1853 } 1854 1855 @Deprecated 1856 @Override 1857 public ROI2DAreaPainter getPainter() 1858 { 1859 return getOverlay(); 1860 } 1861 1862 @Override 1863 public ROI2DAreaPainter getOverlay() 1864 { 1865 return (ROI2DAreaPainter) super.painter; 1866 } 1867 1868 @Override 1869 protected ROI2DAreaPainter createPainter() 1870 { 1871 return new ROI2DAreaPainter(); 1872 } 1873 1874 @Override 1875 public boolean hasSelectedPoint() 1876 { 1877 return false; 1878 } 1879 1880 /** 1881 * @deprecated useless method. 1882 */ 1883 @Deprecated 1884 public boolean canAddPoint() 1885 { 1886 return true; 1887 } 1888 1889 /** 1890 * @deprecated useless method. 1891 */ 1892 @Deprecated 1893 public boolean canRemovePoint() 1894 { 1895 return true; 1896 } 1897 1898 /** 1899 * @deprecated Use {@link #addBrush(Point2D)} instead. 1900 */ 1901 @Deprecated 1902 public boolean addPointAt(Point2D pos, boolean ctrl) 1903 { 1904 addBrush(pos); 1905 return true; 1906 } 1907 1908 /** 1909 * @deprecated Use {@link #removeBrush(Point2D)} instead. 1910 */ 1911 @Deprecated 1912 public boolean removePointAt(IcyCanvas canvas, Point2D pos) 1913 { 1914 removeBrush(pos); 1915 return true; 1916 } 1917 1918 /** 1919 * @deprecated Useless method. 1920 */ 1921 @Deprecated 1922 protected boolean removeSelectedPoint(IcyCanvas canvas, Point2D imagePoint) 1923 { 1924 // no selected point for this ROI 1925 return false; 1926 } 1927 1928 /** 1929 * Add brush point at specified position. 1930 */ 1931 public void addBrush(Point2D pos) 1932 { 1933 getOverlay().addToMask(pos); 1934 } 1935 1936 /** 1937 * Remove brush point from the mask at specified position.<br> 1938 * Don't forget to call optimizeBounds() after consecutive remove operation 1939 * to refresh the mask bounds. 1940 */ 1941 public void removeBrush(Point2D pos) 1942 { 1943 getOverlay().removeFromMask(pos); 1944 } 1945 1946 /** 1947 * Add a point to the mask 1948 */ 1949 public void addPoint(Point pos) 1950 { 1951 addPoint(pos.x, pos.y); 1952 } 1953 1954 /** 1955 * Add a point to the mask 1956 */ 1957 public void addPoint(int x, int y) 1958 { 1959 setPoint(x, y, true); 1960 } 1961 1962 /** 1963 * Remove a point from the mask.<br> 1964 * Don't forget to call optimizeBounds() after consecutive remove operation 1965 * to refresh the mask bounds. 1966 */ 1967 public void removePoint(Point pos) 1968 { 1969 removePoint(pos.x, pos.y); 1970 } 1971 1972 /** 1973 * Remove a point to the mask.<br> 1974 * Don't forget to call optimizeBounds() after consecutive remove operation 1975 * to refresh the mask bounds. 1976 */ 1977 public void removePoint(int x, int y) 1978 { 1979 setPoint(x, y, false); 1980 } 1981 1982 /** 1983 * Add a rectangle to the mask 1984 */ 1985 public void addRect(Rectangle r) 1986 { 1987 updateMask(r, false, false, true, true); 1988 } 1989 1990 /** 1991 * Add a rectangle to the mask 1992 */ 1993 public void addRect(int x, int y, int w, int h) 1994 { 1995 addRect(new Rectangle(x, y, w, h)); 1996 } 1997 1998 /** 1999 * Remove a rectangle from the mask.<br> 2000 * Don't forget to call optimizeBounds() after consecutive remove operation<br> 2001 * to refresh the mask bounds. 2002 */ 2003 public void removeRect(Rectangle r) 2004 { 2005 updateMask(r, true, false, true, true); 2006 } 2007 2008 /** 2009 * Remove a rectangle from the mask.<br> 2010 * Don't forget to call optimizeBounds() after consecutive remove operation<br> 2011 * to refresh the mask bounds. 2012 */ 2013 public void removeRect(int x, int y, int w, int h) 2014 { 2015 removeRect(new Rectangle(x, y, w, h)); 2016 } 2017 2018 /** 2019 * Add a shape to the mask 2020 */ 2021 public void addShape(Shape s) 2022 { 2023 updateMask(s, false, false, true, true); 2024 } 2025 2026 /** 2027 * Remove a shape to the mask.<br> 2028 * Don't forget to call optimizeBounds() after consecutive remove operation<br> 2029 * to refresh the mask bounds. 2030 */ 2031 public void removeShape(Shape s) 2032 { 2033 updateMask(s, true, false, true, true); 2034 } 2035 2036 @Override 2037 public ROI add(ROI roi, boolean allowCreate) throws UnsupportedOperationException 2038 { 2039 if (roi instanceof ROI2D) 2040 { 2041 final ROI2D roi2d = (ROI2D) roi; 2042 2043 // only if on same position 2044 if ((getZ() == roi2d.getZ()) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) 2045 { 2046 if (roi2d instanceof ROI2DArea) 2047 add((ROI2DArea) roi2d); 2048 else if (roi2d instanceof ROI2DShape) 2049 updateMask(((ROI2DShape) roi2d).getShape(), false, true, true, true); 2050 else 2051 add(roi2d.getBooleanMask(true)); 2052 2053 return this; 2054 } 2055 } 2056 2057 return super.add(roi, allowCreate); 2058 } 2059 2060 @Override 2061 public ROI intersect(ROI roi, boolean allowCreate) throws UnsupportedOperationException 2062 { 2063 if (roi instanceof ROI2D) 2064 { 2065 final ROI2D roi2d = (ROI2D) roi; 2066 2067 // only if on same position 2068 if ((getZ() == roi2d.getZ()) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) 2069 { 2070 final Rectangle intersection = getBounds().intersection(roi2d.getBounds()); 2071 final BooleanMask2D mask = new BooleanMask2D(intersection, getBooleanMask(intersection, true)); 2072 final BooleanMask2D roiMask = new BooleanMask2D(intersection, roi2d.getBooleanMask(intersection, true)); 2073 2074 setAsBooleanMask(BooleanMask2D.getIntersection(mask, roiMask)); 2075 2076 return this; 2077 } 2078 } 2079 2080 return super.intersect(roi, allowCreate); 2081 } 2082 2083 @Override 2084 public ROI exclusiveAdd(ROI roi, boolean allowCreate) throws UnsupportedOperationException 2085 { 2086 if (roi instanceof ROI2D) 2087 { 2088 final ROI2D roi2d = (ROI2D) roi; 2089 2090 // only if on same position 2091 if ((getZ() == roi2d.getZ()) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) 2092 { 2093 if (roi2d instanceof ROI2DArea) 2094 exclusiveAdd((ROI2DArea) roi2d); 2095 else 2096 exclusiveAdd(roi2d.getBooleanMask(true)); 2097 2098 return this; 2099 } 2100 } 2101 2102 return super.exclusiveAdd(roi, allowCreate); 2103 } 2104 2105 @Override 2106 public ROI subtract(ROI roi, boolean allowCreate) throws UnsupportedOperationException 2107 { 2108 if (roi instanceof ROI2D) 2109 { 2110 final ROI2D roi2d = (ROI2D) roi; 2111 2112 // only if on same position 2113 if ((getZ() == roi2d.getZ()) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) 2114 { 2115 if (roi2d instanceof ROI2DArea) 2116 subtract((ROI2DArea) roi2d); 2117 else if (roi2d instanceof ROI2DShape) 2118 updateMask(((ROI2DShape) roi2d).getShape(), true, true, true, true); 2119 else 2120 subtract(roi2d.getBooleanMask(true)); 2121 2122 return this; 2123 } 2124 } 2125 2126 return super.subtract(roi, allowCreate); 2127 } 2128 2129 /** 2130 * Return true if bounds need to be updated by calling optimizeBounds() method. 2131 */ 2132 public boolean getBoundsNeedUpdate() 2133 { 2134 return boundsNeedUpdate; 2135 } 2136 2137 /** 2138 * Clear the mask 2139 */ 2140 public void clear() 2141 { 2142 // reset image with new rectangle 2143 updateImage(new Rectangle()); 2144 } 2145 2146 @Override 2147 public boolean isOverEdge(IcyCanvas canvas, double x, double y) 2148 { 2149 // use bigger stroke for isOverEdge test for easier intersection 2150 final double strk = getAdjustedStroke(canvas) * 3; 2151 final Rectangle2D rect = new Rectangle2D.Double(x - (strk * 0.5), y - (strk * 0.5), strk, strk); 2152 2153 // fast intersect test to start with 2154 if (getBounds2D().intersects(rect)) 2155 // use flatten path, intersects on curved shape return incorrect result 2156 return ShapeUtil.pathIntersects(bounds.getPathIterator(null, 0.1), rect); 2157 2158 return false; 2159 } 2160 2161 @Override 2162 public boolean contains(double x, double y) 2163 { 2164 final byte[] data; 2165 final Rectangle bnds; 2166 2167 synchronized (this) 2168 { 2169 data = maskData; 2170 bnds = bounds; 2171 } 2172 2173 // fast discard 2174 if (!bnds.contains(x, y)) 2175 return false; 2176 2177 // replace to origin 2178 final int xi = (int) x - bnds.x; 2179 final int yi = (int) y - bnds.y; 2180 2181 return (data[(yi * bnds.width) + xi] != 0); 2182 } 2183 2184 @Override 2185 public boolean contains(double x, double y, double w, double h) 2186 { 2187 final byte[] data; 2188 final Rectangle bnds; 2189 2190 synchronized (this) 2191 { 2192 data = maskData; 2193 bnds = bounds; 2194 } 2195 2196 // fast discard 2197 if (!bnds.contains(x, y, w, h)) 2198 return false; 2199 2200 // replace to origin 2201 final int xi = (int) x - bnds.x; 2202 final int yi = (int) y - bnds.y; 2203 final int wi = (int) (x + w) - (int) x; 2204 final int hi = (int) (y + h) - (int) y; 2205 2206 // scan all pixels, can take sometime if mask is large 2207 int offset = (yi * bnds.width) + xi; 2208 for (int j = 0; j < hi; j++) 2209 { 2210 for (int i = 0; i < wi; i++) 2211 if (data[offset++] == 0) 2212 return false; 2213 2214 offset += bnds.width - wi; 2215 } 2216 2217 return true; 2218 } 2219 2220 /* 2221 * already calculated 2222 */ 2223 @Override 2224 public Rectangle2D computeBounds2D() 2225 { 2226 return bounds; 2227 } 2228 2229 /* 2230 * We can override directly this method as we use our own bounds calculation method here 2231 */ 2232 @Override 2233 public Rectangle2D getBounds2D() 2234 { 2235 return bounds; 2236 } 2237 2238 @Override 2239 public boolean intersects(double x, double y, double w, double h) 2240 { 2241 final byte[] data; 2242 final Rectangle bnds; 2243 2244 synchronized (this) 2245 { 2246 data = maskData; 2247 bnds = bounds; 2248 } 2249 2250 // fast discard 2251 if (!bnds.intersects(x, y, w, h)) 2252 return false; 2253 2254 // replace to origin 2255 int xi = (int) x - bnds.x; 2256 int yi = (int) y - bnds.y; 2257 int wi = (int) (x + w) - (int) x; 2258 int hi = (int) (y + h) - (int) y; 2259 2260 // adjust box to mask size 2261 if (xi < 0) 2262 { 2263 wi += xi; 2264 xi = 0; 2265 } 2266 if (yi < 0) 2267 { 2268 hi += yi; 2269 yi = 0; 2270 } 2271 if ((xi + wi) > bnds.width) 2272 wi -= (xi + wi) - bnds.width; 2273 if ((yi + hi) > bnds.height) 2274 hi -= (yi + hi) - bnds.height; 2275 2276 // scan all pixels, can take sometime if mask is large 2277 int offset = (yi * bnds.width) + xi; 2278 for (int j = 0; j < hi; j++) 2279 { 2280 for (int i = 0; i < wi; i++) 2281 if (data[offset++] != 0) 2282 return true; 2283 2284 offset += bnds.width - wi; 2285 } 2286 2287 return false; 2288 } 2289 2290 @Override 2291 public boolean[] getBooleanMask(int x, int y, int w, int h, boolean inclusive) 2292 { 2293 final boolean[] result = new boolean[Math.max(0, w) * Math.max(0, h)]; 2294 final byte[] data; 2295 final Rectangle bnds; 2296 2297 synchronized (this) 2298 { 2299 data = maskData; 2300 bnds = bounds; 2301 } 2302 2303 // calculate intersection 2304 final Rectangle intersect = bnds.intersection(new Rectangle(x, y, w, h)); 2305 2306 // no intersection between mask and specified rectangle 2307 if (intersect.isEmpty()) 2308 return result; 2309 2310 // this ROI doesn't take care of inclusive parameter as intersect = contains 2311 int offSrc = 0; 2312 int offDst = 0; 2313 2314 // adjust offset in source mask 2315 if (intersect.x > bnds.x) 2316 offSrc += (intersect.x - bnds.x); 2317 if (intersect.y > bnds.y) 2318 offSrc += (intersect.y - bnds.y) * bnds.width; 2319 // adjust offset in destination mask 2320 if (bnds.x > x) 2321 offDst += (bnds.x - x); 2322 if (bnds.y > y) 2323 offDst += (bnds.y - y) * w; 2324 2325 for (int j = 0; j < intersect.height; j++) 2326 { 2327 for (int i = 0; i < intersect.width; i++) 2328 result[offDst++] = (data[offSrc++] != 0); 2329 2330 offSrc += bnds.width - intersect.width; 2331 offDst += w - intersect.width; 2332 } 2333 2334 return result; 2335 } 2336 2337 @Override 2338 public double computeNumberOfPoints() 2339 { 2340 // just count the number of point contained in the mask 2341 double result = 0d; 2342 final byte[] data = maskData; 2343 2344 for (int i = 0; i < data.length; i++) 2345 if (data[i] != 0) 2346 result += 1d; 2347 2348 return result; 2349 } 2350 2351 @Override 2352 public boolean canTranslate() 2353 { 2354 return true; 2355 } 2356 2357 @Override 2358 public void translate(double dx, double dy) 2359 { 2360 translateX += dx; 2361 translateY += dy; 2362 2363 // convert to integer 2364 final int dxi = (int) translateX; 2365 final int dyi = (int) translateY; 2366 // keep trace of not used floating part 2367 translateX -= dxi; 2368 translateY -= dyi; 2369 2370 if ((dxi != 0) || (dyi != 0)) 2371 { 2372 bounds.translate(dxi, dyi); 2373 roiChanged(false); 2374 } 2375 } 2376 2377 @Override 2378 public boolean canSetPosition() 2379 { 2380 return true; 2381 } 2382 2383 @Override 2384 public void setPosition2D(Point2D newPosition) 2385 { 2386 bounds = new Rectangle((int) newPosition.getX(), (int) newPosition.getY(), bounds.width, bounds.height); 2387 2388 roiChanged(false); 2389 } 2390 2391 /** 2392 * Set the mask from a BooleanMask2D object.<br> 2393 * If specified mask is <i>null</i> then ROI is cleared. 2394 */ 2395 public void setAsBooleanMask(BooleanMask2D mask) 2396 { 2397 // mask empty ? --> just clear the ROI 2398 if ((mask == null) || mask.isEmpty()) 2399 clear(); 2400 // don't need bounds optimization as BooleanMask2D should be already optimized 2401 else 2402 setAsBooleanMask(mask.bounds, mask.mask, false); 2403 } 2404 2405 /** 2406 * Set the mask from a boolean array.<br> 2407 * r represents the region defined by the boolean array. 2408 * 2409 * @param r 2410 * @param booleanMask 2411 */ 2412 protected void setAsByteMask(Rectangle r, byte[] mask, boolean doBoundsOptimization) 2413 { 2414 // reset image with new rectangle 2415 updateImage(r); 2416 2417 System.arraycopy(mask, 0, maskData, 0, r.width * r.height); 2418 2419 if (doBoundsOptimization) 2420 { 2421 // optimize bounds 2422 if (isUpdating()) 2423 boundsNeedUpdate = true; 2424 else 2425 optimizeBounds(); 2426 } 2427 2428 // notify roi changed 2429 roiChanged(true); 2430 } 2431 2432 /** 2433 * Set the mask from a boolean array.<br> 2434 * r represents the region defined by the boolean array. 2435 * 2436 * @param r 2437 * @param booleanMask 2438 */ 2439 protected void setAsBooleanMask(Rectangle r, boolean[] booleanMask, boolean doBoundsOptimization) 2440 { 2441 // reset image with new rectangle 2442 updateImage(r); 2443 2444 final byte[] data = maskData; 2445 2446 for (int i = 0; i < data.length; i++) 2447 data[i] = (byte) (booleanMask[i] ? 1 : 0); 2448 2449 if (doBoundsOptimization) 2450 { 2451 // optimize bounds 2452 if (isUpdating()) 2453 boundsNeedUpdate = true; 2454 else 2455 optimizeBounds(); 2456 } 2457 2458 // notify roi changed 2459 roiChanged(true); 2460 } 2461 2462 /** 2463 * Set the mask from a boolean array.<br> 2464 * r represents the region defined by the boolean array. 2465 * 2466 * @param r 2467 * @param booleanMask 2468 */ 2469 public void setAsBooleanMask(Rectangle r, boolean[] booleanMask) 2470 { 2471 setAsBooleanMask(r, booleanMask, true); 2472 } 2473 2474 public void setAsBooleanMask(int x, int y, int w, int h, boolean[] booleanMask) 2475 { 2476 setAsBooleanMask(new Rectangle(x, y, w, h), booleanMask); 2477 } 2478 2479 /** 2480 * Fast up scaling by a factor of 2 (each point become a 2x2 block points) 2481 */ 2482 public void upscale() 2483 { 2484 setAsBooleanMask(getBooleanMask(true).upscale()); 2485 } 2486 2487 /** 2488 * Fast 2x down scaling (each 2x2 block points become 1 point).<br> 2489 * 2490 * @param nbPointForTrue 2491 * the minimum number of <code>true</code>points from a 2x2 block to give a <code>true</code> resulting 2492 * point.<br> 2493 * Accepted value: 1 to 4 2494 */ 2495 public void downscale(int nbPointForTrue) 2496 { 2497 setAsBooleanMask(getBooleanMask(true).downscale(nbPointForTrue)); 2498 } 2499 2500 /** 2501 * Fast 2x down scaling (each 2x2 block points become 1 point).<br> 2502 */ 2503 public void downscale() 2504 { 2505 setAsBooleanMask(getBooleanMask(true).downscale()); 2506 } 2507 2508 @Override 2509 public void onChanged(CollapsibleEvent object) 2510 { 2511 final ROIEvent event = (ROIEvent) object; 2512 2513 // do here global process on ROI change 2514 switch (event.getType()) 2515 { 2516 case ROI_CHANGED: 2517 // update bounds if needed 2518 if (boundsNeedUpdate && !roiModifiedByMouse) 2519 { 2520 if (optimizeBounds()) 2521 // need to send a new change event ! 2522 roiChanged(true); 2523 } 2524 // we need to rebuild shape 2525 if (StringUtil.equals(event.getPropertyName(), ROI_CHANGED_ALL)) 2526 getOverlay().needRebuild = true; 2527 else 2528 { 2529 final ROI2DAreaPainter overlay = getOverlay(); 2530 2531 // z position change ? --> need total rebuild 2532 if (overlay.lastBuildPosZ != getZ()) 2533 overlay.needRebuild = true; 2534 // just need to change position 2535 else 2536 overlay.updateVtkObjectsBounds(); 2537 } 2538 break; 2539 2540 case FOCUS_CHANGED: 2541 case SELECTION_CHANGED: 2542 getOverlay().updateVtkDisplayProperties(); 2543 break; 2544 2545 case PROPERTY_CHANGED: 2546 final String property = event.getPropertyName(); 2547 2548 if (StringUtil.equals(property, PROPERTY_STROKE) || StringUtil.equals(property, PROPERTY_COLOR) 2549 || StringUtil.equals(property, PROPERTY_OPACITY)) 2550 getOverlay().updateVtkDisplayProperties(); 2551 break; 2552 2553 default: 2554 break; 2555 } 2556 2557 super.onChanged(object); 2558 } 2559 2560 @Override 2561 public boolean loadFromXML(Node node) 2562 { 2563 beginUpdate(); 2564 try 2565 { 2566 if (!super.loadFromXML(node)) 2567 return false; 2568 2569 final Rectangle rect = new Rectangle(); 2570 2571 // retrieve mask bounds 2572 rect.x = XMLUtil.getElementIntValue(node, ID_BOUNDS_X, 0); 2573 rect.y = XMLUtil.getElementIntValue(node, ID_BOUNDS_Y, 0); 2574 rect.width = XMLUtil.getElementIntValue(node, ID_BOUNDS_W, 0); 2575 rect.height = XMLUtil.getElementIntValue(node, ID_BOUNDS_H, 0); 2576 2577 // retrieve mask data 2578 final byte[] data = XMLUtil.getElementBytesValue(node, ID_BOOLMASK_DATA, new byte[0]); 2579 2580 // an error occurred while retrieved XML data 2581 if (data == null) 2582 return false; 2583 2584 // set the ROI from the unpacked boolean mask 2585 setAsByteMask(rect, data, false); 2586 } 2587 finally 2588 { 2589 endUpdate(); 2590 } 2591 2592 return true; 2593 } 2594 2595 @Override 2596 public boolean saveToXML(Node node) 2597 { 2598 if (!super.saveToXML(node)) 2599 return false; 2600 2601 final byte[] data; 2602 final Rectangle bnds; 2603 2604 synchronized (maskData) 2605 { 2606 // need to duplicate to avoid array change during XML saving (ZIP packing don't like that) 2607 data = maskData.clone(); 2608 bnds = bounds; 2609 } 2610 2611 final int len = bnds.width * bnds.height; 2612 2613 // invalid --> return false 2614 if ((len > 0) && (len != data.length)) 2615 return false; 2616 2617 // retrieve mask bounds 2618 XMLUtil.setElementIntValue(node, ID_BOUNDS_X, bnds.x); 2619 XMLUtil.setElementIntValue(node, ID_BOUNDS_Y, bnds.y); 2620 XMLUtil.setElementIntValue(node, ID_BOUNDS_W, bnds.width); 2621 XMLUtil.setElementIntValue(node, ID_BOUNDS_H, bnds.height); 2622 2623 // set mask data as byte array 2624 if (len > 0) 2625 XMLUtil.setElementBytesValue(node, ID_BOOLMASK_DATA, data); 2626 2627 return true; 2628 } 2629}