/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.roi;

import icy.type.TypeUtil;
import icy.type.collection.array.DynamicArray;
import icy.type.point.Point2DUtil;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Class to define a 2D boolean mask region and make basic boolean operation between masks.<br>
 * The bounds property of this object represents the region defined by the boolean mask.
 * 
 * @author Stephane
 */
public class BooleanMask2D implements Cloneable
{
    private static class Component
    {
        public DynamicArray.Int points;
        public List<Component> children;
        private Component root;

        public Component()
        {
            points = new DynamicArray.Int(0);
            children = new ArrayList<Component>();
            root = this;
        }

        public void addPoint(int x, int y)
        {
            points.addSingle(x);
            points.addSingle(y);
        }

        public void addComponent(Component c)
        {
            final Component croot = c.root;

            if (croot != root)
            {
                children.add(croot);
                croot.setRoot(root);
            }
        }

        public boolean isRoot()
        {
            return root == this;
        }

        private void setRoot(Component value)
        {
            root = value;

            final int size = children.size();
            for (int c = 0; c < size; c++)
                children.get(c).setRoot(value);
        }

        public int getTotalSize()
        {
            int result = points.getSize();

            final int size = children.size();
            for (int c = 0; c < size; c++)
                result += children.get(c).getTotalSize();

            return result;
        }

        public int[] getAllPoints()
        {
            final int[] result = new int[getTotalSize()];

            // get all point
            getAllPoints(result, 0);

            return result;
        }

        private int getAllPoints(int[] result, int offset)
        {
            final int[] intPoints = points.asArray();

            System.arraycopy(intPoints, 0, result, offset, intPoints.length);

            int off = offset + intPoints.length;
            final int csize = children.size();
            for (int c = 0; c < csize; c++)
                off = children.get(c).getAllPoints(result, off);

            return off;
        }
    }

    // find first non visited contour point
    private static int findStartPoint(int startOffset, boolean mask[], boolean visitedMask[])
    {
        for (int i = startOffset; i < mask.length; i++)
            if (mask[i] && !visitedMask[i])
                return i;

        return -1;
    }

    private static List<Point> insertPoints(List<Point> result, List<Point> source)
    {
        if (result.isEmpty())
        {
            result.addAll(source);
            return result;
        }
        // nothing to insert
        if (source.isEmpty())
            return result;

        final Point firstPointSource = source.get(0);
        final Point lastPointSource = source.get(source.size() - 1);
        final int len = result.size();

        for (int i = 0; i < len; i++)
        {
            final Point p = result.get(i);

            if (Point2DUtil.areConnected(p, firstPointSource))
            {
                final List<Point> newResult = new ArrayList<Point>(result.subList(0, i + 1));
                newResult.addAll(source);
                if ((i + 1) < len)
                    newResult.addAll(new ArrayList<Point>(result.subList(i + 1, len)));
                return newResult;
            }
            if (Point2DUtil.areConnected(p, lastPointSource))
            {
                final List<Point> newResult = new ArrayList<Point>(result.subList(0, i + 1));
                Collections.reverse(source);
                newResult.addAll(source);
                if ((i + 1) < len)
                    newResult.addAll(new ArrayList<Point>(result.subList(i + 1, len)));
                return newResult;
            }
        }

        return null;
    }

    private static List<Point> connect(List<Point> result, List<Point> source)
    {
        if (result.isEmpty())
        {
            result.addAll(source);
            return result;
        }
        // nothing to connect
        if (source.isEmpty())
            return result;

        final Point2D firstPointResult = result.get(0);
        final Point2D lastPointResult = result.get(result.size() - 1);
        final Point2D firstPointSource = source.get(0);
        final Point2D lastPointSource = source.get(source.size() - 1);

        // res-tail - src-head connection --> res = res + src
        if (Point2DUtil.areConnected(firstPointSource, lastPointResult))
        {
            result.addAll(source);
            return result;
        }
        // res-head - src-head connection --> res = reverse(src) + res
        if (Point2DUtil.areConnected(firstPointSource, firstPointResult))
        {
            Collections.reverse(source);
            source.addAll(result);
            return source;
        }
        // res-head - src-tail connection --> res = src + res
        if (Point2DUtil.areConnected(lastPointSource, firstPointResult))
        {
            source.addAll(result);
            return source;
        }
        // res-tail - src-tail connection --> res = res + reverse(src)
        if (Point2DUtil.areConnected(lastPointSource, lastPointResult))
        {
            Collections.reverse(source);
            result.addAll(source);
            return result;
        }

        // can't connect
        return null;
    }

    /**
     * Return a list of Point representing the contour points of the mask.<br>
     * Points are returned in ascending XY order:
     * 
     * <pre>
     *  1234 
     *  5  6
     *  7 8
     *   9
     * </pre>
     */
    public static List<Point> getContourPoints(Rectangle bounds, boolean mask[])
    {
        if (bounds.isEmpty())
            return new ArrayList<Point>(1);

        final List<Point> points = new ArrayList<Point>(mask.length / 16);
        final int h = bounds.height;
        final int w = bounds.width;
        final int minx = bounds.x;
        final int miny = bounds.y;
        final int maxx = minx + (w - 1);
        final int maxy = miny + (h - 1);

        // cache
        boolean top = false;
        boolean bottom = false;
        boolean left = false;
        boolean right = false;
        boolean current;

        int offset = 0;

        // special case of single pixel mask
        if ((w == 1) && (h == 1))
        {
            if (mask[0])
                points.add(new Point(minx, miny));
        }
        // special case of single pixel width mask
        else if (w == 1)
        {
            // first pixel of row
            top = false;
            current = mask[offset];
            bottom = mask[++offset];

            // current pixel is a border ?
            if (current && !(top && bottom))
                points.add(new Point(minx, miny));

            // row
            for (int y = miny + 1; y < maxy; y++)
            {
                // cache
                top = current;
                current = bottom;
                bottom = mask[++offset];

                // current pixel is a border ?
                if (current && !(top && bottom))
                    points.add(new Point(minx, y));
            }

            // cache
            top = current;
            current = bottom;
            bottom = false;

            // current pixel is a border ?
            if (current && !(top && bottom))
                points.add(new Point(minx, maxy));
        }
        // special case of single pixel height mask
        else if (h == 1)
        {
            // first pixel of line
            left = false;
            current = mask[offset];
            right = mask[++offset];

            // current pixel is a border ?
            if (current && !(left && right))
                points.add(new Point(minx, miny));

            // line
            for (int x = minx + 1; x < maxx; x++)
            {
                // cache
                left = current;
                current = right;
                right = mask[++offset];

                // current pixel is a border ?
                if (current && !(left && right))
                    points.add(new Point(x, miny));
            }

            // last pixel of first line
            left = current;
            current = right;
            right = false;

            // current pixel is a border ?
            if (current && !(left && right))
                points.add(new Point(maxx, miny));
        }
        // normal case
        else
        {
            // first pixel of first line
            top = false;
            left = false;
            current = mask[offset];
            bottom = mask[offset + w];
            right = mask[++offset];

            // current pixel is a border ?
            if (current && !(top && left && right && bottom))
                points.add(new Point(minx, miny));

            // first line
            for (int x = minx + 1; x < maxx; x++)
            {
                // cache
                left = current;
                current = right;
                bottom = mask[offset + w];
                right = mask[++offset];

                // current pixel is a border ?
                if (current && !(top && left && right && bottom))
                    points.add(new Point(x, miny));
            }

            // last pixel of first line
            left = current;
            current = right;
            bottom = mask[offset + w];
            right = false;
            offset++;

            // current pixel is a border ?
            if (current && !(top && left && right && bottom))
                points.add(new Point(maxx, miny));

            for (int y = miny + 1; y < maxy; y++)
            {
                // first pixel of line
                left = false;
                current = mask[offset];
                top = mask[offset - w];
                bottom = mask[offset + w];
                right = mask[++offset];

                // current pixel is a border ?
                if (current && !(top && left && right && bottom))
                    points.add(new Point(minx, y));

                for (int x = minx + 1; x < maxx; x++)
                {
                    // cache
                    left = current;
                    current = right;
                    top = mask[offset - w];
                    bottom = mask[offset + w];
                    right = mask[++offset];

                    // current pixel is a border ?
                    if (current && !(top && left && right && bottom))
                        points.add(new Point(x, y));
                }

                // last pixel of line
                left = current;
                current = right;
                top = mask[offset - w];
                bottom = mask[offset + w];
                right = false;
                offset++;

                // current pixel is a border ?
                if (current && !(top && left && right && bottom))
                    points.add(new Point(maxx, y));
            }

            // first pixel of last line
            left = false;
            current = mask[offset];
            top = mask[offset - w];
            bottom = false;
            right = mask[++offset];

            // current pixel is a border ?
            if (current && !(top && left && right && bottom))
                points.add(new Point(minx, maxy));

            // last line
            for (int x = minx + 1; x < maxx; x++)
            {
                // cache
                left = current;
                current = right;
                top = mask[offset - w];
                right = mask[++offset];

                // current pixel is a border ?
                if (current && !(top && left && right && bottom))
                    points.add(new Point(x, maxy));
            }

            // last pixel of last line
            left = current;
            current = right;
            top = mask[offset - w];
            right = false;

            // current pixel is a border ?
            if (current && !(top && left && right && bottom))
                points.add(new Point(maxx, maxy));
        }

        return points;
    }

    /**
     * Build global boolean mask from union of all specified mask
     */
    public static BooleanMask2D getUnion(List<BooleanMask2D> masks)
    {
        BooleanMask2D result = null;

        // compute global union boolean mask of all ROI2D
        for (BooleanMask2D bm : masks)
        {
            // update global mask
            if (result == null)
                result = new BooleanMask2D(bm.bounds, bm.mask);
            else
                result = getUnion(result, bm);
        }

        // return an empty BooleanMask2D instead of null
        if (result == null)
            result = new BooleanMask2D();

        return result;
    }

    /**
     * Build resulting mask from union of the mask1 and mask2.<br>
     * If <code>mask1</code> is <code>null</code> then a copy of <code>mask2</code> is returned.<br>
     * If <code>mask2</code> is <code>null</code> then a copy of <code>mask1</code> is returned.<br>
     * An empty mask is returned if both <code>mask1</code> and <code>mask2</code> are <code>null</code>.
     */
    public static BooleanMask2D getUnion(BooleanMask2D mask1, BooleanMask2D mask2)
    {
        if ((mask1 == null) && (mask2 == null))
            return new BooleanMask2D();

        if ((mask1 == null) || mask1.isEmpty())
            return (BooleanMask2D) mask2.clone();
        if ((mask2 == null) || mask2.isEmpty())
            return (BooleanMask2D) mask1.clone();

        return getUnion(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
    }

    /**
     * Build resulting mask from union of the mask1 and mask2:
     * 
     * <pre>
     *        mask1          +       mask2        =      result
     * 
     *     ################     ################     ################
     *     ##############         ##############     ################
     *     ############             ############     ################
     *     ##########                 ##########     ################
     *     ########                     ########     ################
     *     ######                         ######     ######    ######
     *     ####                             ####     ####        ####
     *     ##                                 ##     ##            ##
     * </pre>
     */
    public static BooleanMask2D getUnion(Rectangle bounds1, boolean[] mask1, Rectangle bounds2, boolean[] mask2)
    {
        final Rectangle union = bounds1.union(bounds2);

        if (!union.isEmpty())
        {
            final boolean[] mask = new boolean[union.width * union.height];
            int offDst, offSrc;

            // calculate offset
            offDst = ((bounds1.y - union.y) * union.width) + (bounds1.x - union.x);
            offSrc = 0;

            for (int y = 0; y < bounds1.height; y++)
            {
                for (int x = 0; x < bounds1.width; x++)
                    mask[offDst + x] |= mask1[offSrc++];

                offDst += union.width;
            }

            // calculate offset
            offDst = ((bounds2.y - union.y) * union.width) + (bounds2.x - union.x);
            offSrc = 0;

            for (int y = 0; y < bounds2.height; y++)
            {
                for (int x = 0; x < bounds2.width; x++)
                    mask[offDst + x] |= mask2[offSrc++];

                offDst += union.width;
            }

            return new BooleanMask2D(union, mask);
        }

        return new BooleanMask2D();
    }

    /**
     * Build global boolean mask from intersection of all specified mask
     */
    public static BooleanMask2D getIntersection(List<BooleanMask2D> masks)
    {
        BooleanMask2D result = null;

        // compute global intersect boolean mask of all ROI2D
        for (BooleanMask2D bm : masks)
        {
            // update global mask
            if (result == null)
                result = new BooleanMask2D(bm.bounds, bm.mask);
            else
                result = getIntersection(result, bm);
        }

        // return an empty BooleanMask2D instead of null
        if (result == null)
            result = new BooleanMask2D();

        return result;
    }

    /**
     * Build resulting mask from intersection of the mask1 and mask2.<br>
     * An empty mask is returned if <code>mask1</code> or <code>mask2</code> is <code>null</code>.
     */
    public static BooleanMask2D getIntersection(BooleanMask2D mask1, BooleanMask2D mask2)
    {
        if ((mask1 == null) || (mask2 == null))
            return new BooleanMask2D();

        return getIntersection(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
    }

    /**
     * Build resulting mask from intersection of the mask1 and mask2:
     * 
     * <pre>
     *        mask1     intersect     mask2      =        result
     * 
     *     ################     ################     ################
     *     ##############         ##############       ############
     *     ############             ############         ########
     *     ##########                 ##########           ####
     *     ########                     ########
     *     ######                         ######
     *     ####                             ####
     *     ##                                 ##
     * </pre>
     */
    public static BooleanMask2D getIntersection(Rectangle bounds1, boolean[] mask1, Rectangle bounds2, boolean[] mask2)
    {
        final Rectangle intersect = bounds1.intersection(bounds2);

        if (!intersect.isEmpty())
        {
            final boolean[] mask = new boolean[intersect.width * intersect.height];

            // calculate offsets
            int off1 = ((intersect.y - bounds1.y) * bounds1.width) + (intersect.x - bounds1.x);
            int off2 = ((intersect.y - bounds2.y) * bounds2.width) + (intersect.x - bounds2.x);
            int off = 0;

            for (int y = 0; y < intersect.height; y++)
            {
                for (int x = 0; x < intersect.width; x++)
                    mask[off++] = mask1[off1 + x] & mask2[off2 + x];

                off1 += bounds1.width;
                off2 += bounds2.width;
            }

            return new BooleanMask2D(intersect, mask);
        }

        return new BooleanMask2D();
    }

    /**
     * Build global boolean mask from exclusive union of all specified mask
     */
    public static BooleanMask2D getExclusiveUnion(List<BooleanMask2D> masks)
    {
        BooleanMask2D result = null;

        // compute global exclusive union boolean mask of all ROI2D
        for (BooleanMask2D bm : masks)
        {
            // update global mask
            if (result == null)
                result = new BooleanMask2D(bm.bounds, bm.mask);
            else
                result = getExclusiveUnion(result, bm);
        }

        // return an empty BooleanMask2D instead of null
        if (result == null)
            result = new BooleanMask2D();

        return result;
    }

    /**
     * Build resulting mask from exclusive union of the mask1 and mask2.<br>
     * If <code>mask1</code> is <code>null</code> then a copy of <code>mask2</code> is returned.<br>
     * If <code>mask2</code> is <code>null</code> then a copy of <code>mask1</code> is returned.<br>
     * <code>null</code> is returned if both <code>mask1</code> and <code>mask2</code> are <code>null</code>.
     */
    public static BooleanMask2D getExclusiveUnion(BooleanMask2D mask1, BooleanMask2D mask2)
    {
        if ((mask1 == null) && (mask2 == null))
            return new BooleanMask2D();

        if ((mask1 == null) || mask1.isEmpty())
            return (BooleanMask2D) mask2.clone();
        if ((mask2 == null) || mask2.isEmpty())
            return (BooleanMask2D) mask1.clone();

        return getExclusiveUnion(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
    }

    /**
     * Build resulting mask from exclusive union of the mask1 and mask2:
     * 
     * <pre>
     *          mask1       xor      mask2        =       result
     * 
     *     ################     ################
     *     ##############         ##############     ##            ##
     *     ############             ############     ####        ####
     *     ##########                 ##########     ######    ######
     *     ########                     ########     ################
     *     ######                         ######     ######    ######
     *     ####                             ####     ####        ####
     *     ##                                 ##     ##            ##
     * </pre>
     */
    public static BooleanMask2D getExclusiveUnion(Rectangle bounds1, boolean[] mask1, Rectangle bounds2, boolean[] mask2)
    {
        final Rectangle union = bounds1.union(bounds2);

        if (!union.isEmpty())
        {
            final boolean[] mask = new boolean[union.width * union.height];
            int offDst, offSrc;

            // calculate offset
            offDst = ((bounds1.y - union.y) * union.width) + (bounds1.x - union.x);
            offSrc = 0;

            for (int y = 0; y < bounds1.height; y++)
            {
                for (int x = 0; x < bounds1.width; x++)
                    mask[offDst + x] ^= mask1[offSrc++];

                offDst += union.width;
            }

            // calculate offset
            offDst = ((bounds2.y - union.y) * union.width) + (bounds2.x - union.x);
            offSrc = 0;

            for (int y = 0; y < bounds2.height; y++)
            {
                for (int x = 0; x < bounds2.width; x++)
                    mask[offDst + x] ^= mask2[offSrc++];

                offDst += union.width;
            }

            final BooleanMask2D result = new BooleanMask2D(union, mask);

            // optimize bounds
            result.optimizeBounds();

            return result;
        }

        return new BooleanMask2D();
    }

    /**
     * Build resulting mask from the subtraction of mask2 from mask1.<br>
     * If <code>mask2</code> is <code>null</code> then a copy of <code>mask1</code> is returned.<br>
     * If <code>mask1</code> is <code>null</code> then a empty mask is returned.
     */
    public static BooleanMask2D getSubtraction(BooleanMask2D mask1, BooleanMask2D mask2)
    {
        if (mask1 == null)
            return new BooleanMask2D();
        if (mask2 == null)
            return (BooleanMask2D) mask1.clone();

        return getSubtraction(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
    }

    /**
     * Build resulting mask from the subtraction of mask2 from mask1:
     * 
     * <pre>
     *        mask1          -        mask2       =  result
     * 
     *     ################     ################
     *     ##############         ##############     ##
     *     ############             ############     ####
     *     ##########                 ##########     ######
     *     ########                     ########     ########
     *     ######                         ######     ######
     *     ####                             ####     ####
     *     ##                                 ##     ##
     * </pre>
     */
    public static BooleanMask2D getSubtraction(Rectangle bounds1, boolean[] mask1, Rectangle bounds2, boolean[] mask2)
    {
        final boolean[] mask = mask1.clone();
        final Rectangle subtract = new Rectangle(bounds1);
        final BooleanMask2D result = new BooleanMask2D(subtract, mask);

        // compute intersection
        final Rectangle intersection = bounds1.intersection(bounds2);

        // need to subtract something ?
        if (!intersection.isEmpty())
        {
            // calculate offset
            int offDst = ((intersection.y - subtract.y) * subtract.width) + (intersection.x - subtract.x);
            int offSrc = ((intersection.y - bounds2.y) * bounds2.width) + (intersection.x - bounds2.x);

            for (int y = 0; y < intersection.height; y++)
            {
                for (int x = 0; x < intersection.width; x++)
                    mask[offDst + x] &= !mask2[offSrc + x];

                offDst += subtract.width;
                offSrc += bounds2.width;
            }

            // optimize bounds
            result.optimizeBounds();
        }

        return result;
    }

    /**
     * @deprecated Use {@link #getUnion(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getUnionBooleanMask(List<BooleanMask2D> masks)
    {
        return getUnion(masks);
    }

    /**
     * @deprecated Use {@link ROIUtil#getUnion(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getUnionBooleanMask(ROI2D[] rois)
    {
        final List<BooleanMask2D> masks = new ArrayList<BooleanMask2D>();

        // compute global union boolean mask of all ROI2D
        for (ROI2D roi : rois)
            masks.add(roi.getBooleanMask(true));

        return getUnionBooleanMask(masks);
    }

    /**
     * @deprecated Use {@link ROIUtil#getUnion(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getUnionBooleanMask(ArrayList<ROI2D> rois)
    {
        return getUnionBooleanMask(rois.toArray(new ROI2D[rois.size()]));
    }

    /**
     * @deprecated Use {@link #getUnion(BooleanMask2D, BooleanMask2D)} instead.
     */
    @Deprecated
    public static BooleanMask2D getUnionBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
    {
        return getUnion(mask1, mask2);

    }

    /**
     * @deprecated Use {@link #getUnion(Rectangle, boolean[], Rectangle, boolean[])} instead.
     */
    @Deprecated
    public static BooleanMask2D getUnionBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
            boolean[] mask2)
    {
        return getUnion(bounds1, mask1, bounds2, mask2);

    }

    /**
     * @deprecated Use {@link #getIntersection(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getIntersectionBooleanMask(List<BooleanMask2D> masks)
    {
        return getIntersection(masks);
    }

    /**
     * @deprecated Use {@link #getIntersection(BooleanMask2D, BooleanMask2D)} instead.
     */
    @Deprecated
    public static BooleanMask2D getIntersectionBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
    {
        return getIntersection(mask1, mask2);
    }

    /**
     * @deprecated Use {@link #getIntersection(Rectangle, boolean[], Rectangle, boolean[])} instead.
     */
    @Deprecated
    public static BooleanMask2D getIntersectionBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
            boolean[] mask2)
    {
        return getIntersection(bounds1, mask1, bounds2, mask2);
    }

    /**
     * @deprecated Use {@link ROIUtil#getIntersection(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getIntersectBooleanMask(ROI2D[] rois)
    {
        final List<BooleanMask2D> masks = new ArrayList<BooleanMask2D>();

        // compute global union boolean mask of all ROI2D
        for (ROI2D roi : rois)
            masks.add(roi.getBooleanMask(true));

        return getIntersectionBooleanMask(masks);
    }

    /**
     * @deprecated Use {@link ROIUtil#getIntersection(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getIntersectBooleanMask(ArrayList<ROI2D> rois)
    {
        return getIntersectBooleanMask(rois.toArray(new ROI2D[rois.size()]));
    }

    /**
     * @deprecated Use {@link #getIntersectionBooleanMask(BooleanMask2D, BooleanMask2D)} instead.
     */
    @Deprecated
    public static BooleanMask2D getIntersectBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
    {
        return getIntersectionBooleanMask(mask1.bounds, mask1.mask, mask2.bounds, mask2.mask);
    }

    /**
     * @deprecated Use {@link #getIntersectionBooleanMask(Rectangle, boolean[], Rectangle, boolean[])} instead.
     */
    @Deprecated
    public static BooleanMask2D getIntersectBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
            boolean[] mask2)
    {
        return getIntersectionBooleanMask(bounds1, mask1, bounds2, mask2);
    }

    /**
     * @deprecated Use {@link #getExclusiveUnion(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getExclusiveUnionBooleanMask(List<BooleanMask2D> masks)
    {
        return getExclusiveUnion(masks);
    }

    /**
     * @deprecated Use {@link ROIUtil#getExclusiveUnion(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getExclusiveUnionBooleanMask(ROI2D[] rois)
    {
        final List<BooleanMask2D> masks = new ArrayList<BooleanMask2D>();

        // compute global union boolean mask of all ROI2D
        for (ROI2D roi : rois)
            masks.add(roi.getBooleanMask(true));

        return getExclusiveUnion(masks);
    }

    /**
     * @deprecated Use {@link ROIUtil#getExclusiveUnion(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getExclusiveUnionBooleanMask(ArrayList<ROI2D> rois)
    {
        return getExclusiveUnionBooleanMask(rois.toArray(new ROI2D[rois.size()]));
    }

    /**
     * @deprecated Use {@link #getExclusiveUnion(BooleanMask2D, BooleanMask2D)} instead.
     */
    @Deprecated
    public static BooleanMask2D getExclusiveUnionBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
    {
        return getExclusiveUnion(mask1, mask2);

    }

    /**
     * @deprecated Use {@link #getExclusiveUnion(Rectangle, boolean[], Rectangle, boolean[])} instead.
     */
    @Deprecated
    public static BooleanMask2D getExclusiveUnionBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2,
            boolean[] mask2)
    {
        return getExclusiveUnion(bounds1, mask1, bounds2, mask2);

    }

    /**
     * @deprecated Use {@link #getExclusiveUnionBooleanMask(ROI2D[])} instead.
     */
    @Deprecated
    public static BooleanMask2D getXorBooleanMask(ROI2D[] rois)
    {
        return getExclusiveUnionBooleanMask(rois);
    }

    /**
     * @deprecated Use {@link #getExclusiveUnionBooleanMask(List)} instead.
     */
    @Deprecated
    public static BooleanMask2D getXorBooleanMask(ArrayList<ROI2D> rois)
    {
        return getExclusiveUnionBooleanMask(rois);
    }

    /**
     * @deprecated Use {@link #getExclusiveUnionBooleanMask(BooleanMask2D, BooleanMask2D)} instead.
     */
    @Deprecated
    public static BooleanMask2D getXorBooleanMask(BooleanMask2D mask1, BooleanMask2D mask2)
    {
        return getExclusiveUnionBooleanMask(mask1, mask2);
    }

    /**
     * @deprecated Uses {@link #getExclusiveUnionBooleanMask(Rectangle, boolean[], Rectangle, boolean[])} instead.
     */
    @Deprecated
    public static BooleanMask2D getXorBooleanMask(Rectangle bounds1, boolean[] mask1, Rectangle bounds2, boolean[] mask2)
    {
        return getExclusiveUnionBooleanMask(bounds1, mask1, bounds2, mask2);
    }

    /**
     * Region represented by the mask.
     */
    public Rectangle bounds;
    /**
     * Boolean mask array.
     */
    public boolean[] mask;

    /**
     * Create an empty BooleanMask2D
     */
    public BooleanMask2D()
    {
        this(new Rectangle(), new boolean[0]);
    }

    /**
     * @param bounds
     * @param mask
     */
    public BooleanMask2D(Rectangle bounds, boolean[] mask)
    {
        super();

        this.bounds = bounds;
        this.mask = mask;
    }

    /**
     * Build a new boolean mask from the specified array of {@link Point}.<br>
     */
    public BooleanMask2D(Point[] points)
    {
        super();

        if ((points == null) || (points.length == 0))
        {
            bounds = new Rectangle();
            mask = new boolean[0];
        }
        else
        {
            int minX = Integer.MAX_VALUE;
            int minY = Integer.MAX_VALUE;
            int maxX = Integer.MIN_VALUE;
            int maxY = Integer.MIN_VALUE;

            for (Point pt : points)
            {
                final int x = pt.x;
                final int y = pt.y;

                if (x < minX)
                    minX = x;
                if (x > maxX)
                    maxX = x;
                if (y < minY)
                    minY = y;
                if (y > maxY)
                    maxY = y;
            }

            bounds = new Rectangle(minX, minY, (maxX - minX) + 1, (maxY - minY) + 1);
            mask = new boolean[bounds.width * bounds.height];

            for (Point pt : points)
                mask[((pt.y - minY) * bounds.width) + (pt.x - minX)] = true;
        }
    }

    /**
     * Build a new boolean mask from the specified array of integer representing points.<br>
     * <br>
     * The array should contains point coordinates defined as follow:<br>
     * <code>result.length</code> = number of point * 2<br>
     * <code>result[(pt * 2) + 0]</code> = X coordinate for point <i>pt</i>.<br>
     * <code>result[(pt * 2) + 1]</code> = Y coordinate for point <i>pt</i>.<br>
     */
    public BooleanMask2D(int[] points)
    {
        super();

        if ((points == null) || (points.length == 0))
        {
            bounds = new Rectangle();
            mask = new boolean[0];
        }
        else
        {
            int minX = Integer.MAX_VALUE;
            int minY = Integer.MAX_VALUE;
            int maxX = Integer.MIN_VALUE;
            int maxY = Integer.MIN_VALUE;

            for (int i = 0; i < points.length; i += 2)
            {
                final int x = points[i + 0];
                final int y = points[i + 1];

                if (x < minX)
                    minX = x;
                if (x > maxX)
                    maxX = x;
                if (y < minY)
                    minY = y;
                if (y > maxY)
                    maxY = y;
            }

            bounds = new Rectangle(minX, minY, (maxX - minX) + 1, (maxY - minY) + 1);
            mask = new boolean[bounds.width * bounds.height];

            for (int i = 0; i < points.length; i += 2)
            {
                final int x = points[i + 0];
                final int y = points[i + 1];

                mask[((y - minY) * bounds.width) + (x - minX)] = true;
            }
        }
    }

    /**
     * Return true if boolean mask is empty<br>
     */
    public boolean isEmpty()
    {
        return bounds.isEmpty();
    }

    /**
     * Return true if mask contains the specified point
     */
    public boolean contains(int x, int y)
    {
        if (bounds.contains(x, y))
            return mask[(x - bounds.x) + ((y - bounds.y) * bounds.width)];

        return false;
    }

    /**
     * Return true if mask contains the specified 2D mask.
     */
    public boolean contains(BooleanMask2D booleanMask)
    {
        return contains(booleanMask.bounds, booleanMask.mask);
    }

    /**
     * Return true if mask contains the specified 2D mask.
     */
    public boolean contains(Rectangle rect, boolean[] bmask)
    {
        final Rectangle intersect = bounds.intersection(rect);

        // intersection should be equal to rect
        if (intersect.equals(rect))
        {
            // calculate offsets
            int off1 = ((intersect.y - bounds.y) * bounds.width) + (intersect.x - bounds.x);
            int off2 = ((intersect.y - rect.y) * rect.width) + (intersect.x - rect.x);

            for (int y = 0; y < intersect.height; y++)
            {
                for (int x = 0; x < intersect.width; x++)
                    if (bmask[off2 + x] && !mask[off1 + x])
                        return false;

                off1 += bounds.width;
                off2 += rect.width;
            }

            return true;
        }

        return false;
    }

    /**
     * Return true if mask intersects (contains at least one point) the specified 2D mask.
     */
    public boolean intersects(BooleanMask2D booleanMask)
    {
        return intersects(booleanMask.bounds, booleanMask.mask);
    }

    /**
     * Return true if mask intersects (contains at least one point) the specified 2D mask region.
     */
    public boolean intersects(Rectangle rect, boolean[] bmask)
    {
        final Rectangle intersect = bounds.intersection(rect);

        if (!intersect.isEmpty())
        {
            // calculate offsets
            int off1 = ((intersect.y - bounds.y) * bounds.width) + (intersect.x - bounds.x);
            int off2 = ((intersect.y - rect.y) * rect.width) + (intersect.x - rect.x);

            for (int y = 0; y < intersect.height; y++)
            {
                for (int x = 0; x < intersect.width; x++)
                    if (mask[off1 + x] && bmask[off2 + x])
                        return true;

                off1 += bounds.width;
                off2 += rect.width;
            }
        }

        return false;
    }

    /**
     * Return the number of points contained in this boolean mask.
     */
    public int getNumberOfPoints()
    {
        int result = 0;

        for (int i = 0; i < mask.length;)
            if (mask[i++])
                result++;

        return result;
    }

    /**
     * Return an array of {@link Point} representing all points of the current mask.<br>
     * Points are returned in ascending XY order :
     * 
     * <pre>
     * Ymin  12 
     *      3456
     *       78
     * Ymax   9
     * </pre>
     * 
     * @see #getPointsAsIntArray()
     */
    public Point[] getPoints()
    {
        return TypeUtil.toPoint(getPointsAsIntArray());
    }

    /**
     * Return an array of integer representing all points of the current mask.<br>
     * <code>result.length</code> = number of point * 2<br>
     * <code>result[(pt * 2) + 0]</code> = X coordinate for point <i>pt</i>.<br>
     * <code>result[(pt * 2) + 1]</code> = Y coordinate for point <i>pt</i>.<br>
     * Points are returned in ascending XY order :
     * 
     * <pre>
     * Ymin  12 
     *      3456
     *       78
     * Ymax   9
     * </pre>
     */
    public int[] getPointsAsIntArray()
    {
        if (bounds.isEmpty())
            return new int[0];

        int[] points = new int[mask.length * 2];
        final int maxx = bounds.x + bounds.width;
        final int maxy = bounds.y + bounds.height;

        int pt = 0;
        int off = 0;
        for (int y = bounds.y; y < maxy; y++)
        {
            for (int x = bounds.x; x < maxx; x++)
            {
                if (mask[off++])
                {
                    points[pt++] = x;
                    points[pt++] = y;
                }
            }
        }

        final int[] result = new int[pt];

        System.arraycopy(points, 0, result, 0, pt);

        return result;
    }

    /**
     * Return a 2D array of integer representing points of each component of the current mask.<br>
     * A component is basically an isolated object which does not touch any other objects.<br>
     * Internal use only.
     */
    protected List<Component> getComponentsPointsInternal()
    {
        final List<Component> components = new ArrayList<Component>();
        final int w = bounds.width;
        final int minx = bounds.x;
        final int miny = bounds.y;

        // cache
        final Component[] line0 = new Component[w + 2];
        final Component[] line1 = new Component[w + 2];

        Arrays.fill(line0, null);
        Arrays.fill(line1, null);

        Component[] prevLine = line0;
        Component[] currLine = line1;

        Component left;
        Component top;
        Component topleft;

        int offset = 0;
        for (int y = 0; y < bounds.height; y++)
        {
            // prepare previous cache
            topleft = null;
            left = null;

            for (int x = 0; x < w; x++)
            {
                top = prevLine[x + 1];

                // we have a component pixel
                if (mask[offset++])
                {
                    if (topleft != null)
                    {
                        // mix component
                        if ((left != null) && (left != topleft))
                            topleft.addComponent(left);

                        left = topleft;
                    }
                    else if (top != null)
                    {
                        // mix component
                        if ((left != null) && (left != top))
                            top.addComponent(left);

                        left = top;
                    }
                    else if (left == null)
                    {
                        // new component
                        left = new Component();
                        components.add(left);
                    }

                    // add pixel to component
                    left.addPoint(x + minx, y + miny);
                }
                else
                {
                    // mix component
                    if ((left != null) && (top != null) && (left != top))
                        top.addComponent(left);

                    left = null;
                }

                topleft = top;
                // set current component index line cache
                currLine[x + 1] = left;
            }

            Component[] pl = prevLine;
            prevLine = currLine;
            currLine = pl;
        }

        final List<Component> result = new ArrayList<Component>();

        // remove partial components
        for (Component component : components)
            if (component.isRoot())
                result.add(component);

        return result;
    }

    /**
     * Compute and return a 2D array of {@link Point} representing points of each component of the
     * current mask.<br>
     * A component is basically an isolated object which do not touch any other object.<br>
     * <br>
     * The array is returned in the following format :<br>
     * <code>result.lenght</code> = number of component.<br>
     * <code>result[c].length</code> = number of point of component c.<br>
     * <code>result[c][n]</code> = Point n of component c.<br>
     * 
     * @param sorted
     *        When true points are returned in ascending XY order :
     * 
     *        <pre>
     * Ymin  12 
     *      3456
     *       78
     * Ymax   9
     * </pre>
     * @see #getComponentsPointsAsIntArray()
     */
    public Point[][] getComponentsPoints(boolean sorted)
    {
        if (bounds.isEmpty())
            return new Point[0][0];

        final Comparator<Point> pointComparator;

        if (sorted)
        {
            pointComparator = new Comparator<Point>()
            {
                @Override
                public int compare(Point p1, Point p2)
                {
                    if (p1.y < p2.y)
                        return -1;
                    if (p1.y > p2.y)
                        return 1;

                    return 0;
                }
            };
        }
        else
            pointComparator = null;

        final int[][] cPoints = getComponentsPointsAsIntArray();
        final Point[][] cResult = new Point[cPoints.length][];

        for (int i = 0; i < cPoints.length; i++)
        {
            final Point[] result = TypeUtil.toPoint(cPoints[i]);

            // sort points
            if (pointComparator != null)
                Arrays.sort(result, pointComparator);

            cResult[i] = result;
        }

        return cResult;
    }

    /**
     * Compute and return a 2D array of integer representing points of each component of the
     * current mask.<br>
     * A component is basically an isolated object which do not touch any other object.<br>
     * <br>
     * The array is returned in the following format :<br>
     * <code>result.lenght</code> = number of component.<br>
     * <code>result[c].length</code> = number of point * 2 for component c.<br>
     * <code>result[c][(pt * 2) + 0]</code> = X coordinate for point <i>pt</i> of component
     * <i>c</i>.<br>
     * <code>result[c][(pt * 2) + 1]</code> = Y coordinate for point <i>pt</i> of component
     * <i>c</i>.<br>
     * 
     * @see #getComponentsPoints(boolean)
     */
    public int[][] getComponentsPointsAsIntArray()
    {
        if (bounds.isEmpty())
            return new int[0][0];

        final List<Component> components = getComponentsPointsInternal();
        final int[][] result = new int[components.size()][];

        // convert list of point to Point array
        for (int i = 0; i < result.length; i++)
            result[i] = components.get(i).getAllPoints();

        return result;
    }

    /**
     * Return an array of boolean mask representing each independent component of the current
     * mask.<br>
     * A component is basically an isolated object which does not touch any other objects.
     */
    public BooleanMask2D[] getComponents()
    {
        if (bounds.isEmpty())
            return new BooleanMask2D[0];

        final int[][] componentsPoints = getComponentsPointsAsIntArray();
        final List<BooleanMask2D> result = new ArrayList<BooleanMask2D>();

        // convert array of point to boolean mask
        for (int[] componentPoints : componentsPoints)
            result.add(new BooleanMask2D(componentPoints));

        return result.toArray(new BooleanMask2D[result.size()]);
    }

    /**
     * Returns a list of Point representing the contour points of the mask in connected order.<br>
     * Note that you should use this method carefully at it does not make any sense to use this method for mask
     * containing disconnected objects.<br>
     * Also it may returns incorrect result if the contour is not consistent (several connected contour possible).
     * 
     * @see #getContourPoints()
     */
    public List<Point> getConnectedContourPoints()
    {
        final int[] intArrayPoints = getContourPointsAsIntArray();
        final List<List<Point>> allPoints = new ArrayList<List<Point>>();

        // empty contour
        if (intArrayPoints.length == 0)
            return new ArrayList<Point>(0);

        final boolean[] contourMask;
        final Rectangle contourBounds;

        synchronized (this)
        {
            contourMask = new boolean[mask.length];
            contourBounds = bounds;
        }

        final int minx = contourBounds.x;
        final int miny = contourBounds.y;
        final int w = contourBounds.width;

        // build contour mask
        for (int i = 0; i < intArrayPoints.length; i += 2)
            contourMask[(intArrayPoints[i + 0] - minx) + ((intArrayPoints[i + 1] - miny) * w)] = true;

        final int maxx = minx + (w - 1);
        final int maxy = miny + (contourBounds.height - 1);
        // create visited pixel mask
        final boolean[] visitedMask = new boolean[contourMask.length];

        // first start point
        int startOffset = findStartPoint(0, contourMask, visitedMask);

        while (startOffset != -1)
        {
            final List<Point> points = new ArrayList<Point>(intArrayPoints.length / 2);

            int offset = startOffset;
            int x = (offset % w) + minx;
            int y = (offset / w) + miny;

            // add first point
            visitedMask[offset] = true;
            points.add(new Point(x, y));

            // scanning order
            // 567
            // 4x0
            // 321
            int scan = 0;
            int remain = 8;

            // scan connected pixels (8 points)
            while (remain-- > 0)
            {
                int tmpOff;

                switch (scan & 7)
                {
                    case 0:
                        // not last X
                        if (x < maxx)
                        {
                            tmpOff = offset + 1;

                            // contour ?
                            if (contourMask[tmpOff])
                            {
                                // contour already visited --> done
                                if (visitedMask[tmpOff])
                                {
                                    remain = 0;
                                    break;
                                }

                                // set new position
                                x++;
                                offset = tmpOff;
                                // mark as visited
                                visitedMask[offset] = true;
                                // and add point
                                points.add(new Point(x, y));
                                // change next scan pos
                                scan = 7 - 1;
                                remain = 8;
                            }
                        }
                        break;

                    case 1:
                        // not last X and not last Y
                        if ((x < maxx) && (y < maxy))
                        {
                            tmpOff = offset + w + 1;
                            // contour ?
                            if (contourMask[tmpOff])
                            {
                                // contour already visited --> done
                                if (visitedMask[tmpOff])
                                {
                                    remain = 0;
                                    break;
                                }

                                // set new position
                                x++;
                                y++;
                                offset = tmpOff;
                                // mark as visited
                                visitedMask[offset] = true;
                                // and add point
                                points.add(new Point(x, y));
                                // change next scan pos
                                scan = 7 - 1;
                                remain = 8;
                            }
                        }
                        break;

                    case 2:
                        // not last Y
                        if (y < maxy)
                        {
                            tmpOff = offset + w;
                            // contour ?
                            if (contourMask[tmpOff])
                            {
                                // contour already visited --> done
                                if (visitedMask[tmpOff])
                                {
                                    remain = 0;
                                    break;
                                }

                                // set new position
                                y++;
                                offset = tmpOff;
                                // mark as visited
                                visitedMask[offset] = true;
                                // and add point
                                points.add(new Point(x, y));
                                // change next scan pos
                                scan = 1 - 1;
                                remain = 8;
                            }
                        }
                        break;

                    case 3:
                        // not first X and not last Y
                        if ((x > minx) && (y < maxy))
                        {
                            tmpOff = (offset + w) - 1;

                            // contour ?
                            if (contourMask[tmpOff])
                            {
                                // contour already visited --> done
                                if (visitedMask[tmpOff])
                                {
                                    remain = 0;
                                    break;
                                }

                                // set new position
                                x--;
                                y++;
                                offset = tmpOff;
                                // mark as visited
                                visitedMask[offset] = true;
                                // and add point
                                points.add(new Point(x, y));
                                // change next scan pos
                                scan = 1 - 1;
                                remain = 8;
                            }
                        }
                        break;

                    case 4:
                        // not first X
                        if (x > minx)
                        {
                            tmpOff = offset - 1;
                            // contour ?
                            if (contourMask[tmpOff])
                            {
                                // contour already visited --> done
                                if (visitedMask[tmpOff])
                                {
                                    remain = 0;
                                    break;
                                }

                                // set new position
                                x--;
                                offset = tmpOff;
                                // mark as visited
                                visitedMask[offset] = true;
                                // and add point
                                points.add(new Point(x, y));
                                // change next scan pos
                                scan = 3 - 1;
                                remain = 8;
                            }
                        }
                        break;

                    case 5:
                        // not first X and not first Y
                        if ((x > minx) && (y > miny))
                        {
                            tmpOff = offset - (w + 1);
                            // contour ?
                            if (contourMask[tmpOff])
                            {
                                // contour already visited --> done
                                if (visitedMask[tmpOff])
                                {
                                    remain = 0;
                                    break;
                                }

                                // set new position
                                x--;
                                y--;
                                offset = tmpOff;
                                // mark as visited
                                visitedMask[offset] = true;
                                // and add point
                                points.add(new Point(x, y));
                                // change next scan pos
                                scan = 3 - 1;
                                remain = 8;
                            }
                        }
                        break;

                    case 6:
                        // not first Y
                        if (y > miny)
                        {
                            tmpOff = offset - w;
                            // contour ?
                            if (contourMask[tmpOff])
                            {
                                // contour already visited --> done
                                if (visitedMask[tmpOff])
                                {
                                    remain = 0;
                                    break;
                                }

                                // set new position
                                y--;
                                offset = tmpOff;
                                // mark as visited
                                visitedMask[offset] = true;
                                // and add point
                                points.add(new Point(x, y));
                                // change next scan pos
                                scan = 5 - 1;
                                remain = 8;
                            }
                        }
                        break;

                    case 7:
                        // not last X and not first Y
                        if ((x < maxx) && (y > miny))
                        {
                            tmpOff = (offset - w) + 1;
                            // contour ?
                            if (contourMask[tmpOff])
                            {
                                // contour already visited --> done
                                if (visitedMask[tmpOff])
                                {
                                    remain = 0;
                                    break;
                                }

                                // set new position
                                x++;
                                y--;
                                offset = tmpOff;
                                // mark as visited
                                visitedMask[offset] = true;
                                // and add point
                                points.add(new Point(x, y));
                                // change next scan pos
                                scan = 5 - 1;
                                remain = 8;
                            }
                        }
                        break;
                }

                scan = (scan + 1) & 7;
            }

            allPoints.add(points);
            // get next start point
            startOffset = findStartPoint(startOffset, contourMask, visitedMask);
        }

        List<Point> result = new ArrayList<Point>(allPoints.get(0));
        allPoints.remove(0);

        // connect all found paths
        int i = 0;
        while (i < allPoints.size())
        {
            final List<Point> newResult = connect(result, allPoints.get(i));

            if (newResult != null)
            {
                result = newResult;
                allPoints.remove(i);
                i = 0;
            }
            else
                i++;
        }

        // try to insert remaining paths
        i = 0;
        while (i < allPoints.size())
        {
            final List<Point> newResult = insertPoints(result, allPoints.get(i));

            if (newResult != null)
            {
                result = newResult;
                allPoints.remove(i);
                i = 0;
            }
            else
                i++;
        }

        // debug
        // for(Point pt:result)
        // System.out.println(pt);

        // some parts can't be connected --> display warning
        // if (!allPoints.isEmpty())
        // System.out.println("Warning: can't connect some points...");

        return result;
    }

    /**
     * Returns an array of {@link Point} containing the contour points of the mask.<br>
     * Points are returned in ascending XY order:
     * 
     * <pre>
     *  123 
     *  4 5
     *  6 7
     *   89
     * </pre>
     * 
     * @see #getContourPointsAsIntArray()
     */
    public Point[] getContourPoints()
    {
        return TypeUtil.toPoint(getContourPointsAsIntArray());
    }

    /**
     * Returns an array of integer containing the contour points of the mask.<br>
     * <code>result.length</code> = number of point * 2<br>
     * <code>result[(pt * 2) + 0]</code> = X coordinate for point <i>pt</i>.<br>
     * <code>result[(pt * 2) + 1]</code> = Y coordinate for point <i>pt</i>.<br>
     * Points are returned in ascending XY order:
     * 
     * <pre>
     *  123 
     *  4 5
     *  6 7
     *   89
     * </pre>
     * 
     * @see #getConnectedContourPoints()
     */
    public int[] getContourPointsAsIntArray()
    {
        if (isEmpty())
            return new int[0];

        final boolean[] mask;
        final Rectangle bounds;

        synchronized (this)
        {
            mask = this.mask;
            bounds = this.bounds;
        }

        final int[] points = new int[mask.length * 2];
        final int h = bounds.height;
        final int w = bounds.width;
        final int maxx = bounds.x + (w - 1);
        final int maxy = bounds.y + (h - 1);

        // cache
        boolean top = false;
        boolean bottom = false;
        boolean left = false;
        boolean right = false;
        boolean current;

        int offset = 0;
        int pt = 0;

        // special case
        if ((w == 1) && (h == 1))
        {
            if (mask[0])
            {
                points[pt++] = bounds.x;
                points[pt++] = bounds.y;
            }
        }
        else if (w == 1)
        {
            // first pixel of row
            top = false;
            current = mask[offset];
            bottom = mask[++offset];

            // current pixel is a border ?
            if (current && !(top && bottom))
            {
                points[pt++] = bounds.x;
                points[pt++] = bounds.y;
            }

            // row
            for (int y = bounds.y + 1; y < maxy; y++)
            {
                // cache
                top = current;
                current = bottom;
                bottom = mask[++offset];

                // current pixel is a border ?
                if (current && !(top && bottom))
                {
                    points[pt++] = bounds.x;
                    points[pt++] = y;
                }
            }

            // cache
            top = current;
            current = bottom;
            bottom = false;

            // current pixel is a border ?
            if (current && !(top && bottom))
            {
                points[pt++] = bounds.x;
                points[pt++] = maxy;
            }
        }
        // special case
        else if (h == 1)
        {
            // first pixel of line
            left = false;
            current = mask[offset];
            right = mask[++offset];

            // current pixel is a border ?
            if (current && !(left && right))
            {
                points[pt++] = bounds.x;
                points[pt++] = bounds.y;
            }

            // line
            for (int x = bounds.x + 1; x < maxx; x++)
            {
                // cache
                left = current;
                current = right;
                right = mask[++offset];

                // current pixel is a border ?
                if (current && !(left && right))
                {
                    points[pt++] = x;
                    points[pt++] = bounds.y;
                }
            }

            // last pixel of first line
            left = current;
            current = right;
            right = false;

            // current pixel is a border ?
            if (current && !(left && right))
            {
                points[pt++] = maxx;
                points[pt++] = bounds.y;
            }
        }
        else
        {
            // first pixel of first line
            top = false;
            left = false;
            current = mask[offset];
            bottom = mask[offset + w];
            right = mask[++offset];

            // current pixel is a border ?
            if (current && !(top && left && right && bottom))
            {
                points[pt++] = bounds.x;
                points[pt++] = bounds.y;
            }

            // first line
            for (int x = bounds.x + 1; x < maxx; x++)
            {
                // cache
                left = current;
                current = right;
                bottom = mask[offset + w];
                right = mask[++offset];

                // current pixel is a border ?
                if (current && !(top && left && right && bottom))
                {
                    points[pt++] = x;
                    points[pt++] = bounds.y;
                }
            }

            // last pixel of first line
            left = current;
            current = right;
            bottom = mask[offset + w];
            right = false;
            offset++;

            // current pixel is a border ?
            if (current && !(top && left && right && bottom))
            {
                points[pt++] = maxx;
                points[pt++] = bounds.y;
            }

            for (int y = bounds.y + 1; y < maxy; y++)
            {
                // first pixel of line
                left = false;
                current = mask[offset];
                top = mask[offset - w];
                bottom = mask[offset + w];
                right = mask[++offset];

                // current pixel is a border ?
                if (current && !(top && left && right && bottom))
                {
                    points[pt++] = bounds.x;
                    points[pt++] = y;
                }

                for (int x = bounds.x + 1; x < maxx; x++)
                {
                    // cache
                    left = current;
                    current = right;
                    top = mask[offset - w];
                    bottom = mask[offset + w];
                    right = mask[++offset];

                    // current pixel is a border ?
                    if (current && !(top && left && right && bottom))
                    {
                        points[pt++] = x;
                        points[pt++] = y;
                    }
                }

                // last pixel of line
                left = current;
                current = right;
                top = mask[offset - w];
                bottom = mask[offset + w];
                right = false;
                offset++;

                // current pixel is a border ?
                if (current && !(top && left && right && bottom))
                {
                    points[pt++] = maxx;
                    points[pt++] = y;
                }
            }

            // first pixel of last line
            left = false;
            current = mask[offset];
            top = mask[offset - w];
            bottom = false;
            right = mask[++offset];

            // current pixel is a border ?
            if (current && !(top && left && right && bottom))
            {
                points[pt++] = bounds.x;
                points[pt++] = maxy;
            }

            // last line
            for (int x = bounds.x + 1; x < maxx; x++)
            {
                // cache
                left = current;
                current = right;
                top = mask[offset - w];
                right = mask[++offset];

                // current pixel is a border ?
                if (current && !(top && left && right && bottom))
                {
                    points[pt++] = x;
                    points[pt++] = maxy;
                }
            }

            // last pixel of last line
            left = current;
            current = right;
            top = mask[offset - w];
            right = false;

            // current pixel is a border ?
            if (current && !(top && left && right && bottom))
            {
                points[pt++] = maxx;
                points[pt++] = maxy;
            }
        }

        final int[] result = new int[pt];

        System.arraycopy(points, 0, result, 0, pt);

        return result;
    }

    /**
     * @deprecated Use {@link #getContourPoints()} instead.
     */
    @Deprecated
    public Point[] getEdgePoints()
    {
        return TypeUtil.toPoint(getContourPointsAsIntArray());
    }

    /**
     * Computes and returns the length of the contour.<br/>
     * This is different from the number of contour point as it takes care of approximating
     * correctly distance between each contour point.
     * 
     * @author Alexandre Dufour
     * @author Stephane Dallongeville
     * @return the length of the contour
     */
    public double getContourLength()
    {
        double perimeter = 0;

        final int[] edge = getContourPointsAsIntArray();
        final int baseX = bounds.x;
        final int baseY = bounds.y;
        final int width = bounds.width;
        final int height = bounds.height;

        // count the edges and corners in 2D/3D
        double sideEdges = 0, cornerEdges = 0;

        for (int i = 0; i < edge.length; i += 2)
        {
            final int x = edge[i + 0] - baseX;
            final int y = edge[i + 1] - baseY;
            final int xy = x + (y * width);

            final boolean topLeftConnected;
            final boolean topConnected;
            final boolean topRightConnected;
            final boolean bottomLeftConnected;
            final boolean bottomConnected;
            final boolean bottomRightConnected;

            if (y != 0)
            {
                topLeftConnected = (x != 0) && mask[(xy - 1) - width];
                topConnected = mask[(xy + 0) - width];
                topRightConnected = (x != (width - 1)) && mask[(xy + 1) - width];
            }
            else
            {
                topLeftConnected = false;
                topConnected = false;
                topRightConnected = false;
            }

            final boolean leftConnected = (x != 0) && mask[xy - 1];
            final boolean rightConnected = (x != (width - 1)) && mask[xy + 1];

            if (y != (height - 1))
            {
                bottomLeftConnected = (x != 0) && mask[(xy - 1) + width];
                bottomConnected = mask[(xy + 0) + width];
                bottomRightConnected = (x != (width - 1)) && mask[(xy + 1) + width];
            }
            else
            {
                bottomLeftConnected = false;
                bottomConnected = false;
                bottomRightConnected = false;
            }

            // count the connections
            int directConnection = 0;
            int diagConnection = 0;

            if (topLeftConnected)
                diagConnection++;
            if (topConnected)
                directConnection++;
            if (topRightConnected)
                diagConnection++;
            if (leftConnected)
                directConnection++;
            if (rightConnected)
                directConnection++;
            if (bottomLeftConnected)
                diagConnection++;
            if (bottomConnected)
                directConnection++;
            if (bottomRightConnected)
                diagConnection++;

            switch (directConnection)
            {
                case 0: // no direct connection
                    switch (diagConnection)
                    {
                        case 0: // isolated point
                            perimeter += Math.PI;
                            break;

                        case 1: // ending point (diagonal)
                            cornerEdges++;
                            perimeter += Math.sqrt(2) + (Math.PI / 2);
                            break;

                        default: // diagonal angle line
                            cornerEdges += 2;
                            perimeter += 2 * Math.sqrt(2);
                            break;
                    }
                    break;

                case 1: // ending point
                    switch (diagConnection)
                    {
                        case 0: // ending point straight
                            sideEdges++;
                            perimeter += 1 + (Math.PI / 2);
                            break;

                        default: // assume triangle with 45° angle
                            cornerEdges++;
                            sideEdges++;
                            perimeter += 1 + Math.sqrt(2);
                            break;
                    }
                    break;

                case 2:
                    if ((leftConnected && rightConnected) || (topConnected && bottomConnected))
                    {
                        final double dgc = diagConnection * 0.5;
                        final double dtc = 2 - dgc;

                        cornerEdges += dgc;
                        sideEdges += dtc;
                        perimeter += dtc + (dgc * Math.sqrt(2));
                    }
                    else
                    {
                        // consider 90° corner
                        cornerEdges++;
                        perimeter += Math.sqrt(2);
                    }
                    break;

                case 3: // classic border (180°)
                    switch (diagConnection)
                    {
                        default: // classic border
                            sideEdges++;
                            perimeter++;
                            break;

                        case 3:
                            // consider 225° interior corner
                            cornerEdges += 0.5;
                            sideEdges += 0.5;
                            perimeter += 0.5 + (Math.sqrt(2) / 2);
                            break;

                        case 4: // hole inside contour
                            cornerEdges++;
                            perimeter += Math.sqrt(2);
                            break;
                    }
                    break;

                case 4: // internal point --> should not happen
                    break;
            }
        }

        // adjust the perimeter empirically according to the edge distribution
        double overShoot = Math.min(sideEdges / 10, cornerEdges);

        return perimeter - overShoot;
    }

    /**
     * Compute intersection with specified mask and return result in a new mask.
     * 
     * @see #intersect(BooleanMask2D)
     */
    public BooleanMask2D getIntersection(BooleanMask2D booleanMask)
    {
        return getIntersection(this, booleanMask);
    }

    /**
     * Compute intersection with specified mask and return result in a new mask.
     * 
     * @see #intersect(Rectangle, boolean[])
     */
    public BooleanMask2D getIntersection(Rectangle bounds, boolean[] mask)
    {
        return getIntersection(this.bounds, this.mask, bounds, mask);
    }

    /**
     * @deprecated Use {@link #getIntersection(BooleanMask2D)} instead.
     */
    @Deprecated
    public BooleanMask2D getIntersect(BooleanMask2D booleanMask)
    {
        return getIntersection(booleanMask);
    }

    /**
     * @deprecated Use {@link #getIntersection(Rectangle, boolean[])} instead.
     */
    @Deprecated
    public BooleanMask2D getIntersect(Rectangle bounds, boolean[] mask)
    {
        return getIntersection(bounds, mask);
    }

    /**
     * Compute union with specified mask and return result in a new mask.
     * 
     * @see #add(BooleanMask2D)
     */
    public BooleanMask2D getUnion(BooleanMask2D booleanMask)
    {
        return getUnion(this, booleanMask);
    }

    /**
     * Compute union with specified mask and return result in a new mask.
     * 
     * @see #add(Rectangle, boolean[])
     */
    public BooleanMask2D getUnion(Rectangle bounds, boolean[] mask)
    {
        return getUnion(this.bounds, this.mask, bounds, mask);
    }

    /**
     * Compute exclusive union operation with specified mask and return result in a new mask.
     * 
     * @see #exclusiveAdd(BooleanMask2D)
     */
    public BooleanMask2D getExclusiveUnion(BooleanMask2D booleanMask)
    {
        return getExclusiveUnion(this, booleanMask);
    }

    /**
     * Compute exclusive union operation with specified mask and return result in a new mask
     * 
     * @see #exclusiveAdd(Rectangle, boolean[])
     */
    public BooleanMask2D getExclusiveUnion(Rectangle bounds, boolean[] mask)
    {
        return getExclusiveUnion(this.bounds, this.mask, bounds, mask);
    }

    /**
     * Subtract the specified mask from current and return result in a new mask.
     */
    public BooleanMask2D getSubtraction(BooleanMask2D mask)
    {
        return getSubtraction(this, mask);
    }

    /**
     * Subtract the specified mask from current and return result in a new mask.
     */
    public BooleanMask2D getSubtraction(Rectangle bounds, boolean[] mask)
    {
        return getSubtraction(this.bounds, this.mask, bounds, mask);
    }

    /**
     * @deprecated Use {@link #getExclusiveUnion(BooleanMask2D)} instead.
     */
    @Deprecated
    public BooleanMask2D getXor(BooleanMask2D booleanMask)
    {
        return getExclusiveUnion(booleanMask);
    }

    /**
     * @deprecated Use {@link #getExclusiveUnion(Rectangle, boolean[])} instead.
     */
    @Deprecated
    public BooleanMask2D getXor(Rectangle bounds, boolean[] mask)
    {
        return getExclusiveUnion(bounds, mask);
    }

    /**
     * Add the specified mask into the current mask (bounds can be enlarged):
     * 
     * <pre>
     *       current mask    +         mask       =       result
     * 
     *     ################     ################     ################
     *     ##############         ##############     ################
     *     ############             ############     ################
     *     ##########                 ##########     ################
     *     ########                     ########     ################
     *     ######                         ######     ######    ######
     *     ####                             ####     ####        ####
     *     ##                                 ##     ##            ##
     * </pre>
     */
    public void add(BooleanMask2D booleanMask)
    {
        add(booleanMask.bounds, booleanMask.mask);
    }

    /**
     * Add the specified mask into the current mask (bounds can be enlarged):
     * 
     * <pre>
     *       current mask    +         mask       =       result
     * 
     *     ################     ################     ################
     *     ##############         ##############     ################
     *     ############             ############     ################
     *     ##########                 ##########     ################
     *     ########                     ########     ################
     *     ######                         ######     ######    ######
     *     ####                             ####     ####        ####
     *     ##                                 ##     ##            ##
     * </pre>
     */
    public void add(Rectangle boundsToAdd, boolean[] maskToAdd)
    {
        // don't need to reallocate the mask
        if (bounds.contains(boundsToAdd))
        {
            int offDst, offSrc;

            // calculate offset
            offDst = ((boundsToAdd.y - bounds.y) * bounds.width) + (boundsToAdd.x - bounds.x);
            offSrc = 0;

            for (int y = 0; y < boundsToAdd.height; y++)
            {
                for (int x = 0; x < boundsToAdd.width; x++)
                    if (maskToAdd[offSrc++])
                        mask[offDst + x] = true;

                offDst += bounds.width;
            }
        }
        else
        {
            // create a new mask
            final BooleanMask2D result = getUnion(boundsToAdd, maskToAdd);

            // update bounds and mask
            synchronized (this)
            {
                this.bounds = result.bounds;
                this.mask = result.mask;
            }
        }
    }

    /**
     * Set the content of current mask with the result of the intersection with the specified mask:
     * 
     * <pre>
     *     current mask  intersect     newMask       =       result
     * 
     *     ################     ################     ################
     *     ##############         ##############       ############
     *     ############             ############         ########
     *     ##########                 ##########           ####
     *     ########                     ########
     *     ######                         ######
     *     ####                             ####
     *     ##                                 ##
     * </pre>
     */
    public void intersect(BooleanMask2D booleanMask)
    {
        intersect(booleanMask.bounds, booleanMask.mask);
    }

    /**
     * Set the content of current mask with the result of the intersection with the specified mask:
     * 
     * <pre>
     *     current mask  intersect     newMask       =       result
     * 
     *     ################     ################     ################
     *     ##############         ##############       ############
     *     ############             ############         ########
     *     ##########                 ##########           ####
     *     ########                     ########
     *     ######                         ######
     *     ####                             ####
     *     ##                                 ##
     * </pre>
     */
    public void intersect(Rectangle boundsToIntersect, boolean[] maskToIntersect)
    {
        // faster to always create a new mask
        final BooleanMask2D result = getIntersection(boundsToIntersect, maskToIntersect);

        synchronized (this)
        {
            this.bounds = result.bounds;
            this.mask = result.mask;
        }
    }

    /**
     * Exclusively add the specified mask into the current mask (bounds can change):
     * 
     * <pre>
     *          mask1       xor      mask2        =       result
     * 
     *     ################     ################
     *     ##############         ##############     ##            ##
     *     ############             ############     ####        ####
     *     ##########                 ##########     ######    ######
     *     ########                     ########     ################
     *     ######                         ######     ######    ######
     *     ####                             ####     ####        ####
     *     ##                                 ##     ##            ##
     * </pre>
     */
    public void exclusiveAdd(BooleanMask2D booleanMask)
    {
        exclusiveAdd(booleanMask.bounds, booleanMask.mask);
    }

    /**
     * Exclusively add the specified mask into the current mask (bounds can change):
     * 
     * <pre>
     *          mask1       xor      mask2        =       result
     * 
     *     ################     ################
     *     ##############         ##############     ##            ##
     *     ############             ############     ####        ####
     *     ##########                 ##########     ######    ######
     *     ########                     ########     ################
     *     ######                         ######     ######    ######
     *     ####                             ####     ####        ####
     *     ##                                 ##     ##            ##
     * </pre>
     */
    public void exclusiveAdd(Rectangle boundsToXAdd, boolean[] maskToXAdd)
    {
        // don't need to reallocate the mask
        if (bounds.contains(boundsToXAdd))
        {
            int offDst, offSrc;

            // calculate offset
            offDst = ((boundsToXAdd.y - bounds.y) * bounds.width) + (boundsToXAdd.x - bounds.x);
            offSrc = 0;

            for (int y = 0; y < boundsToXAdd.height; y++)
            {
                for (int x = 0; x < boundsToXAdd.width; x++)
                    if (maskToXAdd[offSrc++])
                        mask[offDst + x] = !mask[offDst + x];

                offDst += bounds.width;
            }

            // bounds may have changed
            optimizeBounds();
        }
        else
        {
            // create a new mask
            final BooleanMask2D result = getExclusiveUnion(boundsToXAdd, maskToXAdd);

            // update bounds and mask
            synchronized (this)
            {
                this.bounds = result.bounds;
                this.mask = result.mask;
            }
        }
    }

    /**
     * Subtract the specified mask from the current mask (bounds can change):
     * 
     * <pre>
     *       current mask    -        mask        =  result
     * 
     *     ################     ################
     *     ##############         ##############     ##
     *     ############             ############     ####
     *     ##########                 ##########     ######
     *     ########                     ########     ########
     *     ######                         ######     ######
     *     ####                             ####     ####
     *     ##                                 ##     ##
     * </pre>
     */
    public void subtract(Rectangle boundsToSubtract, boolean[] maskToSubtract)
    {
        // compute intersection
        final Rectangle intersection = bounds.intersection(boundsToSubtract);

        // need to subtract something ?
        if (!intersection.isEmpty())
        {
            // calculate offset
            int offDst = ((intersection.y - bounds.y) * bounds.width) + (intersection.x - bounds.x);
            int offSrc = ((intersection.y - boundsToSubtract.y) * boundsToSubtract.width)
                    + (intersection.x - boundsToSubtract.x);

            for (int y = 0; y < intersection.height; y++)
            {
                for (int x = 0; x < intersection.width; x++)
                    if (maskToSubtract[offSrc + x])
                        mask[offDst + x] = false;

                offDst += bounds.width;
                offSrc += boundsToSubtract.width;
            }

            // optimize bounds
            optimizeBounds();
        }
    }

    /**
     * @deprecated Use {@link #add(BooleanMask2D)} instead.
     */
    @Deprecated
    public void union(BooleanMask2D booleanMask)
    {
        add(booleanMask.bounds, booleanMask.mask);
    }

    /**
     * @deprecated Use {@link #add(BooleanMask2D)} instead.
     */
    @Deprecated
    public void union(Rectangle bounds, boolean[] mask)
    {
        add(bounds, mask);
    }

    /**
     * @deprecated Use {@link #getExclusiveUnion(BooleanMask2D)} instead.
     */
    @Deprecated
    public void exclusiveUnion(BooleanMask2D booleanMask)
    {
        exclusiveAdd(booleanMask.bounds, booleanMask.mask);
    }

    /**
     * @deprecated Use {@link #getExclusiveUnion(Rectangle, boolean[])} instead.
     */
    @Deprecated
    public void exclusiveUnion(Rectangle bounds, boolean[] mask)
    {
        exclusiveAdd(bounds, mask);
    }

    /**
     * @deprecated Use {@link #exclusiveUnion(BooleanMask2D)} instead
     */
    @Deprecated
    public void xor(BooleanMask2D booleanMask)
    {
        exclusiveAdd(booleanMask);
    }

    /**
     * @deprecated Use {@link #exclusiveUnion(Rectangle, boolean[])} instead
     */
    @Deprecated
    public void xor(Rectangle bounds, boolean[] mask)
    {
        exclusiveAdd(bounds, mask);
    }

    /**
     * Get the smallest bounds which fit mask content.
     */
    public Rectangle getOptimizedBounds()
    {
        // find best bounds
        final int sizeX = bounds.width;
        final int sizeY = bounds.height;

        int minX = sizeX;
        int minY = sizeY;
        int maxX = -1;
        int maxY = -1;
        int offset = 0;
        for (int y = 0; y < sizeY; y++)
        {
            for (int x = 0; x < sizeX; x++)
            {
                if (mask[offset++])
                {
                    if (x < minX)
                        minX = x;
                    if (x > maxX)
                        maxX = x;
                    if (y < minY)
                        minY = y;
                    if (y > maxY)
                        maxY = y;
                }
            }
        }

        // empty --> return empty bounds
        if (minX == sizeX)
            return new Rectangle(bounds.x, bounds.y, 0, 0);

        // new calculated bounds
        return new Rectangle(bounds.x + minX, bounds.y + minY, (maxX - minX) + 1, (maxY - minY) + 1);
    }

    /**
     * Optimize mask bounds so it fit mask content.
     */
    public void optimizeBounds()
    {
        moveBounds(getOptimizedBounds());
    }

    /**
     * @deprecated Use {@link #moveBounds(Rectangle)} instead.
     */
    @Deprecated
    public void setBounds(Rectangle value)
    {
        moveBounds(value);
    }

    /**
     * Change the bounds of BooleanMask.<br>
     * Keep mask data intersecting from old bounds.
     */
    public void moveBounds(Rectangle value)
    {
        // bounds changed ?
        if (!bounds.equals(value))
        {
            // copy bounds as we modify them
            final Rectangle oldBounds = new Rectangle(bounds);
            final Rectangle newBounds = new Rectangle(value);

            // replace to origin (relative to old bounds)
            oldBounds.translate(-bounds.x, -bounds.y);
            newBounds.translate(-bounds.x, -bounds.y);

            final boolean[] newMask = new boolean[Math.max(0, newBounds.width) * Math.max(0, newBounds.height)];
            final Rectangle intersect = newBounds.intersection(oldBounds);

            if (!intersect.isEmpty())
            {
                int offSrc = 0;
                int offDst = 0;

                // adjust offset in source mask
                if (intersect.x > 0)
                    offSrc += intersect.x;
                if (intersect.y > 0)
                    offSrc += intersect.y * oldBounds.width;
                // adjust offset in destination mask
                if (newBounds.x < 0)
                    offDst += -newBounds.x;
                if (newBounds.y < 0)
                    offDst += -newBounds.y * newBounds.width;

                // preserve data
                for (int j = 0; j < intersect.height; j++)
                {
                    System.arraycopy(mask, offSrc, newMask, offDst, intersect.width);

                    offSrc += oldBounds.width;
                    offDst += newBounds.width;
                }
            }

            // update mask and bounds
            synchronized (this)
            {
                mask = newMask;
                bounds = value;
            }
        }
    }

    @Override
    public Object clone()
    {
        return new BooleanMask2D((Rectangle) bounds.clone(), mask.clone());
    }

}