001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.roi;
020
021import icy.type.TypeUtil;
022import icy.type.collection.array.DynamicArray;
023import icy.type.point.Point2DUtil;
024
025import java.awt.Point;
026import java.awt.Rectangle;
027import java.awt.geom.Point2D;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.Collections;
031import java.util.Comparator;
032import java.util.List;
033
034/**
035 * Class to define a 2D boolean mask region and make basic boolean operation between masks.<br>
036 * The bounds property of this object represents the region defined by the boolean mask.
037 * 
038 * @author Stephane
039 */
040public class BooleanMask2D implements Cloneable
041{
042    private static class Component
043    {
044        public DynamicArray.Int points;
045        public List<Component> children;
046        private Component root;
047
048        public Component()
049        {
050            points = new DynamicArray.Int(0);
051            children = new ArrayList<Component>();
052            root = this;
053        }
054
055        public void addPoint(int x, int y)
056        {
057            points.addSingle(x);
058            points.addSingle(y);
059        }
060
061        public void addComponent(Component c)
062        {
063            final Component croot = c.root;
064
065            if (croot != root)
066            {
067                children.add(croot);
068                croot.setRoot(root);
069            }
070        }
071
072        public boolean isRoot()
073        {
074            return root == this;
075        }
076
077        private void setRoot(Component value)
078        {
079            root = value;
080
081            final int size = children.size();
082            for (int c = 0; c < size; c++)
083                children.get(c).setRoot(value);
084        }
085
086        public int getTotalSize()
087        {
088            int result = points.getSize();
089
090            final int size = children.size();
091            for (int c = 0; c < size; c++)
092                result += children.get(c).getTotalSize();
093
094            return result;
095        }
096
097        public int[] getAllPoints()
098        {
099            final int[] result = new int[getTotalSize()];
100
101            // get all point
102            getAllPoints(result, 0);
103
104            return result;
105        }
106
107        private int getAllPoints(int[] result, int offset)
108        {
109            final int[] intPoints = points.asArray();
110
111            System.arraycopy(intPoints, 0, result, offset, intPoints.length);
112
113            int off = offset + intPoints.length;
114            final int csize = children.size();
115            for (int c = 0; c < csize; c++)
116                off = children.get(c).getAllPoints(result, off);
117
118            return off;
119        }
120    }
121
122    // find first non visited contour point
123    private static int findStartPoint(int startOffset, boolean mask[], boolean visitedMask[])
124    {
125        for (int i = startOffset; i < mask.length; i++)
126            if (mask[i] && !visitedMask[i])
127                return i;
128
129        return -1;
130    }
131
132    private static List<Point> insertPoints(List<Point> result, List<Point> source)
133    {
134        if (result.isEmpty())
135        {
136            result.addAll(source);
137            return result;
138        }
139        // nothing to insert
140        if (source.isEmpty())
141            return result;
142
143        final Point firstPointSource = source.get(0);
144        final Point lastPointSource = source.get(source.size() - 1);
145        final int len = result.size();
146
147        for (int i = 0; i < len; i++)
148        {
149            final Point p = result.get(i);
150
151            if (Point2DUtil.areConnected(p, firstPointSource))
152            {
153                final List<Point> newResult = new ArrayList<Point>(result.subList(0, i + 1));
154                newResult.addAll(source);
155                if ((i + 1) < len)
156                    newResult.addAll(new ArrayList<Point>(result.subList(i + 1, len)));
157                return newResult;
158            }
159            if (Point2DUtil.areConnected(p, lastPointSource))
160            {
161                final List<Point> newResult = new ArrayList<Point>(result.subList(0, i + 1));
162                Collections.reverse(source);
163                newResult.addAll(source);
164                if ((i + 1) < len)
165                    newResult.addAll(new ArrayList<Point>(result.subList(i + 1, len)));
166                return newResult;
167            }
168        }
169
170        return null;
171    }
172
173    private static List<Point> connect(List<Point> result, List<Point> source)
174    {
175        if (result.isEmpty())
176        {
177            result.addAll(source);
178            return result;
179        }
180        // nothing to connect
181        if (source.isEmpty())
182            return result;
183
184        final Point2D firstPointResult = result.get(0);
185        final Point2D lastPointResult = result.get(result.size() - 1);
186        final Point2D firstPointSource = source.get(0);
187        final Point2D lastPointSource = source.get(source.size() - 1);
188
189        // res-tail - src-head connection --> res = res + src
190        if (Point2DUtil.areConnected(firstPointSource, lastPointResult))
191        {
192            result.addAll(source);
193            return result;
194        }
195        // res-head - src-head connection --> res = reverse(src) + res
196        if (Point2DUtil.areConnected(firstPointSource, firstPointResult))
197        {
198            Collections.reverse(source);
199            source.addAll(result);
200            return source;
201        }
202        // res-head - src-tail connection --> res = src + res
203        if (Point2DUtil.areConnected(lastPointSource, firstPointResult))
204        {
205            source.addAll(result);
206            return source;
207        }
208        // res-tail - src-tail connection --> res = res + reverse(src)
209        if (Point2DUtil.areConnected(lastPointSource, lastPointResult))
210        {
211            Collections.reverse(source);
212            result.addAll(source);
213            return result;
214        }
215
216        // can't connect
217        return null;
218    }
219
220    /**
221     * Return a list of Point representing the contour points of the mask.<br>
222     * Points are returned in ascending XY order:
223     * 
224     * <pre>
225     *  1234 
226     *  5  6
227     *  7 8
228     *   9
229     * </pre>
230     */
231    public static List<Point> getContourPoints(Rectangle bounds, boolean mask[])
232    {
233        if (bounds.isEmpty())
234            return new ArrayList<Point>(1);
235
236        final List<Point> points = new ArrayList<Point>(mask.length / 16);
237        final int h = bounds.height;
238        final int w = bounds.width;
239        final int minx = bounds.x;
240        final int miny = bounds.y;
241        final int maxx = minx + (w - 1);
242        final int maxy = miny + (h - 1);
243
244        // cache
245        boolean top = false;
246        boolean bottom = false;
247        boolean left = false;
248        boolean right = false;
249        boolean current;
250
251        int offset = 0;
252
253        // special case of single pixel mask
254        if ((w == 1) && (h == 1))
255        {
256            if (mask[0])
257                points.add(new Point(minx, miny));
258        }
259        // special case of single pixel width mask
260        else if (w == 1)
261        {
262            // first pixel of row
263            top = false;
264            current = mask[offset];
265            bottom = mask[++offset];
266
267            // current pixel is a border ?
268            if (current && !(top && bottom))
269                points.add(new Point(minx, miny));
270
271            // row
272            for (int y = miny + 1; y < maxy; y++)
273            {
274                // cache
275                top = current;
276                current = bottom;
277                bottom = mask[++offset];
278
279                // current pixel is a border ?
280                if (current && !(top && bottom))
281                    points.add(new Point(minx, y));
282            }
283
284            // cache
285            top = current;
286            current = bottom;
287            bottom = false;
288
289            // current pixel is a border ?
290            if (current && !(top && bottom))
291                points.add(new Point(minx, maxy));
292        }
293        // special case of single pixel height mask
294        else if (h == 1)
295        {
296            // first pixel of line
297            left = false;
298            current = mask[offset];
299            right = mask[++offset];
300
301            // current pixel is a border ?
302            if (current && !(left && right))
303                points.add(new Point(minx, miny));
304
305            // line
306            for (int x = minx + 1; x < maxx; x++)
307            {
308                // cache
309                left = current;
310                current = right;
311                right = mask[++offset];
312
313                // current pixel is a border ?
314                if (current && !(left && right))
315                    points.add(new Point(x, miny));
316            }
317
318            // last pixel of first line
319            left = current;
320            current = right;
321            right = false;
322
323            // current pixel is a border ?
324            if (current && !(left && right))
325                points.add(new Point(maxx, miny));
326        }
327        // normal case
328        else
329        {
330            // first pixel of first line
331            top = false;
332            left = false;
333            current = mask[offset];
334            bottom = mask[offset + w];
335            right = mask[++offset];
336
337            // current pixel is a border ?
338            if (current && !(top && left && right && bottom))
339                points.add(new Point(minx, miny));
340
341            // first line
342            for (int x = minx + 1; x < maxx; x++)
343            {
344                // cache
345                left = current;
346                current = right;
347                bottom = mask[offset + w];
348                right = mask[++offset];
349
350                // current pixel is a border ?
351                if (current && !(top && left && right && bottom))
352                    points.add(new Point(x, miny));
353            }
354
355            // last pixel of first line
356            left = current;
357            current = right;
358            bottom = mask[offset + w];
359            right = false;
360            offset++;
361
362            // current pixel is a border ?
363            if (current && !(top && left && right && bottom))
364                points.add(new Point(maxx, miny));
365
366            for (int y = miny + 1; y < maxy; y++)
367            {
368                // first pixel of line
369                left = false;
370                current = mask[offset];
371                top = mask[offset - w];
372                bottom = mask[offset + w];
373                right = mask[++offset];
374
375                // current pixel is a border ?
376                if (current && !(top && left && right && bottom))
377                    points.add(new Point(minx, y));
378
379                for (int x = minx + 1; x < maxx; x++)
380                {
381                    // cache
382                    left = current;
383                    current = right;
384                    top = mask[offset - w];
385                    bottom = mask[offset + w];
386                    right = mask[++offset];
387
388                    // current pixel is a border ?
389                    if (current && !(top && left && right && bottom))
390                        points.add(new Point(x, y));
391                }
392
393                // last pixel of line
394                left = current;
395                current = right;
396                top = mask[offset - w];
397                bottom = mask[offset + w];
398                right = false;
399                offset++;
400
401                // current pixel is a border ?
402                if (current && !(top && left && right && bottom))
403                    points.add(new Point(maxx, y));
404            }
405
406            // first pixel of last line
407            left = false;
408            current = mask[offset];
409            top = mask[offset - w];
410            bottom = false;
411            right = mask[++offset];
412
413            // current pixel is a border ?
414            if (current && !(top && left && right && bottom))
415                points.add(new Point(minx, maxy));
416
417            // last line
418            for (int x = minx + 1; x < maxx; x++)
419            {
420                // cache
421                left = current;
422                current = right;
423                top = mask[offset - w];
424                right = mask[++offset];
425
426                // current pixel is a border ?
427                if (current && !(top && left && right && bottom))
428                    points.add(new Point(x, maxy));
429            }
430
431            // last pixel of last line
432            left = current;
433            current = right;
434            top = mask[offset - w];
435            right = false;
436
437            // current pixel is a border ?
438            if (current && !(top && left && right && bottom))
439                points.add(new Point(maxx, maxy));
440        }
441
442        return points;
443    }
444
445    /**
446     * Fast 2x up scaling (each point become 2x2 bloc points).<br>
447     * This method create a new boolean mask.
448     */
449    public static BooleanMask2D upscale(BooleanMask2D mask)
450    {
451        final Rectangle srcBounds;
452        final boolean[] srcMask;
453        final boolean[] resMask;
454
455        synchronized (mask)
456        {
457            srcBounds = mask.bounds;
458            srcMask = mask.mask;
459        }
460
461        final int srcW = srcBounds.width;
462        final int srcH = srcBounds.height;
463
464        resMask = new boolean[srcW * srcH * 2 * 2];
465
466        int offSrc = 0;
467        int offRes = 0;
468        for (int y = 0; y < srcH; y++)
469        {
470            for (int x = 0; x < srcW; x++)
471            {
472                final boolean v = srcMask[offSrc++];
473
474                // unpack in 4 points
475                resMask[offRes + 0] = v;
476                resMask[offRes + 1] = v;
477                resMask[offRes + (srcW * 2) + 0] = v;
478                resMask[offRes + (srcW * 2) + 1] = v;
479
480                // next
481                offRes += 2;
482            }
483
484            // pass 1 line
485            offRes += srcW * 2;
486        }
487
488        return new BooleanMask2D(new Rectangle(srcBounds.x * 2, srcBounds.y * 2, srcW * 2, srcH * 2), resMask);
489    }
490
491    /**
492     * Fast 2x down scaling (each 2x2 block points become 1 point value).<br>
493     * This method create a new int[] array returning the number of <code>true</code> point for each 2x2 block.
494     * 
495     * @param mask
496     *        the boolean mask to download
497     */
498    public static byte[] getDownscaleValues(BooleanMask2D mask)
499    {
500        final Rectangle srcBounds;
501        final boolean[] srcMask;
502        final byte[] resMask;
503
504        synchronized (mask)
505        {
506            srcBounds = mask.bounds;
507            srcMask = mask.mask;
508        }
509
510        final int resW = srcBounds.width / 2;
511        final int resH = srcBounds.height / 2;
512
513        resMask = new byte[resW * resH];
514
515        int offSrc = 0;
516        int offRes = 0;
517        for (int y = 0; y < resH; y++)
518        {
519            for (int x = 0; x < resW; x++)
520            {
521                byte v = 0;
522
523                if (srcMask[offSrc + 0])
524                    v++;
525                if (srcMask[offSrc + 1])
526                    v++;
527                if (srcMask[offSrc + (resW * 2) + 0])
528                    v++;
529                if (srcMask[offSrc + (resW * 2) + 1])
530                    v++;
531
532                // pack in 1 point
533                resMask[offRes++] = v;
534                // next
535                offSrc += 2;
536            }
537
538            // pass 1 line
539            offSrc += resW * 2;
540            // fix for odd width
541            if ((srcBounds.width & 1) == 1) offSrc += 2;
542        }
543
544        return resMask;
545    }
546
547    /**
548     * Fast 2x down scaling (each 2x2 block points become 1 point).<br>
549     * This method create a new boolean mask.
550     * 
551     * @param mask
552     *        the boolean mask to download
553     * @param nbPointForTrue
554     *        the minimum number of <code>true</code>points from a 2x2 block to give a <code>true</code> resulting
555     *        point.<br>
556     *        Accepted value: 1 to 4
557     */
558    public static BooleanMask2D downscale(BooleanMask2D mask, int nbPointForTrue)
559    {
560        final Rectangle srcBounds;
561
562        synchronized (mask)
563        {
564            srcBounds = mask.bounds;
565        }
566
567        final int validPt = Math.min(Math.max(nbPointForTrue, 1), 4);
568        final int resW = srcBounds.width / 2;
569        final int resH = srcBounds.height / 2;
570
571        final byte[] valueMask = getDownscaleValues(mask);
572        final boolean[] resMask = new boolean[resW * resH];
573
574        for (int i = 0; i < valueMask.length; i++)
575            resMask[i] = valueMask[i] >= validPt;
576
577        return new BooleanMask2D(new Rectangle(srcBounds.x / 2, srcBounds.y / 2, resW, resH), resMask);
578    }
579
580    /**
581     * Fast 2x down scaling (each 2x2 block points become 1 point).<br>
582     * This method create a new boolean mask.
583     */
584    public static BooleanMask2D downscale(BooleanMask2D mask)
585    {
586        return downscale(mask, 2);
587    }
588
589    /**
590     * Build global boolean mask from union of all specified mask
591     */
592    public static BooleanMask2D getUnion(List<BooleanMask2D> masks)
593    {
594        BooleanMask2D result = null;
595
596        // compute global union boolean mask of all ROI2D
597        for (BooleanMask2D bm : masks)
598        {
599            // update global mask
600            if (result == null)
601                result = new BooleanMask2D(bm.bounds, bm.mask);
602            else
603                result = getUnion(result, bm);
604        }
605
606        // return an empty BooleanMask2D instead of null
607        if (result == null)
608            result = new BooleanMask2D();
609
610        return result;
611    }
612
613    /**
614     * Build resulting mask from union of the mask1 and mask2.<br>
615     * If <code>mask1</code> is <code>null</code> then a copy of <code>mask2</code> is returned.<br>
616     * If <code>mask2</code> is <code>null</code> then a copy of <code>mask1</code> is returned.<br>
617     * An empty mask is returned if both <code>mask1</code> and <code>mask2</code> are <code>null</code>.
618     */
619    public static BooleanMask2D getUnion(BooleanMask2D mask1, BooleanMask2D mask2)
620    {
621        if ((mask1 == null) && (mask2 == null))
622            return new BooleanMask2D();
623
624        if ((mask1 == null) || mask1.isEmpty())
625            return (BooleanMask2D) mask2.clone();
626        if ((mask2 == null) || mask2.isEmpty())
627            return (BooleanMask2D) mask1.clone();
628
629        return getUnion(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
630    }
631
632    /**
633     * Build resulting mask from union of the mask1 and mask2:
634     * 
635     * <pre>
636     *        mask1          +       mask2        =      result
637     * 
638     *     ################     ################     ################
639     *     ##############         ##############     ################
640     *     ############             ############     ################
641     *     ##########                 ##########     ################
642     *     ########                     ########     ################
643     *     ######                         ######     ######    ######
644     *     ####                             ####     ####        ####
645     *     ##                                 ##     ##            ##
646     * </pre>
647     */
648    public static BooleanMask2D getUnion(Rectangle bounds1, boolean[] mask1, Rectangle bounds2, boolean[] mask2)
649    {
650        final Rectangle union = bounds1.union(bounds2);
651
652        if (!union.isEmpty())
653        {
654            final boolean[] mask = new boolean[union.width * union.height];
655            int offDst, offSrc;
656
657            // calculate offset
658            offDst = ((bounds1.y - union.y) * union.width) + (bounds1.x - union.x);
659            offSrc = 0;
660
661            for (int y = 0; y < bounds1.height; y++)
662            {
663                for (int x = 0; x < bounds1.width; x++)
664                    mask[offDst + x] |= mask1[offSrc++];
665
666                offDst += union.width;
667            }
668
669            // calculate offset
670            offDst = ((bounds2.y - union.y) * union.width) + (bounds2.x - union.x);
671            offSrc = 0;
672
673            for (int y = 0; y < bounds2.height; y++)
674            {
675                for (int x = 0; x < bounds2.width; x++)
676                    mask[offDst + x] |= mask2[offSrc++];
677
678                offDst += union.width;
679            }
680
681            return new BooleanMask2D(union, mask);
682        }
683
684        return new BooleanMask2D();
685    }
686
687    /**
688     * Build global boolean mask from intersection of all specified mask
689     */
690    public static BooleanMask2D getIntersection(List<BooleanMask2D> masks)
691    {
692        BooleanMask2D result = null;
693
694        // compute global intersect boolean mask of all ROI2D
695        for (BooleanMask2D bm : masks)
696        {
697            // update global mask
698            if (result == null)
699                result = new BooleanMask2D(bm.bounds, bm.mask);
700            else
701                result = getIntersection(result, bm);
702        }
703
704        // return an empty BooleanMask2D instead of null
705        if (result == null)
706            result = new BooleanMask2D();
707
708        return result;
709    }
710
711    /**
712     * Build resulting mask from intersection of the mask1 and mask2.<br>
713     * An empty mask is returned if <code>mask1</code> or <code>mask2</code> is <code>null</code>.
714     */
715    public static BooleanMask2D getIntersection(BooleanMask2D mask1, BooleanMask2D mask2)
716    {
717        if ((mask1 == null) || (mask2 == null))
718            return new BooleanMask2D();
719
720        return getIntersection(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
721    }
722
723    /**
724     * Build resulting mask from intersection of the mask1 and mask2:
725     * 
726     * <pre>
727     *        mask1     intersect     mask2      =        result
728     * 
729     *     ################     ################     ################
730     *     ##############         ##############       ############
731     *     ############             ############         ########
732     *     ##########                 ##########           ####
733     *     ########                     ########
734     *     ######                         ######
735     *     ####                             ####
736     *     ##                                 ##
737     * </pre>
738     */
739    public static BooleanMask2D getIntersection(Rectangle bounds1, boolean[] mask1, Rectangle bounds2, boolean[] mask2)
740    {
741        final Rectangle intersect = bounds1.intersection(bounds2);
742
743        if (!intersect.isEmpty())
744        {
745            final boolean[] mask = new boolean[intersect.width * intersect.height];
746
747            // calculate offsets
748            int off1 = ((intersect.y - bounds1.y) * bounds1.width) + (intersect.x - bounds1.x);
749            int off2 = ((intersect.y - bounds2.y) * bounds2.width) + (intersect.x - bounds2.x);
750            int off = 0;
751
752            for (int y = 0; y < intersect.height; y++)
753            {
754                for (int x = 0; x < intersect.width; x++)
755                    mask[off++] = mask1[off1 + x] & mask2[off2 + x];
756
757                off1 += bounds1.width;
758                off2 += bounds2.width;
759            }
760
761            return new BooleanMask2D(intersect, mask);
762        }
763
764        return new BooleanMask2D();
765    }
766
767    /**
768     * Build global boolean mask from exclusive union of all specified mask
769     */
770    public static BooleanMask2D getExclusiveUnion(List<BooleanMask2D> masks)
771    {
772        BooleanMask2D result = null;
773
774        // compute global exclusive union boolean mask of all ROI2D
775        for (BooleanMask2D bm : masks)
776        {
777            // update global mask
778            if (result == null)
779                result = new BooleanMask2D(bm.bounds, bm.mask);
780            else
781                result = getExclusiveUnion(result, bm);
782        }
783
784        // return an empty BooleanMask2D instead of null
785        if (result == null)
786            result = new BooleanMask2D();
787
788        return result;
789    }
790
791    /**
792     * Build resulting mask from exclusive union of the mask1 and mask2.<br>
793     * If <code>mask1</code> is <code>null</code> then a copy of <code>mask2</code> is returned.<br>
794     * If <code>mask2</code> is <code>null</code> then a copy of <code>mask1</code> is returned.<br>
795     * <code>null</code> is returned if both <code>mask1</code> and <code>mask2</code> are <code>null</code>.
796     */
797    public static BooleanMask2D getExclusiveUnion(BooleanMask2D mask1, BooleanMask2D mask2)
798    {
799        if ((mask1 == null) && (mask2 == null))
800            return new BooleanMask2D();
801
802        if ((mask1 == null) || mask1.isEmpty())
803            return (BooleanMask2D) mask2.clone();
804        if ((mask2 == null) || mask2.isEmpty())
805            return (BooleanMask2D) mask1.clone();
806
807        return getExclusiveUnion(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
808    }
809
810    /**
811     * Build resulting mask from exclusive union of the mask1 and mask2:
812     * 
813     * <pre>
814     *          mask1       xor      mask2        =       result
815     * 
816     *     ################     ################
817     *     ##############         ##############     ##            ##
818     *     ############             ############     ####        ####
819     *     ##########                 ##########     ######    ######
820     *     ########                     ########     ################
821     *     ######                         ######     ######    ######
822     *     ####                             ####     ####        ####
823     *     ##                                 ##     ##            ##
824     * </pre>
825     */
826    public static BooleanMask2D getExclusiveUnion(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
827            boolean[] mask2)
828    {
829        final Rectangle union = bounds1.union(bounds2);
830
831        if (!union.isEmpty())
832        {
833            final boolean[] mask = new boolean[union.width * union.height];
834            int offDst, offSrc;
835
836            // calculate offset
837            offDst = ((bounds1.y - union.y) * union.width) + (bounds1.x - union.x);
838            offSrc = 0;
839
840            for (int y = 0; y < bounds1.height; y++)
841            {
842                for (int x = 0; x < bounds1.width; x++)
843                    mask[offDst + x] ^= mask1[offSrc++];
844
845                offDst += union.width;
846            }
847
848            // calculate offset
849            offDst = ((bounds2.y - union.y) * union.width) + (bounds2.x - union.x);
850            offSrc = 0;
851
852            for (int y = 0; y < bounds2.height; y++)
853            {
854                for (int x = 0; x < bounds2.width; x++)
855                    mask[offDst + x] ^= mask2[offSrc++];
856
857                offDst += union.width;
858            }
859
860            final BooleanMask2D result = new BooleanMask2D(union, mask);
861
862            // optimize bounds
863            result.optimizeBounds();
864
865            return result;
866        }
867
868        return new BooleanMask2D();
869    }
870
871    /**
872     * Build resulting mask from the subtraction of mask2 from mask1.<br>
873     * If <code>mask2</code> is <code>null</code> then a copy of <code>mask1</code> is returned.<br>
874     * If <code>mask1</code> is <code>null</code> then a empty mask is returned.
875     */
876    public static BooleanMask2D getSubtraction(BooleanMask2D mask1, BooleanMask2D mask2)
877    {
878        if (mask1 == null)
879            return new BooleanMask2D();
880        if (mask2 == null)
881            return (BooleanMask2D) mask1.clone();
882
883        return getSubtraction(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
884    }
885
886    /**
887     * Build resulting mask from the subtraction of mask2 from mask1:
888     * 
889     * <pre>
890     *        mask1          -        mask2       =  result
891     * 
892     *     ################     ################
893     *     ##############         ##############     ##
894     *     ############             ############     ####
895     *     ##########                 ##########     ######
896     *     ########                     ########     ########
897     *     ######                         ######     ######
898     *     ####                             ####     ####
899     *     ##                                 ##     ##
900     * </pre>
901     */
902    public static BooleanMask2D getSubtraction(Rectangle bounds1, boolean[] mask1, Rectangle bounds2, boolean[] mask2)
903    {
904        final boolean[] mask = mask1.clone();
905        final Rectangle subtract = new Rectangle(bounds1);
906        final BooleanMask2D result = new BooleanMask2D(subtract, mask);
907
908        // compute intersection
909        final Rectangle intersection = bounds1.intersection(bounds2);
910
911        // need to subtract something ?
912        if (!intersection.isEmpty())
913        {
914            // calculate offset
915            int offDst = ((intersection.y - subtract.y) * subtract.width) + (intersection.x - subtract.x);
916            int offSrc = ((intersection.y - bounds2.y) * bounds2.width) + (intersection.x - bounds2.x);
917
918            for (int y = 0; y < intersection.height; y++)
919            {
920                for (int x = 0; x < intersection.width; x++)
921                    mask[offDst + x] &= !mask2[offSrc + x];
922
923                offDst += subtract.width;
924                offSrc += bounds2.width;
925            }
926
927            // optimize bounds
928            result.optimizeBounds();
929        }
930
931        return result;
932    }
933
934    /**
935     * @deprecated Use {@link #getUnion(List)} instead.
936     */
937    @Deprecated
938    public static BooleanMask2D getUnionBooleanMask(List<BooleanMask2D> masks)
939    {
940        return getUnion(masks);
941    }
942
943    /**
944     * @deprecated Use {@link ROIUtil#getUnion(List)} instead.
945     */
946    @Deprecated
947    public static BooleanMask2D getUnionBooleanMask(ROI2D[] rois)
948    {
949        final List<BooleanMask2D> masks = new ArrayList<BooleanMask2D>();
950
951        // compute global union boolean mask of all ROI2D
952        for (ROI2D roi : rois)
953            masks.add(roi.getBooleanMask(true));
954
955        return getUnionBooleanMask(masks);
956    }
957
958    /**
959     * @deprecated Use {@link ROIUtil#getUnion(List)} instead.
960     */
961    @Deprecated
962    public static BooleanMask2D getUnionBooleanMask(ArrayList<ROI2D> rois)
963    {
964        return getUnionBooleanMask(rois.toArray(new ROI2D[rois.size()]));
965    }
966
967    /**
968     * @deprecated Use {@link #getUnion(BooleanMask2D, BooleanMask2D)} instead.
969     */
970    @Deprecated
971    public static BooleanMask2D getUnionBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
972    {
973        return getUnion(mask1, mask2);
974
975    }
976
977    /**
978     * @deprecated Use {@link #getUnion(Rectangle, boolean[], Rectangle, boolean[])} instead.
979     */
980    @Deprecated
981    public static BooleanMask2D getUnionBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
982            boolean[] mask2)
983    {
984        return getUnion(bounds1, mask1, bounds2, mask2);
985
986    }
987
988    /**
989     * @deprecated Use {@link #getIntersection(List)} instead.
990     */
991    @Deprecated
992    public static BooleanMask2D getIntersectionBooleanMask(List<BooleanMask2D> masks)
993    {
994        return getIntersection(masks);
995    }
996
997    /**
998     * @deprecated Use {@link #getIntersection(BooleanMask2D, BooleanMask2D)} instead.
999     */
1000    @Deprecated
1001    public static BooleanMask2D getIntersectionBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
1002    {
1003        return getIntersection(mask1, mask2);
1004    }
1005
1006    /**
1007     * @deprecated Use {@link #getIntersection(Rectangle, boolean[], Rectangle, boolean[])} instead.
1008     */
1009    @Deprecated
1010    public static BooleanMask2D getIntersectionBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
1011            boolean[] mask2)
1012    {
1013        return getIntersection(bounds1, mask1, bounds2, mask2);
1014    }
1015
1016    /**
1017     * @deprecated Use {@link ROIUtil#getIntersection(List)} instead.
1018     */
1019    @Deprecated
1020    public static BooleanMask2D getIntersectBooleanMask(ROI2D[] rois)
1021    {
1022        final List<BooleanMask2D> masks = new ArrayList<BooleanMask2D>();
1023
1024        // compute global union boolean mask of all ROI2D
1025        for (ROI2D roi : rois)
1026            masks.add(roi.getBooleanMask(true));
1027
1028        return getIntersectionBooleanMask(masks);
1029    }
1030
1031    /**
1032     * @deprecated Use {@link ROIUtil#getIntersection(List)} instead.
1033     */
1034    @Deprecated
1035    public static BooleanMask2D getIntersectBooleanMask(ArrayList<ROI2D> rois)
1036    {
1037        return getIntersectBooleanMask(rois.toArray(new ROI2D[rois.size()]));
1038    }
1039
1040    /**
1041     * @deprecated Use {@link #getIntersectionBooleanMask(BooleanMask2D, BooleanMask2D)} instead.
1042     */
1043    @Deprecated
1044    public static BooleanMask2D getIntersectBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
1045    {
1046        return getIntersectionBooleanMask(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
1047    }
1048
1049    /**
1050     * @deprecated Use {@link #getIntersectionBooleanMask(Rectangle, boolean[], Rectangle, boolean[])} instead.
1051     */
1052    @Deprecated
1053    public static BooleanMask2D getIntersectBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
1054            boolean[] mask2)
1055    {
1056        return getIntersectionBooleanMask(bounds1, mask1, bounds2, mask2);
1057    }
1058
1059    /**
1060     * @deprecated Use {@link #getExclusiveUnion(List)} instead.
1061     */
1062    @Deprecated
1063    public static BooleanMask2D getExclusiveUnionBooleanMask(List<BooleanMask2D> masks)
1064    {
1065        return getExclusiveUnion(masks);
1066    }
1067
1068    /**
1069     * @deprecated Use {@link ROIUtil#getExclusiveUnion(List)} instead.
1070     */
1071    @Deprecated
1072    public static BooleanMask2D getExclusiveUnionBooleanMask(ROI2D[] rois)
1073    {
1074        final List<BooleanMask2D> masks = new ArrayList<BooleanMask2D>();
1075
1076        // compute global union boolean mask of all ROI2D
1077        for (ROI2D roi : rois)
1078            masks.add(roi.getBooleanMask(true));
1079
1080        return getExclusiveUnion(masks);
1081    }
1082
1083    /**
1084     * @deprecated Use {@link ROIUtil#getExclusiveUnion(List)} instead.
1085     */
1086    @Deprecated
1087    public static BooleanMask2D getExclusiveUnionBooleanMask(ArrayList<ROI2D> rois)
1088    {
1089        return getExclusiveUnionBooleanMask(rois.toArray(new ROI2D[rois.size()]));
1090    }
1091
1092    /**
1093     * @deprecated Use {@link #getExclusiveUnion(BooleanMask2D, BooleanMask2D)} instead.
1094     */
1095    @Deprecated
1096    public static BooleanMask2D getExclusiveUnionBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
1097    {
1098        return getExclusiveUnion(mask1, mask2);
1099
1100    }
1101
1102    /**
1103     * @deprecated Use {@link #getExclusiveUnion(Rectangle, boolean[], Rectangle, boolean[])} instead.
1104     */
1105    @Deprecated
1106    public static BooleanMask2D getExclusiveUnionBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
1107            boolean[] mask2)
1108    {
1109        return getExclusiveUnion(bounds1, mask1, bounds2, mask2);
1110
1111    }
1112
1113    /**
1114     * @deprecated Use {@link #getExclusiveUnionBooleanMask(ROI2D[])} instead.
1115     */
1116    @Deprecated
1117    public static BooleanMask2D getXorBooleanMask(ROI2D[] rois)
1118    {
1119        return getExclusiveUnionBooleanMask(rois);
1120    }
1121
1122    /**
1123     * @deprecated Use {@link #getExclusiveUnionBooleanMask(List)} instead.
1124     */
1125    @Deprecated
1126    public static BooleanMask2D getXorBooleanMask(ArrayList<ROI2D> rois)
1127    {
1128        return getExclusiveUnionBooleanMask(rois);
1129    }
1130
1131    /**
1132     * @deprecated Use {@link #getExclusiveUnionBooleanMask(BooleanMask2D, BooleanMask2D)} instead.
1133     */
1134    @Deprecated
1135    public static BooleanMask2D getXorBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
1136    {
1137        return getExclusiveUnionBooleanMask(mask1, mask2);
1138    }
1139
1140    /**
1141     * @deprecated Uses {@link #getExclusiveUnionBooleanMask(Rectangle, boolean[], Rectangle, boolean[])} instead.
1142     */
1143    @Deprecated
1144    public static BooleanMask2D getXorBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
1145            boolean[] mask2)
1146    {
1147        return getExclusiveUnionBooleanMask(bounds1, mask1, bounds2, mask2);
1148    }
1149
1150    /**
1151     * Region represented by the mask.
1152     */
1153    public Rectangle bounds;
1154    /**
1155     * Boolean mask array.
1156     */
1157    public boolean[] mask;
1158
1159    /**
1160     * Create an empty BooleanMask2D
1161     */
1162    public BooleanMask2D()
1163    {
1164        this(new Rectangle(), new boolean[0]);
1165    }
1166
1167    /**
1168     * @param bounds
1169     * @param mask
1170     */
1171    public BooleanMask2D(Rectangle bounds, boolean[] mask)
1172    {
1173        super();
1174
1175        this.bounds = bounds;
1176        this.mask = mask;
1177    }
1178
1179    /**
1180     * Build a new boolean mask from the specified array of {@link Point}.<br>
1181     */
1182    public BooleanMask2D(Point[] points)
1183    {
1184        super();
1185
1186        if ((points == null) || (points.length == 0))
1187        {
1188            bounds = new Rectangle();
1189            mask = new boolean[0];
1190        }
1191        else
1192        {
1193            int minX = Integer.MAX_VALUE;
1194            int minY = Integer.MAX_VALUE;
1195            int maxX = Integer.MIN_VALUE;
1196            int maxY = Integer.MIN_VALUE;
1197
1198            for (Point pt : points)
1199            {
1200                final int x = pt.x;
1201                final int y = pt.y;
1202
1203                if (x < minX)
1204                    minX = x;
1205                if (x > maxX)
1206                    maxX = x;
1207                if (y < minY)
1208                    minY = y;
1209                if (y > maxY)
1210                    maxY = y;
1211            }
1212
1213            bounds = new Rectangle(minX, minY, (maxX - minX) + 1, (maxY - minY) + 1);
1214            mask = new boolean[bounds.width * bounds.height];
1215
1216            for (Point pt : points)
1217                mask[((pt.y - minY) * bounds.width) + (pt.x - minX)] = true;
1218        }
1219    }
1220
1221    /**
1222     * Build a new boolean mask from the specified array of integer representing points.<br>
1223     * <br>
1224     * The array should contains point coordinates defined as follow:<br>
1225     * <code>result.length</code> = number of point * 2<br>
1226     * <code>result[(pt * 2) + 0]</code> = X coordinate for point <i>pt</i>.<br>
1227     * <code>result[(pt * 2) + 1]</code> = Y coordinate for point <i>pt</i>.<br>
1228     */
1229    public BooleanMask2D(int[] points)
1230    {
1231        super();
1232
1233        if ((points == null) || (points.length == 0))
1234        {
1235            bounds = new Rectangle();
1236            mask = new boolean[0];
1237        }
1238        else
1239        {
1240            int minX = Integer.MAX_VALUE;
1241            int minY = Integer.MAX_VALUE;
1242            int maxX = Integer.MIN_VALUE;
1243            int maxY = Integer.MIN_VALUE;
1244
1245            for (int i = 0; i < points.length; i += 2)
1246            {
1247                final int x = points[i + 0];
1248                final int y = points[i + 1];
1249
1250                if (x < minX)
1251                    minX = x;
1252                if (x > maxX)
1253                    maxX = x;
1254                if (y < minY)
1255                    minY = y;
1256                if (y > maxY)
1257                    maxY = y;
1258            }
1259
1260            bounds = new Rectangle(minX, minY, (maxX - minX) + 1, (maxY - minY) + 1);
1261            mask = new boolean[bounds.width * bounds.height];
1262
1263            for (int i = 0; i < points.length; i += 2)
1264            {
1265                final int x = points[i + 0];
1266                final int y = points[i + 1];
1267
1268                mask[((y - minY) * bounds.width) + (x - minX)] = true;
1269            }
1270        }
1271    }
1272
1273    /**
1274     * Return true if boolean mask is empty<br>
1275     */
1276    public boolean isEmpty()
1277    {
1278        return bounds.isEmpty();
1279    }
1280
1281    /**
1282     * Return true if mask contains the specified point
1283     */
1284    public boolean contains(int x, int y)
1285    {
1286        if (bounds.contains(x, y))
1287            return mask[(x - bounds.x) + ((y - bounds.y) * bounds.width)];
1288
1289        return false;
1290    }
1291
1292    /**
1293     * Return true if mask contains the specified 2D mask.
1294     */
1295    public boolean contains(BooleanMask2D booleanMask)
1296    {
1297        return contains(booleanMask.bounds, booleanMask.mask);
1298    }
1299
1300    /**
1301     * Return true if mask contains the specified 2D mask.
1302     */
1303    public boolean contains(Rectangle rect, boolean[] bmask)
1304    {
1305        final Rectangle intersect = bounds.intersection(rect);
1306
1307        // intersection should be equal to rect
1308        if (intersect.equals(rect))
1309        {
1310            // calculate offsets
1311            int off1 = ((intersect.y - bounds.y) * bounds.width) + (intersect.x - bounds.x);
1312            int off2 = ((intersect.y - rect.y) * rect.width) + (intersect.x - rect.x);
1313
1314            for (int y = 0; y < intersect.height; y++)
1315            {
1316                for (int x = 0; x < intersect.width; x++)
1317                    if (bmask[off2 + x] && !mask[off1 + x])
1318                        return false;
1319
1320                off1 += bounds.width;
1321                off2 += rect.width;
1322            }
1323
1324            return true;
1325        }
1326
1327        return false;
1328    }
1329
1330    /**
1331     * Return true if mask intersects (contains at least one point) the specified 2D mask.
1332     */
1333    public boolean intersects(BooleanMask2D booleanMask)
1334    {
1335        return intersects(booleanMask.bounds, booleanMask.mask);
1336    }
1337
1338    /**
1339     * Return true if mask intersects (contains at least one point) the specified 2D mask region.
1340     */
1341    public boolean intersects(Rectangle rect, boolean[] bmask)
1342    {
1343        final Rectangle intersect = bounds.intersection(rect);
1344
1345        if (!intersect.isEmpty())
1346        {
1347            // calculate offsets
1348            int off1 = ((intersect.y - bounds.y) * bounds.width) + (intersect.x - bounds.x);
1349            int off2 = ((intersect.y - rect.y) * rect.width) + (intersect.x - rect.x);
1350
1351            for (int y = 0; y < intersect.height; y++)
1352            {
1353                for (int x = 0; x < intersect.width; x++)
1354                    if (mask[off1 + x] && bmask[off2 + x])
1355                        return true;
1356
1357                off1 += bounds.width;
1358                off2 += rect.width;
1359            }
1360        }
1361
1362        return false;
1363    }
1364
1365    /**
1366     * Return the number of points contained in this boolean mask.
1367     */
1368    public int getNumberOfPoints()
1369    {
1370        int result = 0;
1371
1372        for (int i = 0; i < mask.length;)
1373            if (mask[i++])
1374                result++;
1375
1376        return result;
1377    }
1378
1379    /**
1380     * Return an array of {@link Point} representing all points of the current mask.<br>
1381     * Points are returned in ascending XY order :
1382     * 
1383     * <pre>
1384     * Ymin  12 
1385     *      3456
1386     *       78
1387     * Ymax   9
1388     * </pre>
1389     * 
1390     * @see #getPointsAsIntArray()
1391     */
1392    public Point[] getPoints()
1393    {
1394        return TypeUtil.toPoint(getPointsAsIntArray());
1395    }
1396
1397    /**
1398     * Return an array of integer representing all points of the current mask.<br>
1399     * <code>result.length</code> = number of point * 2<br>
1400     * <code>result[(pt * 2) + 0]</code> = X coordinate for point <i>pt</i>.<br>
1401     * <code>result[(pt * 2) + 1]</code> = Y coordinate for point <i>pt</i>.<br>
1402     * Points are returned in ascending XY order :
1403     * 
1404     * <pre>
1405     * Ymin  12 
1406     *      3456
1407     *       78
1408     * Ymax   9
1409     * </pre>
1410     */
1411    public int[] getPointsAsIntArray()
1412    {
1413        if (bounds.isEmpty())
1414            return new int[0];
1415
1416        int[] points = new int[mask.length * 2];
1417        final int maxx = bounds.x + bounds.width;
1418        final int maxy = bounds.y + bounds.height;
1419
1420        int pt = 0;
1421        int off = 0;
1422        for (int y = bounds.y; y < maxy; y++)
1423        {
1424            for (int x = bounds.x; x < maxx; x++)
1425            {
1426                if (mask[off++])
1427                {
1428                    points[pt++] = x;
1429                    points[pt++] = y;
1430                }
1431            }
1432        }
1433
1434        final int[] result = new int[pt];
1435
1436        System.arraycopy(points, 0, result, 0, pt);
1437
1438        return result;
1439    }
1440
1441    /**
1442     * Return a 2D array of integer representing points of each component of the current mask.<br>
1443     * A component is basically an isolated object which does not touch any other objects.<br>
1444     * Internal use only.
1445     */
1446    protected List<Component> getComponentsPointsInternal()
1447    {
1448        final List<Component> components = new ArrayList<Component>();
1449        final int w = bounds.width;
1450        final int minx = bounds.x;
1451        final int miny = bounds.y;
1452
1453        // cache
1454        final Component[] line0 = new Component[w + 2];
1455        final Component[] line1 = new Component[w + 2];
1456
1457        Arrays.fill(line0, null);
1458        Arrays.fill(line1, null);
1459
1460        Component[] prevLine = line0;
1461        Component[] currLine = line1;
1462
1463        Component left;
1464        Component top;
1465        Component topleft;
1466
1467        int offset = 0;
1468        for (int y = 0; y < bounds.height; y++)
1469        {
1470            // prepare previous cache
1471            topleft = null;
1472            left = null;
1473
1474            for (int x = 0; x < w; x++)
1475            {
1476                top = prevLine[x + 1];
1477
1478                // we have a component pixel
1479                if (mask[offset++])
1480                {
1481                    if (topleft != null)
1482                    {
1483                        // mix component
1484                        if ((left != null) && (left != topleft))
1485                            topleft.addComponent(left);
1486
1487                        left = topleft;
1488                    }
1489                    else if (top != null)
1490                    {
1491                        // mix component
1492                        if ((left != null) && (left != top))
1493                            top.addComponent(left);
1494
1495                        left = top;
1496                    }
1497                    else if (left == null)
1498                    {
1499                        // new component
1500                        left = new Component();
1501                        components.add(left);
1502                    }
1503
1504                    // add pixel to component
1505                    left.addPoint(x + minx, y + miny);
1506                }
1507                else
1508                {
1509                    // mix component
1510                    if ((left != null) && (top != null) && (left != top))
1511                        top.addComponent(left);
1512
1513                    left = null;
1514                }
1515
1516                topleft = top;
1517                // set current component index line cache
1518                currLine[x + 1] = left;
1519            }
1520
1521            Component[] pl = prevLine;
1522            prevLine = currLine;
1523            currLine = pl;
1524        }
1525
1526        final List<Component> result = new ArrayList<Component>();
1527
1528        // remove partial components
1529        for (Component component : components)
1530            if (component.isRoot())
1531                result.add(component);
1532
1533        return result;
1534    }
1535
1536    /**
1537     * Compute and return a 2D array of {@link Point} representing points of each component of the
1538     * current mask.<br>
1539     * A component is basically an isolated object which do not touch any other object.<br>
1540     * <br>
1541     * The array is returned in the following format :<br>
1542     * <code>result.lenght</code> = number of component.<br>
1543     * <code>result[c].length</code> = number of point of component c.<br>
1544     * <code>result[c][n]</code> = Point n of component c.<br>
1545     * 
1546     * @param sorted
1547     *        When true points are returned in ascending XY order :
1548     * 
1549     *        <pre>
1550     * Ymin  12 
1551     *      3456
1552     *       78
1553     * Ymax   9
1554     *        </pre>
1555     * 
1556     * @see #getComponentsPointsAsIntArray()
1557     */
1558    public Point[][] getComponentsPoints(boolean sorted)
1559    {
1560        if (bounds.isEmpty())
1561            return new Point[0][0];
1562
1563        final Comparator<Point> pointComparator;
1564
1565        if (sorted)
1566        {
1567            pointComparator = new Comparator<Point>()
1568            {
1569                @Override
1570                public int compare(Point p1, Point p2)
1571                {
1572                    if (p1.y < p2.y)
1573                        return -1;
1574                    if (p1.y > p2.y)
1575                        return 1;
1576
1577                    return 0;
1578                }
1579            };
1580        }
1581        else
1582            pointComparator = null;
1583
1584        final int[][] cPoints = getComponentsPointsAsIntArray();
1585        final Point[][] cResult = new Point[cPoints.length][];
1586
1587        for (int i = 0; i < cPoints.length; i++)
1588        {
1589            final Point[] result = TypeUtil.toPoint(cPoints[i]);
1590
1591            // sort points
1592            if (pointComparator != null)
1593                Arrays.sort(result, pointComparator);
1594
1595            cResult[i] = result;
1596        }
1597
1598        return cResult;
1599    }
1600
1601    /**
1602     * Compute and return a 2D array of integer representing points of each component of the
1603     * current mask.<br>
1604     * A component is basically an isolated object which do not touch any other object.<br>
1605     * <br>
1606     * The array is returned in the following format :<br>
1607     * <code>result.lenght</code> = number of component.<br>
1608     * <code>result[c].length</code> = number of point * 2 for component c.<br>
1609     * <code>result[c][(pt * 2) + 0]</code> = X coordinate for point <i>pt</i> of component
1610     * <i>c</i>.<br>
1611     * <code>result[c][(pt * 2) + 1]</code> = Y coordinate for point <i>pt</i> of component
1612     * <i>c</i>.<br>
1613     * 
1614     * @see #getComponentsPoints(boolean)
1615     */
1616    public int[][] getComponentsPointsAsIntArray()
1617    {
1618        if (bounds.isEmpty())
1619            return new int[0][0];
1620
1621        final List<Component> components = getComponentsPointsInternal();
1622        final int[][] result = new int[components.size()][];
1623
1624        // convert list of point to Point array
1625        for (int i = 0; i < result.length; i++)
1626            result[i] = components.get(i).getAllPoints();
1627
1628        return result;
1629    }
1630
1631    /**
1632     * Return an array of boolean mask representing each independent component of the current
1633     * mask.<br>
1634     * A component is basically an isolated object which does not touch any other objects.
1635     */
1636    public BooleanMask2D[] getComponents()
1637    {
1638        if (bounds.isEmpty())
1639            return new BooleanMask2D[0];
1640
1641        final int[][] componentsPoints = getComponentsPointsAsIntArray();
1642        final List<BooleanMask2D> result = new ArrayList<BooleanMask2D>();
1643
1644        // convert array of point to boolean mask
1645        for (int[] componentPoints : componentsPoints)
1646            result.add(new BooleanMask2D(componentPoints));
1647
1648        return result.toArray(new BooleanMask2D[result.size()]);
1649    }
1650
1651    /**
1652     * Returns a list of Point representing the contour points of the mask in connected order.<br>
1653     * Note that you should use this method carefully at it does not make any sense to use this method for mask
1654     * containing disconnected objects.<br>
1655     * Also it may returns incorrect result if the contour is not consistent (several connected contour possible).
1656     * 
1657     * @see #getContourPoints()
1658     */
1659    public List<Point> getConnectedContourPoints()
1660    {
1661        final int[] intArrayPoints = getContourPointsAsIntArray();
1662        final List<List<Point>> allPoints = new ArrayList<List<Point>>();
1663
1664        // empty contour
1665        if (intArrayPoints.length == 0)
1666            return new ArrayList<Point>(0);
1667
1668        final boolean[] contourMask;
1669        final Rectangle contourBounds;
1670
1671        synchronized (this)
1672        {
1673            contourMask = new boolean[mask.length];
1674            contourBounds = bounds;
1675        }
1676
1677        final int minx = contourBounds.x;
1678        final int miny = contourBounds.y;
1679        final int w = contourBounds.width;
1680
1681        // build contour mask
1682        for (int i = 0; i < intArrayPoints.length; i += 2)
1683            contourMask[(intArrayPoints[i + 0] - minx) + ((intArrayPoints[i + 1] - miny) * w)] = true;
1684
1685        final int maxx = minx + (w - 1);
1686        final int maxy = miny + (contourBounds.height - 1);
1687        // create visited pixel mask
1688        final boolean[] visitedMask = new boolean[contourMask.length];
1689
1690        // first start point
1691        int startOffset = findStartPoint(0, contourMask, visitedMask);
1692
1693        while (startOffset != -1)
1694        {
1695            final List<Point> points = new ArrayList<Point>(intArrayPoints.length / 2);
1696
1697            int offset = startOffset;
1698            int x = (offset % w) + minx;
1699            int y = (offset / w) + miny;
1700
1701            // add first point
1702            visitedMask[offset] = true;
1703            points.add(new Point(x, y));
1704
1705            // scanning order
1706            // 567
1707            // 4x0
1708            // 321
1709            int scan = 0;
1710            int remain = 8;
1711
1712            // scan connected pixels (8 points)
1713            while (remain-- > 0)
1714            {
1715                int tmpOff;
1716
1717                switch (scan & 7)
1718                {
1719                    case 0:
1720                        // not last X
1721                        if (x < maxx)
1722                        {
1723                            tmpOff = offset + 1;
1724
1725                            // contour ?
1726                            if (contourMask[tmpOff])
1727                            {
1728                                // contour already visited --> done
1729                                if (visitedMask[tmpOff])
1730                                {
1731                                    remain = 0;
1732                                    break;
1733                                }
1734
1735                                // set new position
1736                                x++;
1737                                offset = tmpOff;
1738                                // mark as visited
1739                                visitedMask[offset] = true;
1740                                // and add point
1741                                points.add(new Point(x, y));
1742                                // change next scan pos
1743                                scan = 7 - 1;
1744                                remain = 8;
1745                            }
1746                        }
1747                        break;
1748
1749                    case 1:
1750                        // not last X and not last Y
1751                        if ((x < maxx) && (y < maxy))
1752                        {
1753                            tmpOff = offset + w + 1;
1754                            // contour ?
1755                            if (contourMask[tmpOff])
1756                            {
1757                                // contour already visited --> done
1758                                if (visitedMask[tmpOff])
1759                                {
1760                                    remain = 0;
1761                                    break;
1762                                }
1763
1764                                // set new position
1765                                x++;
1766                                y++;
1767                                offset = tmpOff;
1768                                // mark as visited
1769                                visitedMask[offset] = true;
1770                                // and add point
1771                                points.add(new Point(x, y));
1772                                // change next scan pos
1773                                scan = 7 - 1;
1774                                remain = 8;
1775                            }
1776                        }
1777                        break;
1778
1779                    case 2:
1780                        // not last Y
1781                        if (y < maxy)
1782                        {
1783                            tmpOff = offset + w;
1784                            // contour ?
1785                            if (contourMask[tmpOff])
1786                            {
1787                                // contour already visited --> done
1788                                if (visitedMask[tmpOff])
1789                                {
1790                                    remain = 0;
1791                                    break;
1792                                }
1793
1794                                // set new position
1795                                y++;
1796                                offset = tmpOff;
1797                                // mark as visited
1798                                visitedMask[offset] = true;
1799                                // and add point
1800                                points.add(new Point(x, y));
1801                                // change next scan pos
1802                                scan = 1 - 1;
1803                                remain = 8;
1804                            }
1805                        }
1806                        break;
1807
1808                    case 3:
1809                        // not first X and not last Y
1810                        if ((x > minx) && (y < maxy))
1811                        {
1812                            tmpOff = (offset + w) - 1;
1813
1814                            // contour ?
1815                            if (contourMask[tmpOff])
1816                            {
1817                                // contour already visited --> done
1818                                if (visitedMask[tmpOff])
1819                                {
1820                                    remain = 0;
1821                                    break;
1822                                }
1823
1824                                // set new position
1825                                x--;
1826                                y++;
1827                                offset = tmpOff;
1828                                // mark as visited
1829                                visitedMask[offset] = true;
1830                                // and add point
1831                                points.add(new Point(x, y));
1832                                // change next scan pos
1833                                scan = 1 - 1;
1834                                remain = 8;
1835                            }
1836                        }
1837                        break;
1838
1839                    case 4:
1840                        // not first X
1841                        if (x > minx)
1842                        {
1843                            tmpOff = offset - 1;
1844                            // contour ?
1845                            if (contourMask[tmpOff])
1846                            {
1847                                // contour already visited --> done
1848                                if (visitedMask[tmpOff])
1849                                {
1850                                    remain = 0;
1851                                    break;
1852                                }
1853
1854                                // set new position
1855                                x--;
1856                                offset = tmpOff;
1857                                // mark as visited
1858                                visitedMask[offset] = true;
1859                                // and add point
1860                                points.add(new Point(x, y));
1861                                // change next scan pos
1862                                scan = 3 - 1;
1863                                remain = 8;
1864                            }
1865                        }
1866                        break;
1867
1868                    case 5:
1869                        // not first X and not first Y
1870                        if ((x > minx) && (y > miny))
1871                        {
1872                            tmpOff = offset - (w + 1);
1873                            // contour ?
1874                            if (contourMask[tmpOff])
1875                            {
1876                                // contour already visited --> done
1877                                if (visitedMask[tmpOff])
1878                                {
1879                                    remain = 0;
1880                                    break;
1881                                }
1882
1883                                // set new position
1884                                x--;
1885                                y--;
1886                                offset = tmpOff;
1887                                // mark as visited
1888                                visitedMask[offset] = true;
1889                                // and add point
1890                                points.add(new Point(x, y));
1891                                // change next scan pos
1892                                scan = 3 - 1;
1893                                remain = 8;
1894                            }
1895                        }
1896                        break;
1897
1898                    case 6:
1899                        // not first Y
1900                        if (y > miny)
1901                        {
1902                            tmpOff = offset - w;
1903                            // contour ?
1904                            if (contourMask[tmpOff])
1905                            {
1906                                // contour already visited --> done
1907                                if (visitedMask[tmpOff])
1908                                {
1909                                    remain = 0;
1910                                    break;
1911                                }
1912
1913                                // set new position
1914                                y--;
1915                                offset = tmpOff;
1916                                // mark as visited
1917                                visitedMask[offset] = true;
1918                                // and add point
1919                                points.add(new Point(x, y));
1920                                // change next scan pos
1921                                scan = 5 - 1;
1922                                remain = 8;
1923                            }
1924                        }
1925                        break;
1926
1927                    case 7:
1928                        // not last X and not first Y
1929                        if ((x < maxx) && (y > miny))
1930                        {
1931                            tmpOff = (offset - w) + 1;
1932                            // contour ?
1933                            if (contourMask[tmpOff])
1934                            {
1935                                // contour already visited --> done
1936                                if (visitedMask[tmpOff])
1937                                {
1938                                    remain = 0;
1939                                    break;
1940                                }
1941
1942                                // set new position
1943                                x++;
1944                                y--;
1945                                offset = tmpOff;
1946                                // mark as visited
1947                                visitedMask[offset] = true;
1948                                // and add point
1949                                points.add(new Point(x, y));
1950                                // change next scan pos
1951                                scan = 5 - 1;
1952                                remain = 8;
1953                            }
1954                        }
1955                        break;
1956                }
1957
1958                scan = (scan + 1) & 7;
1959            }
1960
1961            allPoints.add(points);
1962            // get next start point
1963            startOffset = findStartPoint(startOffset, contourMask, visitedMask);
1964        }
1965
1966        List<Point> result = new ArrayList<Point>(allPoints.get(0));
1967        allPoints.remove(0);
1968
1969        // connect all found paths
1970        int i = 0;
1971        while (i < allPoints.size())
1972        {
1973            final List<Point> newResult = connect(result, allPoints.get(i));
1974
1975            if (newResult != null)
1976            {
1977                result = newResult;
1978                allPoints.remove(i);
1979                i = 0;
1980            }
1981            else
1982                i++;
1983        }
1984
1985        // try to insert remaining paths
1986        i = 0;
1987        while (i < allPoints.size())
1988        {
1989            final List<Point> newResult = insertPoints(result, allPoints.get(i));
1990
1991            if (newResult != null)
1992            {
1993                result = newResult;
1994                allPoints.remove(i);
1995                i = 0;
1996            }
1997            else
1998                i++;
1999        }
2000
2001        // debug
2002        // for(Point pt:result)
2003        // System.out.println(pt);
2004
2005        // some parts can't be connected --> display warning
2006        // if (!allPoints.isEmpty())
2007        // System.out.println("Warning: can't connect some points...");
2008
2009        return result;
2010    }
2011
2012    /**
2013     * Returns an array of {@link Point} containing the contour points of the mask.<br>
2014     * Points are returned in ascending XY order:
2015     * 
2016     * <pre>
2017     *  123 
2018     *  4 5
2019     *  6 7
2020     *   89
2021     * </pre>
2022     * 
2023     * @see #getContourPointsAsIntArray()
2024     */
2025    public Point[] getContourPoints()
2026    {
2027        return TypeUtil.toPoint(getContourPointsAsIntArray());
2028    }
2029
2030    /**
2031     * Returns an array of integer containing the contour points of the mask.<br>
2032     * <code>result.length</code> = number of point * 2<br>
2033     * <code>result[(pt * 2) + 0]</code> = X coordinate for point <i>pt</i>.<br>
2034     * <code>result[(pt * 2) + 1]</code> = Y coordinate for point <i>pt</i>.<br>
2035     * Points are returned in ascending XY order:
2036     * 
2037     * <pre>
2038     *  123 
2039     *  4 5
2040     *  6 7
2041     *   89
2042     * </pre>
2043     * 
2044     * @see #getConnectedContourPoints()
2045     */
2046    public int[] getContourPointsAsIntArray()
2047    {
2048        if (isEmpty())
2049            return new int[0];
2050
2051        final boolean[] mask;
2052        final Rectangle bounds;
2053
2054        synchronized (this)
2055        {
2056            mask = this.mask;
2057            bounds = this.bounds;
2058        }
2059
2060        final int[] points = new int[mask.length * 2];
2061        final int h = bounds.height;
2062        final int w = bounds.width;
2063        final int maxx = bounds.x + (w - 1);
2064        final int maxy = bounds.y + (h - 1);
2065
2066        // cache
2067        boolean top = false;
2068        boolean bottom = false;
2069        boolean left = false;
2070        boolean right = false;
2071        boolean current;
2072
2073        int offset = 0;
2074        int pt = 0;
2075
2076        // special case where width <= 2 or height <= 2 in which case all pixels count as border
2077        if ((w <= 2) || (h <= 2))
2078        {
2079            for (int y = bounds.y; y <= maxy; y++)
2080            {
2081                for (int x = bounds.x; x <= maxx; x++)
2082                {
2083                    // current pixel is a border ?
2084                    if (mask[offset++])
2085                    {
2086                        points[pt++] = x;
2087                        points[pt++] = y;
2088                    }
2089                }
2090            }
2091        }
2092        else
2093        {
2094            // first pixel of first line
2095            top = false;
2096            left = false;
2097            current = mask[offset];
2098            bottom = mask[offset + w];
2099            right = mask[++offset];
2100
2101            // current pixel is a border ?
2102            if (current && !(top && left && right && bottom))
2103            {
2104                points[pt++] = bounds.x;
2105                points[pt++] = bounds.y;
2106            }
2107
2108            // first line
2109            for (int x = bounds.x + 1; x < maxx; x++)
2110            {
2111                // cache
2112                left = current;
2113                current = right;
2114                bottom = mask[offset + w];
2115                right = mask[++offset];
2116
2117                // current pixel is a border ?
2118                if (current && !(top && left && right && bottom))
2119                {
2120                    points[pt++] = x;
2121                    points[pt++] = bounds.y;
2122                }
2123            }
2124
2125            // last pixel of first line
2126            left = current;
2127            current = right;
2128            bottom = mask[offset + w];
2129            right = false;
2130            offset++;
2131
2132            // current pixel is a border ?
2133            if (current && !(top && left && right && bottom))
2134            {
2135                points[pt++] = maxx;
2136                points[pt++] = bounds.y;
2137            }
2138
2139            for (int y = bounds.y + 1; y < maxy; y++)
2140            {
2141                // first pixel of line
2142                left = false;
2143                current = mask[offset];
2144                top = mask[offset - w];
2145                bottom = mask[offset + w];
2146                right = mask[++offset];
2147
2148                // current pixel is a border ?
2149                if (current && !(top && left && right && bottom))
2150                {
2151                    points[pt++] = bounds.x;
2152                    points[pt++] = y;
2153                }
2154
2155                for (int x = bounds.x + 1; x < maxx; x++)
2156                {
2157                    // cache
2158                    left = current;
2159                    current = right;
2160                    top = mask[offset - w];
2161                    bottom = mask[offset + w];
2162                    right = mask[++offset];
2163
2164                    // current pixel is a border ?
2165                    if (current && !(top && left && right && bottom))
2166                    {
2167                        points[pt++] = x;
2168                        points[pt++] = y;
2169                    }
2170                }
2171
2172                // last pixel of line
2173                left = current;
2174                current = right;
2175                top = mask[offset - w];
2176                bottom = mask[offset + w];
2177                right = false;
2178                offset++;
2179
2180                // current pixel is a border ?
2181                if (current && !(top && left && right && bottom))
2182                {
2183                    points[pt++] = maxx;
2184                    points[pt++] = y;
2185                }
2186            }
2187
2188            // first pixel of last line
2189            left = false;
2190            current = mask[offset];
2191            top = mask[offset - w];
2192            bottom = false;
2193            right = mask[++offset];
2194
2195            // current pixel is a border ?
2196            if (current && !(top && left && right && bottom))
2197            {
2198                points[pt++] = bounds.x;
2199                points[pt++] = maxy;
2200            }
2201
2202            // last line
2203            for (int x = bounds.x + 1; x < maxx; x++)
2204            {
2205                // cache
2206                left = current;
2207                current = right;
2208                top = mask[offset - w];
2209                right = mask[++offset];
2210
2211                // current pixel is a border ?
2212                if (current && !(top && left && right && bottom))
2213                {
2214                    points[pt++] = x;
2215                    points[pt++] = maxy;
2216                }
2217            }
2218
2219            // last pixel of last line
2220            left = current;
2221            current = right;
2222            top = mask[offset - w];
2223            right = false;
2224
2225            // current pixel is a border ?
2226            if (current && !(top && left && right && bottom))
2227            {
2228                points[pt++] = maxx;
2229                points[pt++] = maxy;
2230            }
2231        }
2232
2233        final int[] result = new int[pt];
2234
2235        System.arraycopy(points, 0, result, 0, pt);
2236
2237        return result;
2238    }
2239
2240    /**
2241     * @deprecated Use {@link #getContourPoints()} instead.
2242     */
2243    @Deprecated
2244    public Point[] getEdgePoints()
2245    {
2246        return TypeUtil.toPoint(getContourPointsAsIntArray());
2247    }
2248
2249    /**
2250     * Computes and returns the length of the contour.<br/>
2251     * This is different from the number of contour point as it takes care of approximating
2252     * correctly distance between each contour point.
2253     * 
2254     * @author Alexandre Dufour
2255     * @author Stephane Dallongeville
2256     * @return the length of the contour
2257     */
2258    public double getContourLength()
2259    {
2260        double perimeter = 0;
2261
2262        final int[] edge = getContourPointsAsIntArray();
2263        final int baseX = bounds.x;
2264        final int baseY = bounds.y;
2265        final int width = bounds.width;
2266        final int height = bounds.height;
2267
2268        // count the edges and corners in 2D/3D
2269        double sideEdges = 0, cornerEdges = 0;
2270
2271        for (int i = 0; i < edge.length; i += 2)
2272        {
2273            final int x = edge[i + 0] - baseX;
2274            final int y = edge[i + 1] - baseY;
2275            final int xy = x + (y * width);
2276
2277            final boolean topLeftConnected;
2278            final boolean topConnected;
2279            final boolean topRightConnected;
2280            final boolean bottomLeftConnected;
2281            final boolean bottomConnected;
2282            final boolean bottomRightConnected;
2283
2284            if (y != 0)
2285            {
2286                topLeftConnected = (x != 0) && mask[(xy - 1) - width];
2287                topConnected = mask[(xy + 0) - width];
2288                topRightConnected = (x != (width - 1)) && mask[(xy + 1) - width];
2289            }
2290            else
2291            {
2292                topLeftConnected = false;
2293                topConnected = false;
2294                topRightConnected = false;
2295            }
2296
2297            final boolean leftConnected = (x != 0) && mask[xy - 1];
2298            final boolean rightConnected = (x != (width - 1)) && mask[xy + 1];
2299
2300            if (y != (height - 1))
2301            {
2302                bottomLeftConnected = (x != 0) && mask[(xy - 1) + width];
2303                bottomConnected = mask[(xy + 0) + width];
2304                bottomRightConnected = (x != (width - 1)) && mask[(xy + 1) + width];
2305            }
2306            else
2307            {
2308                bottomLeftConnected = false;
2309                bottomConnected = false;
2310                bottomRightConnected = false;
2311            }
2312
2313            // count the connections
2314            int directConnection = 0;
2315            int diagConnection = 0;
2316
2317            if (topLeftConnected)
2318                diagConnection++;
2319            if (topConnected)
2320                directConnection++;
2321            if (topRightConnected)
2322                diagConnection++;
2323            if (leftConnected)
2324                directConnection++;
2325            if (rightConnected)
2326                directConnection++;
2327            if (bottomLeftConnected)
2328                diagConnection++;
2329            if (bottomConnected)
2330                directConnection++;
2331            if (bottomRightConnected)
2332                diagConnection++;
2333
2334            switch (directConnection)
2335            {
2336                case 0: // no direct connection
2337                    switch (diagConnection)
2338                    {
2339                        case 0: // isolated point
2340                            perimeter += Math.PI;
2341                            break;
2342
2343                        case 1: // ending point (diagonal)
2344                            cornerEdges++;
2345                            perimeter += Math.sqrt(2) + (Math.PI / 2);
2346                            break;
2347
2348                        default: // diagonal angle line
2349                            cornerEdges += 2;
2350                            perimeter += 2 * Math.sqrt(2);
2351                            break;
2352                    }
2353                    break;
2354
2355                case 1: // ending point
2356                    switch (diagConnection)
2357                    {
2358                        case 0: // ending point straight
2359                            sideEdges++;
2360                            perimeter += 1 + (Math.PI / 2);
2361                            break;
2362
2363                        default: // assume triangle with 45° angle
2364                            cornerEdges++;
2365                            sideEdges++;
2366                            perimeter += 1 + Math.sqrt(2);
2367                            break;
2368                    }
2369                    break;
2370
2371                case 2:
2372                    if ((leftConnected && rightConnected) || (topConnected && bottomConnected))
2373                    {
2374                        final double dgc = diagConnection * 0.5;
2375                        final double dtc = 2 - dgc;
2376
2377                        cornerEdges += dgc;
2378                        sideEdges += dtc;
2379                        perimeter += dtc + (dgc * Math.sqrt(2));
2380                    }
2381                    else
2382                    {
2383                        // consider 90° corner
2384                        cornerEdges++;
2385                        perimeter += Math.sqrt(2);
2386                    }
2387                    break;
2388
2389                case 3: // classic border (180°)
2390                    switch (diagConnection)
2391                    {
2392                        default: // classic border
2393                            sideEdges++;
2394                            perimeter++;
2395                            break;
2396
2397                        case 3:
2398                            // consider 225° interior corner
2399                            cornerEdges += 0.5;
2400                            sideEdges += 0.5;
2401                            perimeter += 0.5 + (Math.sqrt(2) / 2);
2402                            break;
2403
2404                        case 4: // hole inside contour
2405                            cornerEdges++;
2406                            perimeter += Math.sqrt(2);
2407                            break;
2408                    }
2409                    break;
2410
2411                case 4: // internal point --> should not happen
2412                    break;
2413            }
2414        }
2415
2416        // adjust the perimeter empirically according to the edge distribution
2417        double overShoot = Math.min(sideEdges / 10, cornerEdges);
2418
2419        return perimeter - overShoot;
2420    }
2421
2422    /**
2423     * Compute intersection with specified mask and return result in a new mask.
2424     * 
2425     * @see #intersect(BooleanMask2D)
2426     */
2427    public BooleanMask2D getIntersection(BooleanMask2D booleanMask)
2428    {
2429        return getIntersection(this, booleanMask);
2430    }
2431
2432    /**
2433     * Compute intersection with specified mask and return result in a new mask.
2434     * 
2435     * @see #intersect(Rectangle, boolean[])
2436     */
2437    public BooleanMask2D getIntersection(Rectangle bounds, boolean[] mask)
2438    {
2439        return getIntersection(this.bounds, this.mask, bounds, mask);
2440    }
2441
2442    /**
2443     * @deprecated Use {@link #getIntersection(BooleanMask2D)} instead.
2444     */
2445    @Deprecated
2446    public BooleanMask2D getIntersect(BooleanMask2D booleanMask)
2447    {
2448        return getIntersection(booleanMask);
2449    }
2450
2451    /**
2452     * @deprecated Use {@link #getIntersection(Rectangle, boolean[])} instead.
2453     */
2454    @Deprecated
2455    public BooleanMask2D getIntersect(Rectangle bounds, boolean[] mask)
2456    {
2457        return getIntersection(bounds, mask);
2458    }
2459
2460    /**
2461     * Compute union with specified mask and return result in a new mask.
2462     * 
2463     * @see #add(BooleanMask2D)
2464     */
2465    public BooleanMask2D getUnion(BooleanMask2D booleanMask)
2466    {
2467        return getUnion(this, booleanMask);
2468    }
2469
2470    /**
2471     * Compute union with specified mask and return result in a new mask.
2472     * 
2473     * @see #add(Rectangle, boolean[])
2474     */
2475    public BooleanMask2D getUnion(Rectangle bounds, boolean[] mask)
2476    {
2477        return getUnion(this.bounds, this.mask, bounds, mask);
2478    }
2479
2480    /**
2481     * Compute exclusive union operation with specified mask and return result in a new mask.
2482     * 
2483     * @see #exclusiveAdd(BooleanMask2D)
2484     */
2485    public BooleanMask2D getExclusiveUnion(BooleanMask2D booleanMask)
2486    {
2487        return getExclusiveUnion(this, booleanMask);
2488    }
2489
2490    /**
2491     * Compute exclusive union operation with specified mask and return result in a new mask
2492     * 
2493     * @see #exclusiveAdd(Rectangle, boolean[])
2494     */
2495    public BooleanMask2D getExclusiveUnion(Rectangle bounds, boolean[] mask)
2496    {
2497        return getExclusiveUnion(this.bounds, this.mask, bounds, mask);
2498    }
2499
2500    /**
2501     * Subtract the specified mask from current and return result in a new mask.
2502     */
2503    public BooleanMask2D getSubtraction(BooleanMask2D mask)
2504    {
2505        return getSubtraction(this, mask);
2506    }
2507
2508    /**
2509     * Subtract the specified mask from current and return result in a new mask.
2510     */
2511    public BooleanMask2D getSubtraction(Rectangle bounds, boolean[] mask)
2512    {
2513        return getSubtraction(this.bounds, this.mask, bounds, mask);
2514    }
2515
2516    /**
2517     * @deprecated Use {@link #getExclusiveUnion(BooleanMask2D)} instead.
2518     */
2519    @Deprecated
2520    public BooleanMask2D getXor(BooleanMask2D booleanMask)
2521    {
2522        return getExclusiveUnion(booleanMask);
2523    }
2524
2525    /**
2526     * @deprecated Use {@link #getExclusiveUnion(Rectangle, boolean[])} instead.
2527     */
2528    @Deprecated
2529    public BooleanMask2D getXor(Rectangle bounds, boolean[] mask)
2530    {
2531        return getExclusiveUnion(bounds, mask);
2532    }
2533
2534    /**
2535     * Add the specified mask into the current mask (bounds can be enlarged):
2536     * 
2537     * <pre>
2538     *       current mask    +         mask       =       result
2539     * 
2540     *     ################     ################     ################
2541     *     ##############         ##############     ################
2542     *     ############             ############     ################
2543     *     ##########                 ##########     ################
2544     *     ########                     ########     ################
2545     *     ######                         ######     ######    ######
2546     *     ####                             ####     ####        ####
2547     *     ##                                 ##     ##            ##
2548     * </pre>
2549     */
2550    public void add(BooleanMask2D booleanMask)
2551    {
2552        add(booleanMask.bounds, booleanMask.mask);
2553    }
2554
2555    /**
2556     * Add the specified mask into the current mask (bounds can be enlarged):
2557     * 
2558     * <pre>
2559     *       current mask    +         mask       =       result
2560     * 
2561     *     ################     ################     ################
2562     *     ##############         ##############     ################
2563     *     ############             ############     ################
2564     *     ##########                 ##########     ################
2565     *     ########                     ########     ################
2566     *     ######                         ######     ######    ######
2567     *     ####                             ####     ####        ####
2568     *     ##                                 ##     ##            ##
2569     * </pre>
2570     */
2571    public void add(Rectangle boundsToAdd, boolean[] maskToAdd)
2572    {
2573        // don't need to reallocate the mask
2574        if (bounds.contains(boundsToAdd))
2575        {
2576            int offDst, offSrc;
2577
2578            // calculate offset
2579            offDst = ((boundsToAdd.y - bounds.y) * bounds.width) + (boundsToAdd.x - bounds.x);
2580            offSrc = 0;
2581
2582            for (int y = 0; y < boundsToAdd.height; y++)
2583            {
2584                for (int x = 0; x < boundsToAdd.width; x++)
2585                    if (maskToAdd[offSrc++])
2586                        mask[offDst + x] = true;
2587
2588                offDst += bounds.width;
2589            }
2590        }
2591        else
2592        {
2593            // create a new mask
2594            final BooleanMask2D result = getUnion(boundsToAdd, maskToAdd);
2595
2596            // update bounds and mask
2597            synchronized (this)
2598            {
2599                this.bounds = result.bounds;
2600                this.mask = result.mask;
2601            }
2602        }
2603    }
2604
2605    /**
2606     * Set the content of current mask with the result of the intersection with the specified mask:
2607     * 
2608     * <pre>
2609     *     current mask  intersect     newMask       =       result
2610     * 
2611     *     ################     ################     ################
2612     *     ##############         ##############       ############
2613     *     ############             ############         ########
2614     *     ##########                 ##########           ####
2615     *     ########                     ########
2616     *     ######                         ######
2617     *     ####                             ####
2618     *     ##                                 ##
2619     * </pre>
2620     */
2621    public void intersect(BooleanMask2D booleanMask)
2622    {
2623        intersect(booleanMask.bounds, booleanMask.mask);
2624    }
2625
2626    /**
2627     * Set the content of current mask with the result of the intersection with the specified mask:
2628     * 
2629     * <pre>
2630     *     current mask  intersect     newMask       =       result
2631     * 
2632     *     ################     ################     ################
2633     *     ##############         ##############       ############
2634     *     ############             ############         ########
2635     *     ##########                 ##########           ####
2636     *     ########                     ########
2637     *     ######                         ######
2638     *     ####                             ####
2639     *     ##                                 ##
2640     * </pre>
2641     */
2642    public void intersect(Rectangle boundsToIntersect, boolean[] maskToIntersect)
2643    {
2644        // faster to always create a new mask
2645        final BooleanMask2D result = getIntersection(boundsToIntersect, maskToIntersect);
2646
2647        synchronized (this)
2648        {
2649            this.bounds = result.bounds;
2650            this.mask = result.mask;
2651        }
2652    }
2653
2654    /**
2655     * Exclusively add the specified mask into the current mask (bounds can change):
2656     * 
2657     * <pre>
2658     *          mask1       xor      mask2        =       result
2659     * 
2660     *     ################     ################
2661     *     ##############         ##############     ##            ##
2662     *     ############             ############     ####        ####
2663     *     ##########                 ##########     ######    ######
2664     *     ########                     ########     ################
2665     *     ######                         ######     ######    ######
2666     *     ####                             ####     ####        ####
2667     *     ##                                 ##     ##            ##
2668     * </pre>
2669     */
2670    public void exclusiveAdd(BooleanMask2D booleanMask)
2671    {
2672        exclusiveAdd(booleanMask.bounds, booleanMask.mask);
2673    }
2674
2675    /**
2676     * Exclusively add the specified mask into the current mask (bounds can change):
2677     * 
2678     * <pre>
2679     *          mask1       xor      mask2        =       result
2680     * 
2681     *     ################     ################
2682     *     ##############         ##############     ##            ##
2683     *     ############             ############     ####        ####
2684     *     ##########                 ##########     ######    ######
2685     *     ########                     ########     ################
2686     *     ######                         ######     ######    ######
2687     *     ####                             ####     ####        ####
2688     *     ##                                 ##     ##            ##
2689     * </pre>
2690     */
2691    public void exclusiveAdd(Rectangle boundsToXAdd, boolean[] maskToXAdd)
2692    {
2693        // don't need to reallocate the mask
2694        if (bounds.contains(boundsToXAdd))
2695        {
2696            int offDst, offSrc;
2697
2698            // calculate offset
2699            offDst = ((boundsToXAdd.y - bounds.y) * bounds.width) + (boundsToXAdd.x - bounds.x);
2700            offSrc = 0;
2701
2702            for (int y = 0; y < boundsToXAdd.height; y++)
2703            {
2704                for (int x = 0; x < boundsToXAdd.width; x++)
2705                    if (maskToXAdd[offSrc++])
2706                        mask[offDst + x] = !mask[offDst + x];
2707
2708                offDst += bounds.width;
2709            }
2710
2711            // bounds may have changed
2712            optimizeBounds();
2713        }
2714        else
2715        {
2716            // create a new mask
2717            final BooleanMask2D result = getExclusiveUnion(boundsToXAdd, maskToXAdd);
2718
2719            // update bounds and mask
2720            synchronized (this)
2721            {
2722                this.bounds = result.bounds;
2723                this.mask = result.mask;
2724            }
2725        }
2726    }
2727
2728    /**
2729     * Subtract the specified mask from the current mask (bounds can change):
2730     * 
2731     * <pre>
2732     *       current mask    -        mask        =  result
2733     * 
2734     *     ################     ################
2735     *     ##############         ##############     ##
2736     *     ############             ############     ####
2737     *     ##########                 ##########     ######
2738     *     ########                     ########     ########
2739     *     ######                         ######     ######
2740     *     ####                             ####     ####
2741     *     ##                                 ##     ##
2742     * </pre>
2743     */
2744    public void subtract(Rectangle boundsToSubtract, boolean[] maskToSubtract)
2745    {
2746        // compute intersection
2747        final Rectangle intersection = bounds.intersection(boundsToSubtract);
2748
2749        // need to subtract something ?
2750        if (!intersection.isEmpty())
2751        {
2752            // calculate offset
2753            int offDst = ((intersection.y - bounds.y) * bounds.width) + (intersection.x - bounds.x);
2754            int offSrc = ((intersection.y - boundsToSubtract.y) * boundsToSubtract.width)
2755                    + (intersection.x - boundsToSubtract.x);
2756
2757            for (int y = 0; y < intersection.height; y++)
2758            {
2759                for (int x = 0; x < intersection.width; x++)
2760                    if (maskToSubtract[offSrc + x])
2761                        mask[offDst + x] = false;
2762
2763                offDst += bounds.width;
2764                offSrc += boundsToSubtract.width;
2765            }
2766
2767            // optimize bounds
2768            optimizeBounds();
2769        }
2770    }
2771
2772    /**
2773     * @deprecated Use {@link #add(BooleanMask2D)} instead.
2774     */
2775    @Deprecated
2776    public void union(BooleanMask2D booleanMask)
2777    {
2778        add(booleanMask.bounds, booleanMask.mask);
2779    }
2780
2781    /**
2782     * @deprecated Use {@link #add(BooleanMask2D)} instead.
2783     */
2784    @Deprecated
2785    public void union(Rectangle bounds, boolean[] mask)
2786    {
2787        add(bounds, mask);
2788    }
2789
2790    /**
2791     * @deprecated Use {@link #getExclusiveUnion(BooleanMask2D)} instead.
2792     */
2793    @Deprecated
2794    public void exclusiveUnion(BooleanMask2D booleanMask)
2795    {
2796        exclusiveAdd(booleanMask.bounds, booleanMask.mask);
2797    }
2798
2799    /**
2800     * @deprecated Use {@link #getExclusiveUnion(Rectangle, boolean[])} instead.
2801     */
2802    @Deprecated
2803    public void exclusiveUnion(Rectangle bounds, boolean[] mask)
2804    {
2805        exclusiveAdd(bounds, mask);
2806    }
2807
2808    /**
2809     * @deprecated Use {@link #exclusiveUnion(BooleanMask2D)} instead
2810     */
2811    @Deprecated
2812    public void xor(BooleanMask2D booleanMask)
2813    {
2814        exclusiveAdd(booleanMask);
2815    }
2816
2817    /**
2818     * @deprecated Use {@link #exclusiveUnion(Rectangle, boolean[])} instead
2819     */
2820    @Deprecated
2821    public void xor(Rectangle bounds, boolean[] mask)
2822    {
2823        exclusiveAdd(bounds, mask);
2824    }
2825
2826    /**
2827     * Get the smallest bounds which fit mask content.
2828     */
2829    public Rectangle getOptimizedBounds()
2830    {
2831        // find best bounds
2832        final int sizeX = bounds.width;
2833        final int sizeY = bounds.height;
2834
2835        int minX = sizeX;
2836        int minY = sizeY;
2837        int maxX = -1;
2838        int maxY = -1;
2839        int offset = 0;
2840        for (int y = 0; y < sizeY; y++)
2841        {
2842            for (int x = 0; x < sizeX; x++)
2843            {
2844                if (mask[offset++])
2845                {
2846                    if (x < minX)
2847                        minX = x;
2848                    if (x > maxX)
2849                        maxX = x;
2850                    if (y < minY)
2851                        minY = y;
2852                    if (y > maxY)
2853                        maxY = y;
2854                }
2855            }
2856        }
2857
2858        // empty --> return empty bounds
2859        if (minX == sizeX)
2860            return new Rectangle(bounds.x, bounds.y, 0, 0);
2861
2862        // new calculated bounds
2863        return new Rectangle(bounds.x + minX, bounds.y + minY, (maxX - minX) + 1, (maxY - minY) + 1);
2864    }
2865
2866    /**
2867     * Optimize mask bounds so it fit mask content.
2868     */
2869    public void optimizeBounds()
2870    {
2871        moveBounds(getOptimizedBounds());
2872    }
2873
2874    /**
2875     * @deprecated Use {@link #moveBounds(Rectangle)} instead.
2876     */
2877    @Deprecated
2878    public void setBounds(Rectangle value)
2879    {
2880        moveBounds(value);
2881    }
2882
2883    /**
2884     * Change the bounds of BooleanMask.<br>
2885     * Keep mask data intersecting from old bounds.
2886     */
2887    public void moveBounds(Rectangle value)
2888    {
2889        // bounds changed ?
2890        if (!bounds.equals(value))
2891        {
2892            // copy bounds as we modify them
2893            final Rectangle oldBounds = new Rectangle(bounds);
2894            final Rectangle newBounds = new Rectangle(value);
2895
2896            // replace to origin (relative to old bounds)
2897            oldBounds.translate(-bounds.x, -bounds.y);
2898            newBounds.translate(-bounds.x, -bounds.y);
2899
2900            final boolean[] newMask = new boolean[Math.max(0, newBounds.width) * Math.max(0, newBounds.height)];
2901            final Rectangle intersect = newBounds.intersection(oldBounds);
2902
2903            if (!intersect.isEmpty())
2904            {
2905                int offSrc = 0;
2906                int offDst = 0;
2907
2908                // adjust offset in source mask
2909                if (intersect.x > 0)
2910                    offSrc += intersect.x;
2911                if (intersect.y > 0)
2912                    offSrc += intersect.y * oldBounds.width;
2913                // adjust offset in destination mask
2914                if (newBounds.x < 0)
2915                    offDst += -newBounds.x;
2916                if (newBounds.y < 0)
2917                    offDst += -newBounds.y * newBounds.width;
2918
2919                // preserve data
2920                for (int j = 0; j < intersect.height; j++)
2921                {
2922                    System.arraycopy(mask, offSrc, newMask, offDst, intersect.width);
2923
2924                    offSrc += oldBounds.width;
2925                    offDst += newBounds.width;
2926                }
2927            }
2928
2929            // update mask and bounds
2930            synchronized (this)
2931            {
2932                mask = newMask;
2933                bounds = value;
2934            }
2935        }
2936    }
2937
2938    /**
2939     * Fast 2x up scaling (each point become 2x2 bloc point).<br>
2940     * This method create a new boolean mask.
2941     */
2942    public BooleanMask2D upscale()
2943    {
2944        return upscale(this);
2945    }
2946
2947    /**
2948     * Fast 2x down scaling (each 2x2 block points become 1 point).<br>
2949     * This method create a new boolean mask.
2950     * 
2951     * @param nbPointForTrue
2952     *        the minimum number of <code>true</code>points from a 2x2 block to give a <code>true</code> resulting
2953     *        point.<br>
2954     *        Accepted value: 1-4 (default is 3)
2955     */
2956    public BooleanMask2D downscale(int nbPointForTrue)
2957    {
2958        return downscale(this, nbPointForTrue);
2959    }
2960
2961    /**
2962     * Fast 2x down scaling (each 2x2 block points become 1 point).<br>
2963     * This method create a new boolean mask.
2964     */
2965    public BooleanMask2D downscale()
2966    {
2967        return downscale(this);
2968    }
2969
2970    @Override
2971    public Object clone()
2972    {
2973        return new BooleanMask2D((Rectangle) bounds.clone(), mask.clone());
2974    }
2975
2976}