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 icy.painter.Anchor2D; 022import icy.painter.PathAnchor2D; 023import icy.resource.ResourceUtil; 024import icy.roi.ROI; 025import icy.type.point.Point5D; 026import icy.util.ShapeUtil; 027import icy.util.XMLUtil; 028 029import java.awt.Shape; 030import java.awt.geom.Area; 031import java.awt.geom.Path2D; 032import java.awt.geom.Point2D; 033import java.awt.geom.Rectangle2D; 034import java.util.ArrayList; 035import java.util.List; 036 037import org.w3c.dom.Element; 038import org.w3c.dom.Node; 039 040/** 041 * ROI Path.<br> 042 * This ROI can display a Path2D shape.<br> 043 * You can modify and remove points (adding new point isn't supported). 044 * 045 * @author Stephane 046 */ 047public class ROI2DPath extends ROI2DShape 048{ 049 public static final String ID_POINTS = "points"; 050 public static final String ID_POINT = "point"; 051 public static final String ID_WINDING = "winding"; 052 053 protected Area closedArea; 054 protected Path2D openPath; 055 056 static Path2D initPath(Point2D position) 057 { 058 final Path2D result = new Path2D.Double(); 059 060 result.reset(); 061 if (position != null) 062 result.moveTo(position.getX(), position.getY()); 063 064 return result; 065 } 066 067 /** 068 * Build a new ROI2DPath from the specified path. 069 */ 070 public ROI2DPath(Path2D path, Area closedArea, Path2D openPath) 071 { 072 super(path); 073 074 rebuildControlPointsFromPath(); 075 076 if (closedArea == null) 077 this.closedArea = new Area(ShapeUtil.getClosedPath(path)); 078 else 079 this.closedArea = closedArea; 080 if (openPath == null) 081 this.openPath = ShapeUtil.getOpenPath(path); 082 else 083 this.openPath = openPath; 084 085 // set icon (default name is defined by getDefaultName()) 086 setIcon(ResourceUtil.ICON_ROI_POLYLINE); 087 } 088 089 /** 090 * Build a new ROI2DPath from the specified path. 091 */ 092 public ROI2DPath(Path2D path) 093 { 094 this(path, null, null); 095 096 } 097 098 /** 099 * Build a new ROI2DPath from the specified path. 100 */ 101 public ROI2DPath(Shape shape) 102 { 103 this(new Path2D.Double(shape), (shape instanceof Area) ? (Area) shape : null, null); 104 } 105 106 /** 107 * @deprecated 108 */ 109 @Deprecated 110 public ROI2DPath(Point2D pt, boolean cm) 111 { 112 this(pt); 113 } 114 115 public ROI2DPath(Point2D position) 116 { 117 this(initPath(position)); 118 } 119 120 /** 121 * Generic constructor for interactive mode 122 */ 123 public ROI2DPath(Point5D pt) 124 { 125 this(pt.toPoint2D()); 126 } 127 128 public ROI2DPath() 129 { 130 this(new Path2D.Double(Path2D.WIND_NON_ZERO)); 131 } 132 133 @Override 134 public String getDefaultName() 135 { 136 return "Path2D"; 137 } 138 139 @Override 140 protected Anchor2D createAnchor(Point2D pos) 141 { 142 return new PathAnchor2D(pos.getX(), pos.getY(), getColor(), getFocusedColor()); 143 } 144 145 protected void rebuildControlPointsFromPath() 146 { 147 beginUpdate(); 148 try 149 { 150 // remove all point 151 removeAllPoint(); 152 153 // add path points to the control point list 154 for (Anchor2D pt : ShapeUtil.getAnchorsFromShape(getPath(), getColor(), getFocusedColor())) 155 addPoint(pt); 156 } 157 finally 158 { 159 endUpdate(); 160 } 161 } 162 163 protected Path2D getPath() 164 { 165 return (Path2D) shape; 166 } 167 168 /** 169 * Returns the closed area part of the ROI2DPath in {@link Area} shape format 170 */ 171 public Area getClosedArea() 172 { 173 return closedArea; 174 } 175 176 /** 177 * Returns the open path part of the ROI2DPath in {@link Path2D} shape format 178 */ 179 public Path2D getOpenPath() 180 { 181 return openPath; 182 } 183 184 @Override 185 public boolean canAddPoint() 186 { 187 // this ROI doesn't support point add 188 return false; 189 } 190 191 @Override 192 public boolean contains(double x, double y) 193 { 194 // only consider closed path 195 return ShapeUtil.getClosedPath(getPath()).contains(x, y); 196 } 197 198 @Override 199 public boolean contains(Point2D p) 200 { 201 // only consider closed path 202 return ShapeUtil.getClosedPath(getPath()).contains(p); 203 } 204 205 @Override 206 public boolean contains(double x, double y, double w, double h) 207 { 208 // only consider closed path 209 return ShapeUtil.getClosedPath(getPath()).contains(x, y, w, h); 210 } 211 212 @Override 213 public boolean contains(Rectangle2D r) 214 { 215 // only consider closed path 216 return ShapeUtil.getClosedPath(getPath()).contains(r); 217 } 218 219 @Override 220 public boolean contains(ROI roi) 221 { 222 // not closed --> do not contains anything 223 if (!ShapeUtil.isClosed(shape)) 224 return false; 225 226 return super.contains(roi); 227 } 228 229 @Override 230 public ROI add(ROI roi, boolean allowCreate) throws UnsupportedOperationException 231 { 232 if (roi instanceof ROI2DShape) 233 { 234 final ROI2DShape roiShape = (ROI2DShape) roi; 235 236 // only if on same position 237 if ((getZ() == roiShape.getZ()) && (getT() == roiShape.getT()) && (getC() == roiShape.getC())) 238 { 239 final Path2D path = getPath(); 240 241 if (roi instanceof ROI2DPath) 242 { 243 final ROI2DPath roiPath = (ROI2DPath) roi; 244 245 // compute closed area and open path parts 246 closedArea.add(roiPath.closedArea); 247 openPath.append(roiPath.openPath, false); 248 } 249 else 250 { 251 // compute closed area and open path parts 252 if (roiShape.getShape() instanceof Area) 253 closedArea.add((Area) roiShape.getShape()); 254 else 255 closedArea.add(new Area(ShapeUtil.getClosedPath(roiShape))); 256 openPath.append(ShapeUtil.getOpenPath(roiShape), false); 257 } 258 259 // then rebuild path from closed and open parts 260 path.reset(); 261 path.append(closedArea, false); 262 path.append(openPath, false); 263 264 rebuildControlPointsFromPath(); 265 roiChanged(true); 266 267 return this; 268 } 269 } 270 271 return super.add(roi, allowCreate); 272 } 273 274 @Override 275 public ROI intersect(ROI roi, boolean allowCreate) throws UnsupportedOperationException 276 { 277 if (roi instanceof ROI2DShape) 278 { 279 final ROI2DShape roiShape = (ROI2DShape) roi; 280 281 // only if on same position 282 if ((getZ() == roiShape.getZ()) && (getT() == roiShape.getT()) && (getC() == roiShape.getC())) 283 { 284 final Path2D path = getPath(); 285 286 if (roi instanceof ROI2DPath) 287 { 288 final ROI2DPath roiPath = (ROI2DPath) roi; 289 290 // compute closed area intersection and clear open path 291 closedArea.intersect(roiPath.closedArea); 292 openPath.reset(); 293 } 294 else 295 { 296 // compute closed area intersection and clear open path 297 if (roiShape.getShape() instanceof Area) 298 closedArea.intersect((Area) roiShape.getShape()); 299 else 300 closedArea.intersect(new Area(ShapeUtil.getClosedPath(roiShape))); 301 openPath.reset(); 302 } 303 304 // then rebuild path from closed area (open part is empty) 305 path.reset(); 306 path.append(closedArea, false); 307 308 rebuildControlPointsFromPath(); 309 roiChanged(true); 310 311 return this; 312 } 313 } 314 315 return super.intersect(roi, allowCreate); 316 } 317 318 @Override 319 public ROI exclusiveAdd(ROI roi, boolean allowCreate) throws UnsupportedOperationException 320 { 321 if (roi instanceof ROI2DShape) 322 { 323 final ROI2DShape roiShape = (ROI2DShape) roi; 324 325 // only if on same position 326 if ((getZ() == roiShape.getZ()) && (getT() == roiShape.getT()) && (getC() == roiShape.getC())) 327 { 328 final Path2D path = getPath(); 329 330 if (roi instanceof ROI2DPath) 331 { 332 final ROI2DPath roiPath = (ROI2DPath) roi; 333 334 // compute exclusive union on closed area and simple append for open path 335 closedArea.exclusiveOr(roiPath.closedArea); 336 openPath.append(roiPath.openPath, false); 337 } 338 else 339 { 340 // compute exclusive union on closed area and simple append for open path 341 if (roiShape.getShape() instanceof Area) 342 closedArea.exclusiveOr((Area) roiShape.getShape()); 343 else 344 closedArea.exclusiveOr(new Area(ShapeUtil.getClosedPath(roiShape))); 345 openPath.append(ShapeUtil.getOpenPath(roiShape), false); 346 } 347 348 // then rebuild path from closed and open parts 349 path.reset(); 350 path.append(closedArea, false); 351 path.append(openPath, false); 352 353 rebuildControlPointsFromPath(); 354 roiChanged(true); 355 356 return this; 357 } 358 } 359 360 return super.exclusiveAdd(roi, allowCreate); 361 } 362 363 @Override 364 public ROI subtract(ROI roi, boolean allowCreate) throws UnsupportedOperationException 365 { 366 if (roi instanceof ROI2DShape) 367 { 368 final ROI2DShape roiShape = (ROI2DShape) roi; 369 370 // only if on same position 371 if ((getZ() == roiShape.getZ()) && (getT() == roiShape.getT()) && (getC() == roiShape.getC())) 372 { 373 final Path2D path = getPath(); 374 375 if (roi instanceof ROI2DPath) 376 { 377 final ROI2DPath roiPath = (ROI2DPath) roi; 378 379 // compute closed area intersection and clear open path parts 380 closedArea.exclusiveOr(roiPath.closedArea); 381 if (!roiPath.closedArea.isEmpty()) 382 openPath.reset(); 383 } 384 else 385 { 386 final Area area; 387 388 // compute closed area and open path parts 389 if (roiShape.getShape() instanceof Area) 390 area = (Area) roiShape.getShape(); 391 else 392 area = new Area(ShapeUtil.getClosedPath(roiShape)); 393 if (!area.isEmpty()) 394 { 395 closedArea.exclusiveOr(area); 396 openPath.reset(); 397 } 398 } 399 400 // then rebuild path from closed and open parts 401 path.reset(); 402 path.append(closedArea, false); 403 path.append(openPath, false); 404 405 rebuildControlPointsFromPath(); 406 roiChanged(true); 407 408 return this; 409 } 410 } 411 412 return super.subtract(roi, allowCreate); 413 } 414 415 /** 416 * Return the list of control points for this ROI. 417 */ 418 public List<PathAnchor2D> getPathAnchors() 419 { 420 final List<PathAnchor2D> result = new ArrayList<PathAnchor2D>(); 421 422 synchronized (controlPoints) 423 { 424 for (Anchor2D pt : controlPoints) 425 result.add((PathAnchor2D) pt); 426 } 427 428 return result; 429 } 430 431 protected void updateCachedStructures() 432 { 433 closedArea = new Area(ShapeUtil.getClosedPath(getPath())); 434 openPath = ShapeUtil.getOpenPath(getPath()); 435 } 436 437 @Override 438 protected void updateShape() 439 { 440 ShapeUtil.buildPathFromAnchors(getPath(), getPathAnchors(), false); 441 // update internal closed area and open path 442 updateCachedStructures(); 443 444 // call super method after shape has been updated 445 super.updateShape(); 446 } 447 448 @Override 449 public boolean loadFromXML(Node node) 450 { 451 beginUpdate(); 452 try 453 { 454 if (!super.loadFromXML(node)) 455 return false; 456 457 removeAllPoint(); 458 459 final List<Node> nodesPoint = XMLUtil.getChildren(XMLUtil.getElement(node, ID_POINTS), ID_POINT); 460 if (nodesPoint != null) 461 { 462 for (Node n : nodesPoint) 463 { 464 final PathAnchor2D pt = (PathAnchor2D) createAnchor(new Point2D.Double()); 465 pt.loadPositionFromXML(n); 466 addPoint(pt); 467 } 468 } 469 470 getPath().setWindingRule(XMLUtil.getElementIntValue(node, ID_WINDING, Path2D.WIND_NON_ZERO)); 471 } 472 finally 473 { 474 endUpdate(); 475 } 476 477 return true; 478 } 479 480 @Override 481 public boolean saveToXML(Node node) 482 { 483 if (!super.saveToXML(node)) 484 return false; 485 486 final Element points = XMLUtil.setElement(node, ID_POINTS); 487 488 synchronized (controlPoints) 489 { 490 for (Anchor2D pt : controlPoints) 491 pt.savePositionToXML(XMLUtil.addElement(points, ID_POINT)); 492 } 493 494 XMLUtil.setElementIntValue(node, ID_WINDING, getPath().getWindingRule()); 495 496 return true; 497 } 498}