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}