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}