1 /* 2 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.java2d.pipe; 27 28 import java.awt.Rectangle; 29 import java.awt.Shape; 30 import java.awt.geom.AffineTransform; 31 import java.awt.geom.RectangularShape; 32 33 import sun.java2d.loops.TransformHelper; 34 35 /** 36 * This class encapsulates a definition of a two dimensional region which 37 * consists of a number of Y ranges each containing multiple X bands. 38 * <p> 39 * A rectangular Region is allowed to have a null band list in which 40 * case the rectangular shape is defined by the bounding box parameters 41 * (lox, loy, hix, hiy). 42 * <p> 43 * The band list, if present, consists of a list of rows in ascending Y 44 * order, ending at endIndex which is the index beyond the end of the 45 * last row. Each row consists of at least 3 + 2n entries (n >= 1) 46 * where the first 3 entries specify the Y range as start, end, and 47 * the number of X ranges in that Y range. These 3 entries are 48 * followed by pairs of X coordinates in ascending order: 49 * <pre> 50 * bands[rowstart+0] = Y0; // starting Y coordinate 51 * bands[rowstart+1] = Y1; // ending Y coordinate - endY > startY 52 * bands[rowstart+2] = N; // number of X bands - N >= 1 53 * 54 * bands[rowstart+3] = X10; // starting X coordinate of first band 55 * bands[rowstart+4] = X11; // ending X coordinate of first band 56 * bands[rowstart+5] = X20; // starting X coordinate of second band 57 * bands[rowstart+6] = X21; // ending X coordinate of second band 58 * ... 59 * bands[rowstart+3+N*2-2] = XN0; // starting X coord of last band 60 * bands[rowstart+3+N*2-1] = XN1; // ending X coord of last band 61 * 62 * bands[rowstart+3+N*2] = ... // start of next Y row 63 * </pre> 64 */ 65 public class Region { 66 static final int INIT_SIZE = 50; 67 static final int GROW_SIZE = 50; 68 69 /** 70 * Immutable Region. 71 */ 72 private static final class ImmutableRegion extends Region { 73 protected ImmutableRegion(int lox, int loy, int hix, int hiy) { 74 super(lox, loy, hix, hiy); 75 } 76 77 // Override all the methods that mutate the object 78 public void appendSpans(sun.java2d.pipe.SpanIterator si) {} 79 public void setOutputArea(java.awt.Rectangle r) {} 80 public void setOutputAreaXYWH(int x, int y, int w, int h) {} 81 public void setOutputArea(int[] box) {} 82 public void setOutputAreaXYXY(int lox, int loy, int hix, int hiy) {} 83 } 84 85 public static final Region EMPTY_REGION = new ImmutableRegion(0, 0, 0, 0); 86 public static final Region WHOLE_REGION = new ImmutableRegion( 87 Integer.MIN_VALUE, 88 Integer.MIN_VALUE, 89 Integer.MAX_VALUE, 90 Integer.MAX_VALUE); 91 92 int lox; 93 int loy; 94 int hix; 95 int hiy; 96 97 int endIndex; 98 int[] bands; 99 100 private static native void initIDs(); 101 102 static { 103 initIDs(); 104 } 105 106 /** 107 * Adds the dimension {@code dim} to the coordinate 108 * {@code start} with appropriate clipping. If 109 * {@code dim} is non-positive then the method returns 110 * the start coordinate. If the sum overflows an integer 111 * data type then the method returns {@code Integer.MAX_VALUE}. 112 */ 113 public static int dimAdd(int start, int dim) { 114 if (dim <= 0) return start; 115 if ((dim += start) < start) return Integer.MAX_VALUE; 116 return dim; 117 } 118 119 /** 120 * Adds the delta {@code dv} to the value {@code v} with 121 * appropriate clipping to the bounds of Integer resolution. 122 * If the answer would be greater than {@code Integer.MAX_VALUE} 123 * then {@code Integer.MAX_VALUE} is returned. 124 * If the answer would be less than {@code Integer.MIN_VALUE} 125 * then {@code Integer.MIN_VALUE} is returned. 126 * Otherwise the sum is returned. 127 */ 128 public static int clipAdd(int v, int dv) { 129 int newv = v + dv; 130 if ((newv > v) != (dv > 0)) { 131 newv = (dv < 0) ? Integer.MIN_VALUE : Integer.MAX_VALUE; 132 } 133 return newv; 134 } 135 136 /** 137 * Multiply the scale factor {@code sv} and the value {@code v} with 138 * appropriate clipping to the bounds of Integer resolution. If the answer 139 * would be greater than {@code Integer.MAX_VALUE} then {@code 140 * Integer.MAX_VALUE} is returned. If the answer would be less than {@code 141 * Integer.MIN_VALUE} then {@code Integer.MIN_VALUE} is returned. Otherwise 142 * the multiplication is returned. 143 */ 144 public static int clipScale(final int v, final double sv) { 145 if (sv == 1.0) { 146 return v; 147 } 148 final double newv = v * sv; 149 if (newv < Integer.MIN_VALUE) { 150 return Integer.MIN_VALUE; 151 } 152 if (newv > Integer.MAX_VALUE) { 153 return Integer.MAX_VALUE; 154 } 155 return (int) Math.round(newv); 156 } 157 158 protected Region(int lox, int loy, int hix, int hiy) { 159 this.lox = lox; 160 this.loy = loy; 161 this.hix = hix; 162 this.hiy = hiy; 163 } 164 165 private Region(int lox, int loy, int hix, int hiy, int[] bands, int end) { 166 this.lox = lox; 167 this.loy = loy; 168 this.hix = hix; 169 this.hiy = hiy; 170 this.bands = bands; 171 this.endIndex = end; 172 } 173 174 /** 175 * Returns a Region object covering the pixels which would be 176 * touched by a fill or clip operation on a Graphics implementation 177 * on the specified Shape object under the optionally specified 178 * AffineTransform object. 179 * 180 * @param s a non-null Shape object specifying the geometry enclosing 181 * the pixels of interest 182 * @param at an optional {@code AffineTransform} to be applied to the 183 * coordinates as they are returned in the iteration, or 184 * {@code null} if untransformed coordinates are desired 185 */ 186 public static Region getInstance(Shape s, AffineTransform at) { 187 return getInstance(WHOLE_REGION, false, s, at); 188 } 189 190 /** 191 * Returns a Region object covering the pixels which would be 192 * touched by a fill or clip operation on a Graphics implementation 193 * on the specified Shape object under the optionally specified 194 * AffineTransform object further restricted by the specified 195 * device bounds. 196 * <p> 197 * Note that only the bounds of the specified Region are used to 198 * restrict the resulting Region. 199 * If devBounds is non-rectangular and clipping to the specific 200 * bands of devBounds is needed, then an intersection of the 201 * resulting Region with devBounds must be performed in a 202 * subsequent step. 203 * 204 * @param devBounds a non-null Region specifying some bounds to 205 * clip the geometry to 206 * @param s a non-null Shape object specifying the geometry enclosing 207 * the pixels of interest 208 * @param at an optional {@code AffineTransform} to be applied to the 209 * coordinates as they are returned in the iteration, or 210 * {@code null} if untransformed coordinates are desired 211 */ 212 public static Region getInstance(Region devBounds, 213 Shape s, AffineTransform at) 214 { 215 return getInstance(devBounds, false, s, at); 216 } 217 218 /** 219 * Returns a Region object covering the pixels which would be 220 * touched by a fill or clip operation on a Graphics implementation 221 * on the specified Shape object under the optionally specified 222 * AffineTransform object further restricted by the specified 223 * device bounds. 224 * If the normalize parameter is true then coordinate normalization 225 * is performed as per the 2D Graphics non-antialiasing implementation 226 * of the VALUE_STROKE_NORMALIZE hint. 227 * <p> 228 * Note that only the bounds of the specified Region are used to 229 * restrict the resulting Region. 230 * If devBounds is non-rectangular and clipping to the specific 231 * bands of devBounds is needed, then an intersection of the 232 * resulting Region with devBounds must be performed in a 233 * subsequent step. 234 * 235 * @param devBounds a non-null Region specifying some bounds to 236 * clip the geometry to 237 * @param normalize a boolean indicating whether or not to apply 238 * normalization 239 * @param s a non-null Shape object specifying the geometry enclosing 240 * the pixels of interest 241 * @param at an optional {@code AffineTransform} to be applied to the 242 * coordinates as they are returned in the iteration, or 243 * {@code null} if untransformed coordinates are desired 244 */ 245 public static Region getInstance(Region devBounds, boolean normalize, 246 Shape s, AffineTransform at) 247 { 248 // Optimize for empty shapes to avoid involving the SpanIterator 249 if (s instanceof RectangularShape && 250 ((RectangularShape)s).isEmpty()) 251 { 252 return EMPTY_REGION; 253 } 254 255 int box[] = new int[4]; 256 ShapeSpanIterator sr = new ShapeSpanIterator(normalize); 257 try { 258 sr.setOutputArea(devBounds); 259 sr.appendPath(s.getPathIterator(at)); 260 sr.getPathBox(box); 261 Region r = Region.getInstance(box); 262 r.appendSpans(sr); 263 return r; 264 } finally { 265 sr.dispose(); 266 } 267 } 268 269 /** 270 * Returns a Region object with a rectangle of interest specified by the 271 * indicated rectangular area in lox, loy, hix, hiy and edges array, which 272 * is located relative to the rectangular area. Edges array - 0,1 are y 273 * range, 2N,2N+1 are x ranges, 1 per y range. 274 * 275 * @see TransformHelper 276 */ 277 static Region getInstance(final int lox, final int loy, final int hix, 278 final int hiy, final int[] edges) { 279 final int y1 = edges[0]; 280 final int y2 = edges[1]; 281 if (hiy <= loy || hix <= lox || y2 <= y1) { 282 return EMPTY_REGION; 283 } 284 // rowsNum * (3 + 1 * 2) 285 final int[] bands = new int[(y2 - y1) * 5]; 286 int end = 0; 287 int index = 2; 288 for (int y = y1; y < y2; ++y) { 289 final int spanlox = Math.max(clipAdd(lox, edges[index++]), lox); 290 final int spanhix = Math.min(clipAdd(lox, edges[index++]), hix); 291 if (spanlox < spanhix) { 292 final int spanloy = Math.max(clipAdd(loy, y), loy); 293 final int spanhiy = Math.min(clipAdd(spanloy, 1), hiy); 294 if (spanloy < spanhiy) { 295 bands[end++] = spanloy; 296 bands[end++] = spanhiy; 297 bands[end++] = 1; // 1 span per row 298 bands[end++] = spanlox; 299 bands[end++] = spanhix; 300 } 301 } 302 } 303 return end != 0 ? new Region(lox, loy, hix, hiy, bands, end) 304 : EMPTY_REGION; 305 } 306 307 /** 308 * Returns a Region object with a rectangle of interest specified 309 * by the indicated Rectangle object. 310 * <p> 311 * This method can also be used to create a simple rectangular 312 * region. 313 */ 314 public static Region getInstance(Rectangle r) { 315 return Region.getInstanceXYWH(r.x, r.y, r.width, r.height); 316 } 317 318 /** 319 * Returns a Region object with a rectangle of interest specified 320 * by the indicated rectangular area in x, y, width, height format. 321 * <p> 322 * This method can also be used to create a simple rectangular 323 * region. 324 */ 325 public static Region getInstanceXYWH(int x, int y, int w, int h) { 326 return Region.getInstanceXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 327 } 328 329 /** 330 * Returns a Region object with a rectangle of interest specified 331 * by the indicated span array. 332 * <p> 333 * This method can also be used to create a simple rectangular 334 * region. 335 */ 336 public static Region getInstance(int box[]) { 337 return new Region(box[0], box[1], box[2], box[3]); 338 } 339 340 /** 341 * Returns a Region object with a rectangle of interest specified 342 * by the indicated rectangular area in lox, loy, hix, hiy format. 343 * <p> 344 * This method can also be used to create a simple rectangular 345 * region. 346 */ 347 public static Region getInstanceXYXY(int lox, int loy, int hix, int hiy) { 348 return new Region(lox, loy, hix, hiy); 349 } 350 351 /** 352 * Sets the rectangle of interest for storing and returning 353 * region bands. 354 * <p> 355 * This method can also be used to initialize a simple rectangular 356 * region. 357 */ 358 public void setOutputArea(Rectangle r) { 359 setOutputAreaXYWH(r.x, r.y, r.width, r.height); 360 } 361 362 /** 363 * Sets the rectangle of interest for storing and returning 364 * region bands. The rectangle is specified in x, y, width, height 365 * format and appropriate clipping is performed as per the method 366 * {@code dimAdd}. 367 * <p> 368 * This method can also be used to initialize a simple rectangular 369 * region. 370 */ 371 public void setOutputAreaXYWH(int x, int y, int w, int h) { 372 setOutputAreaXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 373 } 374 375 /** 376 * Sets the rectangle of interest for storing and returning 377 * region bands. The rectangle is specified as a span array. 378 * <p> 379 * This method can also be used to initialize a simple rectangular 380 * region. 381 */ 382 public void setOutputArea(int box[]) { 383 this.lox = box[0]; 384 this.loy = box[1]; 385 this.hix = box[2]; 386 this.hiy = box[3]; 387 } 388 389 /** 390 * Sets the rectangle of interest for storing and returning 391 * region bands. The rectangle is specified in lox, loy, 392 * hix, hiy format. 393 * <p> 394 * This method can also be used to initialize a simple rectangular 395 * region. 396 */ 397 public void setOutputAreaXYXY(int lox, int loy, int hix, int hiy) { 398 this.lox = lox; 399 this.loy = loy; 400 this.hix = hix; 401 this.hiy = hiy; 402 } 403 404 /** 405 * Appends the list of spans returned from the indicated 406 * SpanIterator. Each span must be at a higher starting 407 * Y coordinate than the previous data or it must have a 408 * Y range equal to the highest Y band in the region and a 409 * higher X coordinate than any of the spans in that band. 410 */ 411 public void appendSpans(SpanIterator si) { 412 int[] box = new int[6]; 413 414 while (si.nextSpan(box)) { 415 appendSpan(box); 416 } 417 418 endRow(box); 419 calcBBox(); 420 } 421 422 /** 423 * Returns a Region object that represents the same list of rectangles as 424 * the current Region object, scaled by the specified sx, sy factors. 425 */ 426 public Region getScaledRegion(final double sx, final double sy) { 427 if (sx == 0 || sy == 0 || this == EMPTY_REGION) { 428 return EMPTY_REGION; 429 } 430 if ((sx == 1.0 && sy == 1.0) || (this == WHOLE_REGION)) { 431 return this; 432 } 433 434 int tlox = clipScale(lox, sx); 435 int tloy = clipScale(loy, sy); 436 int thix = clipScale(hix, sx); 437 int thiy = clipScale(hiy, sy); 438 Region ret = new Region(tlox, tloy, thix, thiy); 439 int bands[] = this.bands; 440 if (bands != null) { 441 int end = endIndex; 442 int newbands[] = new int[end]; 443 int i = 0; // index for source bands 444 int j = 0; // index for translated newbands 445 int ncol; 446 while (i < end) { 447 int y1, y2; 448 newbands[j++] = y1 = clipScale(bands[i++], sy); 449 newbands[j++] = y2 = clipScale(bands[i++], sy); 450 newbands[j++] = ncol = bands[i++]; 451 int savej = j; 452 if (y1 < y2) { 453 while (--ncol >= 0) { 454 int x1 = clipScale(bands[i++], sx); 455 int x2 = clipScale(bands[i++], sx); 456 if (x1 < x2) { 457 newbands[j++] = x1; 458 newbands[j++] = x2; 459 } 460 } 461 } else { 462 i += ncol * 2; 463 } 464 // Did we get any non-empty bands in this row? 465 if (j > savej) { 466 newbands[savej-1] = (j - savej) / 2; 467 } else { 468 j = savej - 3; 469 } 470 } 471 if (j <= 5) { 472 if (j < 5) { 473 // No rows or bands were generated... 474 ret.lox = ret.loy = ret.hix = ret.hiy = 0; 475 } else { 476 // Only generated one single rect in the end... 477 ret.loy = newbands[0]; 478 ret.hiy = newbands[1]; 479 ret.lox = newbands[3]; 480 ret.hix = newbands[4]; 481 } 482 // ret.endIndex and ret.bands were never initialized... 483 // ret.endIndex = 0; 484 // ret.newbands = null; 485 } else { 486 // Generated multiple bands and/or multiple rows... 487 ret.endIndex = j; 488 ret.bands = newbands; 489 } 490 } 491 return ret; 492 } 493 494 495 /** 496 * Returns a Region object that represents the same list of 497 * rectangles as the current Region object, translated by 498 * the specified dx, dy translation factors. 499 */ 500 public Region getTranslatedRegion(int dx, int dy) { 501 if ((dx | dy) == 0) { 502 return this; 503 } 504 int tlox = lox + dx; 505 int tloy = loy + dy; 506 int thix = hix + dx; 507 int thiy = hiy + dy; 508 if ((tlox > lox) != (dx > 0) || 509 (tloy > loy) != (dy > 0) || 510 (thix > hix) != (dx > 0) || 511 (thiy > hiy) != (dy > 0)) 512 { 513 return getSafeTranslatedRegion(dx, dy); 514 } 515 Region ret = new Region(tlox, tloy, thix, thiy); 516 int bands[] = this.bands; 517 if (bands != null) { 518 int end = endIndex; 519 ret.endIndex = end; 520 int newbands[] = new int[end]; 521 ret.bands = newbands; 522 int i = 0; 523 int ncol; 524 while (i < end) { 525 newbands[i] = bands[i] + dy; i++; 526 newbands[i] = bands[i] + dy; i++; 527 newbands[i] = ncol = bands[i]; i++; 528 while (--ncol >= 0) { 529 newbands[i] = bands[i] + dx; i++; 530 newbands[i] = bands[i] + dx; i++; 531 } 532 } 533 } 534 return ret; 535 } 536 537 private Region getSafeTranslatedRegion(int dx, int dy) { 538 int tlox = clipAdd(lox, dx); 539 int tloy = clipAdd(loy, dy); 540 int thix = clipAdd(hix, dx); 541 int thiy = clipAdd(hiy, dy); 542 Region ret = new Region(tlox, tloy, thix, thiy); 543 int bands[] = this.bands; 544 if (bands != null) { 545 int end = endIndex; 546 int newbands[] = new int[end]; 547 int i = 0; // index for source bands 548 int j = 0; // index for translated newbands 549 int ncol; 550 while (i < end) { 551 int y1, y2; 552 newbands[j++] = y1 = clipAdd(bands[i++], dy); 553 newbands[j++] = y2 = clipAdd(bands[i++], dy); 554 newbands[j++] = ncol = bands[i++]; 555 int savej = j; 556 if (y1 < y2) { 557 while (--ncol >= 0) { 558 int x1 = clipAdd(bands[i++], dx); 559 int x2 = clipAdd(bands[i++], dx); 560 if (x1 < x2) { 561 newbands[j++] = x1; 562 newbands[j++] = x2; 563 } 564 } 565 } else { 566 i += ncol * 2; 567 } 568 // Did we get any non-empty bands in this row? 569 if (j > savej) { 570 newbands[savej-1] = (j - savej) / 2; 571 } else { 572 j = savej - 3; 573 } 574 } 575 if (j <= 5) { 576 if (j < 5) { 577 // No rows or bands were generated... 578 ret.lox = ret.loy = ret.hix = ret.hiy = 0; 579 } else { 580 // Only generated one single rect in the end... 581 ret.loy = newbands[0]; 582 ret.hiy = newbands[1]; 583 ret.lox = newbands[3]; 584 ret.hix = newbands[4]; 585 } 586 // ret.endIndex and ret.bands were never initialized... 587 // ret.endIndex = 0; 588 // ret.newbands = null; 589 } else { 590 // Generated multiple bands and/or multiple rows... 591 ret.endIndex = j; 592 ret.bands = newbands; 593 } 594 } 595 return ret; 596 } 597 598 /** 599 * Returns a Region object that represents the intersection of 600 * this object with the specified Rectangle. The return value 601 * may be this same object if no clipping occurs. 602 */ 603 public Region getIntersection(Rectangle r) { 604 return getIntersectionXYWH(r.x, r.y, r.width, r.height); 605 } 606 607 /** 608 * Returns a Region object that represents the intersection of 609 * this object with the specified rectangular area. The return 610 * value may be this same object if no clipping occurs. 611 */ 612 public Region getIntersectionXYWH(int x, int y, int w, int h) { 613 return getIntersectionXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 614 } 615 616 /** 617 * Returns a Region object that represents the intersection of 618 * this object with the specified rectangular area. The return 619 * value may be this same object if no clipping occurs. 620 */ 621 public Region getIntersectionXYXY(int lox, int loy, int hix, int hiy) { 622 if (isInsideXYXY(lox, loy, hix, hiy)) { 623 return this; 624 } 625 Region ret = new Region((lox < this.lox) ? this.lox : lox, 626 (loy < this.loy) ? this.loy : loy, 627 (hix > this.hix) ? this.hix : hix, 628 (hiy > this.hiy) ? this.hiy : hiy); 629 if (bands != null) { 630 ret.appendSpans(this.getSpanIterator()); 631 } 632 return ret; 633 } 634 635 /** 636 * Returns a Region object that represents the intersection of this 637 * object with the specified Region object. 638 * <p> 639 * If {@code A} and {@code B} are both Region Objects and 640 * {@code C = A.getIntersection(B);} then a point will 641 * be contained in {@code C} iff it is contained in both 642 * {@code A} and {@code B}. 643 * <p> 644 * The return value may be this same object or the argument 645 * Region object if no clipping occurs. 646 */ 647 public Region getIntersection(Region r) { 648 if (this.isInsideQuickCheck(r)) { 649 return this; 650 } 651 if (r.isInsideQuickCheck(this)) { 652 return r; 653 } 654 Region ret = new Region((r.lox < this.lox) ? this.lox : r.lox, 655 (r.loy < this.loy) ? this.loy : r.loy, 656 (r.hix > this.hix) ? this.hix : r.hix, 657 (r.hiy > this.hiy) ? this.hiy : r.hiy); 658 if (!ret.isEmpty()) { 659 ret.filterSpans(this, r, INCLUDE_COMMON); 660 } 661 return ret; 662 } 663 664 /** 665 * Returns a Region object that represents the union of this 666 * object with the specified Region object. 667 * <p> 668 * If {@code A} and {@code B} are both Region Objects and 669 * {@code C = A.getUnion(B);} then a point will 670 * be contained in {@code C} iff it is contained in either 671 * {@code A} or {@code B}. 672 * <p> 673 * The return value may be this same object or the argument 674 * Region object if no augmentation occurs. 675 */ 676 public Region getUnion(Region r) { 677 if (r.isEmpty() || r.isInsideQuickCheck(this)) { 678 return this; 679 } 680 if (this.isEmpty() || this.isInsideQuickCheck(r)) { 681 return r; 682 } 683 Region ret = new Region((r.lox > this.lox) ? this.lox : r.lox, 684 (r.loy > this.loy) ? this.loy : r.loy, 685 (r.hix < this.hix) ? this.hix : r.hix, 686 (r.hiy < this.hiy) ? this.hiy : r.hiy); 687 ret.filterSpans(this, r, INCLUDE_A | INCLUDE_B | INCLUDE_COMMON); 688 return ret; 689 } 690 691 /** 692 * Returns a Region object that represents the difference of the 693 * specified Region object subtracted from this object. 694 * <p> 695 * If {@code A} and {@code B} are both Region Objects and 696 * {@code C = A.getDifference(B);} then a point will 697 * be contained in {@code C} iff it is contained in 698 * {@code A} but not contained in {@code B}. 699 * <p> 700 * The return value may be this same object or the argument 701 * Region object if no clipping occurs. 702 */ 703 public Region getDifference(Region r) { 704 if (!r.intersectsQuickCheck(this)) { 705 return this; 706 } 707 if (this.isInsideQuickCheck(r)) { 708 return EMPTY_REGION; 709 } 710 Region ret = new Region(this.lox, this.loy, this.hix, this.hiy); 711 ret.filterSpans(this, r, INCLUDE_A); 712 return ret; 713 } 714 715 /** 716 * Returns a Region object that represents the exclusive or of this 717 * object with the specified Region object. 718 * <p> 719 * If {@code A} and {@code B} are both Region Objects and 720 * {@code C = A.getExclusiveOr(B);} then a point will 721 * be contained in {@code C} iff it is contained in either 722 * {@code A} or {@code B}, but not if it is contained in both. 723 * <p> 724 * The return value may be this same object or the argument 725 * Region object if either is empty. 726 */ 727 public Region getExclusiveOr(Region r) { 728 if (r.isEmpty()) { 729 return this; 730 } 731 if (this.isEmpty()) { 732 return r; 733 } 734 Region ret = new Region((r.lox > this.lox) ? this.lox : r.lox, 735 (r.loy > this.loy) ? this.loy : r.loy, 736 (r.hix < this.hix) ? this.hix : r.hix, 737 (r.hiy < this.hiy) ? this.hiy : r.hiy); 738 ret.filterSpans(this, r, INCLUDE_A | INCLUDE_B); 739 return ret; 740 } 741 742 static final int INCLUDE_A = 1; 743 static final int INCLUDE_B = 2; 744 static final int INCLUDE_COMMON = 4; 745 746 private void filterSpans(Region ra, Region rb, int flags) { 747 int abands[] = ra.bands; 748 int bbands[] = rb.bands; 749 if (abands == null) { 750 abands = new int[] {ra.loy, ra.hiy, 1, ra.lox, ra.hix}; 751 } 752 if (bbands == null) { 753 bbands = new int[] {rb.loy, rb.hiy, 1, rb.lox, rb.hix}; 754 } 755 int box[] = new int[6]; 756 int acolstart = 0; 757 int ay1 = abands[acolstart++]; 758 int ay2 = abands[acolstart++]; 759 int acolend = abands[acolstart++]; 760 acolend = acolstart + 2 * acolend; 761 int bcolstart = 0; 762 int by1 = bbands[bcolstart++]; 763 int by2 = bbands[bcolstart++]; 764 int bcolend = bbands[bcolstart++]; 765 bcolend = bcolstart + 2 * bcolend; 766 int y = loy; 767 while (y < hiy) { 768 if (y >= ay2) { 769 if (acolend < ra.endIndex) { 770 acolstart = acolend; 771 ay1 = abands[acolstart++]; 772 ay2 = abands[acolstart++]; 773 acolend = abands[acolstart++]; 774 acolend = acolstart + 2 * acolend; 775 } else { 776 if ((flags & INCLUDE_B) == 0) break; 777 ay1 = ay2 = hiy; 778 } 779 continue; 780 } 781 if (y >= by2) { 782 if (bcolend < rb.endIndex) { 783 bcolstart = bcolend; 784 by1 = bbands[bcolstart++]; 785 by2 = bbands[bcolstart++]; 786 bcolend = bbands[bcolstart++]; 787 bcolend = bcolstart + 2 * bcolend; 788 } else { 789 if ((flags & INCLUDE_A) == 0) break; 790 by1 = by2 = hiy; 791 } 792 continue; 793 } 794 int yend; 795 if (y < by1) { 796 if (y < ay1) { 797 y = Math.min(ay1, by1); 798 continue; 799 } 800 // We are in a set of rows that belong only to A 801 yend = Math.min(ay2, by1); 802 if ((flags & INCLUDE_A) != 0) { 803 box[1] = y; 804 box[3] = yend; 805 int acol = acolstart; 806 while (acol < acolend) { 807 box[0] = abands[acol++]; 808 box[2] = abands[acol++]; 809 appendSpan(box); 810 } 811 } 812 } else if (y < ay1) { 813 // We are in a set of rows that belong only to B 814 yend = Math.min(by2, ay1); 815 if ((flags & INCLUDE_B) != 0) { 816 box[1] = y; 817 box[3] = yend; 818 int bcol = bcolstart; 819 while (bcol < bcolend) { 820 box[0] = bbands[bcol++]; 821 box[2] = bbands[bcol++]; 822 appendSpan(box); 823 } 824 } 825 } else { 826 // We are in a set of rows that belong to both A and B 827 yend = Math.min(ay2, by2); 828 box[1] = y; 829 box[3] = yend; 830 int acol = acolstart; 831 int bcol = bcolstart; 832 int ax1 = abands[acol++]; 833 int ax2 = abands[acol++]; 834 int bx1 = bbands[bcol++]; 835 int bx2 = bbands[bcol++]; 836 int x = Math.min(ax1, bx1); 837 if (x < lox) x = lox; 838 while (x < hix) { 839 if (x >= ax2) { 840 if (acol < acolend) { 841 ax1 = abands[acol++]; 842 ax2 = abands[acol++]; 843 } else { 844 if ((flags & INCLUDE_B) == 0) break; 845 ax1 = ax2 = hix; 846 } 847 continue; 848 } 849 if (x >= bx2) { 850 if (bcol < bcolend) { 851 bx1 = bbands[bcol++]; 852 bx2 = bbands[bcol++]; 853 } else { 854 if ((flags & INCLUDE_A) == 0) break; 855 bx1 = bx2 = hix; 856 } 857 continue; 858 } 859 int xend; 860 boolean appendit; 861 if (x < bx1) { 862 if (x < ax1) { 863 xend = Math.min(ax1, bx1); 864 appendit = false; 865 } else { 866 xend = Math.min(ax2, bx1); 867 appendit = ((flags & INCLUDE_A) != 0); 868 } 869 } else if (x < ax1) { 870 xend = Math.min(ax1, bx2); 871 appendit = ((flags & INCLUDE_B) != 0); 872 } else { 873 xend = Math.min(ax2, bx2); 874 appendit = ((flags & INCLUDE_COMMON) != 0); 875 } 876 if (appendit) { 877 box[0] = x; 878 box[2] = xend; 879 appendSpan(box); 880 } 881 x = xend; 882 } 883 } 884 y = yend; 885 } 886 endRow(box); 887 calcBBox(); 888 } 889 890 /** 891 * Returns a Region object that represents the bounds of the 892 * intersection of this object with the bounds of the specified 893 * Region object. 894 * <p> 895 * The return value may be this same object if no clipping occurs 896 * and this Region is rectangular. 897 */ 898 public Region getBoundsIntersection(Rectangle r) { 899 return getBoundsIntersectionXYWH(r.x, r.y, r.width, r.height); 900 } 901 902 /** 903 * Returns a Region object that represents the bounds of the 904 * intersection of this object with the bounds of the specified 905 * rectangular area in x, y, width, height format. 906 * <p> 907 * The return value may be this same object if no clipping occurs 908 * and this Region is rectangular. 909 */ 910 public Region getBoundsIntersectionXYWH(int x, int y, int w, int h) { 911 return getBoundsIntersectionXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 912 } 913 914 /** 915 * Returns a Region object that represents the bounds of the 916 * intersection of this object with the bounds of the specified 917 * rectangular area in lox, loy, hix, hiy format. 918 * <p> 919 * The return value may be this same object if no clipping occurs 920 * and this Region is rectangular. 921 */ 922 public Region getBoundsIntersectionXYXY(int lox, int loy, 923 int hix, int hiy) 924 { 925 if (this.bands == null && 926 this.lox >= lox && this.loy >= loy && 927 this.hix <= hix && this.hiy <= hiy) 928 { 929 return this; 930 } 931 return new Region((lox < this.lox) ? this.lox : lox, 932 (loy < this.loy) ? this.loy : loy, 933 (hix > this.hix) ? this.hix : hix, 934 (hiy > this.hiy) ? this.hiy : hiy); 935 } 936 937 /** 938 * Returns a Region object that represents the intersection of 939 * this object with the bounds of the specified Region object. 940 * <p> 941 * The return value may be this same object or the argument 942 * Region object if no clipping occurs and the Regions are 943 * rectangular. 944 */ 945 public Region getBoundsIntersection(Region r) { 946 if (this.encompasses(r)) { 947 return r; 948 } 949 if (r.encompasses(this)) { 950 return this; 951 } 952 return new Region((r.lox < this.lox) ? this.lox : r.lox, 953 (r.loy < this.loy) ? this.loy : r.loy, 954 (r.hix > this.hix) ? this.hix : r.hix, 955 (r.hiy > this.hiy) ? this.hiy : r.hiy); 956 } 957 958 /** 959 * Appends a single span defined by the 4 parameters 960 * spanlox, spanloy, spanhix, spanhiy. 961 * This span must be at a higher starting Y coordinate than 962 * the previous data or it must have a Y range equal to the 963 * highest Y band in the region and a higher X coordinate 964 * than any of the spans in that band. 965 */ 966 private void appendSpan(int box[]) { 967 int spanlox, spanloy, spanhix, spanhiy; 968 if ((spanlox = box[0]) < lox) spanlox = lox; 969 if ((spanloy = box[1]) < loy) spanloy = loy; 970 if ((spanhix = box[2]) > hix) spanhix = hix; 971 if ((spanhiy = box[3]) > hiy) spanhiy = hiy; 972 if (spanhix <= spanlox || spanhiy <= spanloy) { 973 return; 974 } 975 976 int curYrow = box[4]; 977 if (endIndex == 0 || spanloy >= bands[curYrow + 1]) { 978 if (bands == null) { 979 bands = new int[INIT_SIZE]; 980 } else { 981 needSpace(5); 982 endRow(box); 983 curYrow = box[4]; 984 } 985 bands[endIndex++] = spanloy; 986 bands[endIndex++] = spanhiy; 987 bands[endIndex++] = 0; 988 } else if (spanloy == bands[curYrow] && 989 spanhiy == bands[curYrow + 1] && 990 spanlox >= bands[endIndex - 1]) { 991 if (spanlox == bands[endIndex - 1]) { 992 bands[endIndex - 1] = spanhix; 993 return; 994 } 995 needSpace(2); 996 } else { 997 throw new InternalError("bad span"); 998 } 999 bands[endIndex++] = spanlox; 1000 bands[endIndex++] = spanhix; 1001 bands[curYrow + 2]++; 1002 } 1003 1004 private void needSpace(int num) { 1005 if (endIndex + num >= bands.length) { 1006 int[] newbands = new int[bands.length + GROW_SIZE]; 1007 System.arraycopy(bands, 0, newbands, 0, endIndex); 1008 bands = newbands; 1009 } 1010 } 1011 1012 private void endRow(int box[]) { 1013 int cur = box[4]; 1014 int prev = box[5]; 1015 if (cur > prev) { 1016 int[] bands = this.bands; 1017 if (bands[prev + 1] == bands[cur] && 1018 bands[prev + 2] == bands[cur + 2]) 1019 { 1020 int num = bands[cur + 2] * 2; 1021 cur += 3; 1022 prev += 3; 1023 while (num > 0) { 1024 if (bands[cur++] != bands[prev++]) { 1025 break; 1026 } 1027 num--; 1028 } 1029 if (num == 0) { 1030 // prev == box[4] 1031 bands[box[5] + 1] = bands[prev + 1]; 1032 endIndex = prev; 1033 return; 1034 } 1035 } 1036 } 1037 box[5] = box[4]; 1038 box[4] = endIndex; 1039 } 1040 1041 private void calcBBox() { 1042 int[] bands = this.bands; 1043 if (endIndex <= 5) { 1044 if (endIndex == 0) { 1045 lox = loy = hix = hiy = 0; 1046 } else { 1047 loy = bands[0]; 1048 hiy = bands[1]; 1049 lox = bands[3]; 1050 hix = bands[4]; 1051 endIndex = 0; 1052 } 1053 this.bands = null; 1054 return; 1055 } 1056 int lox = this.hix; 1057 int hix = this.lox; 1058 int hiyindex = 0; 1059 1060 int i = 0; 1061 while (i < endIndex) { 1062 hiyindex = i; 1063 int numbands = bands[i + 2]; 1064 i += 3; 1065 if (lox > bands[i]) { 1066 lox = bands[i]; 1067 } 1068 i += numbands * 2; 1069 if (hix < bands[i - 1]) { 1070 hix = bands[i - 1]; 1071 } 1072 } 1073 1074 this.lox = lox; 1075 this.loy = bands[0]; 1076 this.hix = hix; 1077 this.hiy = bands[hiyindex + 1]; 1078 } 1079 1080 /** 1081 * Returns the lowest X coordinate in the Region. 1082 */ 1083 public final int getLoX() { 1084 return lox; 1085 } 1086 1087 /** 1088 * Returns the lowest Y coordinate in the Region. 1089 */ 1090 public final int getLoY() { 1091 return loy; 1092 } 1093 1094 /** 1095 * Returns the highest X coordinate in the Region. 1096 */ 1097 public final int getHiX() { 1098 return hix; 1099 } 1100 1101 /** 1102 * Returns the highest Y coordinate in the Region. 1103 */ 1104 public final int getHiY() { 1105 return hiy; 1106 } 1107 1108 /** 1109 * Returns the width of this Region clipped to the range (0 - MAX_INT). 1110 */ 1111 public final int getWidth() { 1112 if (hix < lox) return 0; 1113 int w; 1114 if ((w = hix - lox) < 0) { 1115 w = Integer.MAX_VALUE; 1116 } 1117 return w; 1118 } 1119 1120 /** 1121 * Returns the height of this Region clipped to the range (0 - MAX_INT). 1122 */ 1123 public final int getHeight() { 1124 if (hiy < loy) return 0; 1125 int h; 1126 if ((h = hiy - loy) < 0) { 1127 h = Integer.MAX_VALUE; 1128 } 1129 return h; 1130 } 1131 1132 /** 1133 * Returns true iff this Region encloses no area. 1134 */ 1135 public boolean isEmpty() { 1136 return (hix <= lox || hiy <= loy); 1137 } 1138 1139 /** 1140 * Returns true iff this Region represents a single simple 1141 * rectangular area. 1142 */ 1143 public boolean isRectangular() { 1144 return (bands == null); 1145 } 1146 1147 /** 1148 * Returns true iff this Region contains the specified coordinate. 1149 */ 1150 public boolean contains(int x, int y) { 1151 if (x < lox || x >= hix || y < loy || y >= hiy) return false; 1152 if (bands == null) return true; 1153 int i = 0; 1154 while (i < endIndex) { 1155 if (y < bands[i++]) { 1156 return false; 1157 } 1158 if (y >= bands[i++]) { 1159 int numspans = bands[i++]; 1160 i += numspans * 2; 1161 } else { 1162 int end = bands[i++]; 1163 end = i + end * 2; 1164 while (i < end) { 1165 if (x < bands[i++]) return false; 1166 if (x < bands[i++]) return true; 1167 } 1168 return false; 1169 } 1170 } 1171 return false; 1172 } 1173 1174 /** 1175 * Returns true iff this Region lies inside the indicated 1176 * rectangular area specified in x, y, width, height format 1177 * with appropriate clipping performed as per the dimAdd method. 1178 */ 1179 public boolean isInsideXYWH(int x, int y, int w, int h) { 1180 return isInsideXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 1181 } 1182 1183 /** 1184 * Returns true iff this Region lies inside the indicated 1185 * rectangular area specified in lox, loy, hix, hiy format. 1186 */ 1187 public boolean isInsideXYXY(int lox, int loy, int hix, int hiy) { 1188 return (this.lox >= lox && this.loy >= loy && 1189 this.hix <= hix && this.hiy <= hiy); 1190 1191 } 1192 1193 /** 1194 * Quickly checks if this Region lies inside the specified 1195 * Region object. 1196 * <p> 1197 * This method will return false if the specified Region 1198 * object is not a simple rectangle. 1199 */ 1200 public boolean isInsideQuickCheck(Region r) { 1201 return (r.bands == null && 1202 r.lox <= this.lox && r.loy <= this.loy && 1203 r.hix >= this.hix && r.hiy >= this.hiy); 1204 } 1205 1206 /** 1207 * Quickly checks if this Region intersects the specified 1208 * rectangular area specified in lox, loy, hix, hiy format. 1209 * <p> 1210 * This method tests only against the bounds of this region 1211 * and does not bother to test if the rectangular region 1212 * actually intersects any bands. 1213 */ 1214 public boolean intersectsQuickCheckXYXY(int lox, int loy, 1215 int hix, int hiy) 1216 { 1217 return (hix > this.lox && lox < this.hix && 1218 hiy > this.loy && loy < this.hiy); 1219 } 1220 1221 /** 1222 * Quickly checks if this Region intersects the specified 1223 * Region object. 1224 * <p> 1225 * This method tests only against the bounds of this region 1226 * and does not bother to test if the rectangular region 1227 * actually intersects any bands. 1228 */ 1229 public boolean intersectsQuickCheck(Region r) { 1230 return (r.hix > this.lox && r.lox < this.hix && 1231 r.hiy > this.loy && r.loy < this.hiy); 1232 } 1233 1234 /** 1235 * Quickly checks if this Region surrounds the specified 1236 * Region object. 1237 * <p> 1238 * This method will return false if this Region object is 1239 * not a simple rectangle. 1240 */ 1241 public boolean encompasses(Region r) { 1242 return (this.bands == null && 1243 this.lox <= r.lox && this.loy <= r.loy && 1244 this.hix >= r.hix && this.hiy >= r.hiy); 1245 } 1246 1247 /** 1248 * Quickly checks if this Region surrounds the specified 1249 * rectangular area specified in x, y, width, height format. 1250 * <p> 1251 * This method will return false if this Region object is 1252 * not a simple rectangle. 1253 */ 1254 public boolean encompassesXYWH(int x, int y, int w, int h) { 1255 return encompassesXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 1256 } 1257 1258 /** 1259 * Quickly checks if this Region surrounds the specified 1260 * rectangular area specified in lox, loy, hix, hiy format. 1261 * <p> 1262 * This method will return false if this Region object is 1263 * not a simple rectangle. 1264 */ 1265 public boolean encompassesXYXY(int lox, int loy, int hix, int hiy) { 1266 return (this.bands == null && 1267 this.lox <= lox && this.loy <= loy && 1268 this.hix >= hix && this.hiy >= hiy); 1269 } 1270 1271 /** 1272 * Gets the bbox of the available spans, clipped to the OutputArea. 1273 */ 1274 public void getBounds(int pathbox[]) { 1275 pathbox[0] = lox; 1276 pathbox[1] = loy; 1277 pathbox[2] = hix; 1278 pathbox[3] = hiy; 1279 } 1280 1281 /** 1282 * Clips the indicated bbox array to the bounds of this Region. 1283 */ 1284 public void clipBoxToBounds(int bbox[]) { 1285 if (bbox[0] < lox) bbox[0] = lox; 1286 if (bbox[1] < loy) bbox[1] = loy; 1287 if (bbox[2] > hix) bbox[2] = hix; 1288 if (bbox[3] > hiy) bbox[3] = hiy; 1289 } 1290 1291 /** 1292 * Gets an iterator object to iterate over the spans in this region. 1293 */ 1294 public RegionIterator getIterator() { 1295 return new RegionIterator(this); 1296 } 1297 1298 /** 1299 * Gets a span iterator object that iterates over the spans in this region 1300 */ 1301 public SpanIterator getSpanIterator() { 1302 return new RegionSpanIterator(this); 1303 } 1304 1305 /** 1306 * Gets a span iterator object that iterates over the spans in this region 1307 * but clipped to the bounds given in the argument (xlo, ylo, xhi, yhi). 1308 */ 1309 public SpanIterator getSpanIterator(int bbox[]) { 1310 SpanIterator result = getSpanIterator(); 1311 result.intersectClipBox(bbox[0], bbox[1], bbox[2], bbox[3]); 1312 return result; 1313 } 1314 1315 /** 1316 * Returns a SpanIterator that is the argument iterator filtered by 1317 * this region. 1318 */ 1319 public SpanIterator filter(SpanIterator si) { 1320 if (bands == null) { 1321 si.intersectClipBox(lox, loy, hix, hiy); 1322 } else { 1323 si = new RegionClipSpanIterator(this, si); 1324 } 1325 return si; 1326 } 1327 1328 public String toString() { 1329 StringBuilder sb = new StringBuilder(); 1330 sb.append("Region[["); 1331 sb.append(lox); 1332 sb.append(", "); 1333 sb.append(loy); 1334 sb.append(" => "); 1335 sb.append(hix); 1336 sb.append(", "); 1337 sb.append(hiy); 1338 sb.append("]"); 1339 if (bands != null) { 1340 int col = 0; 1341 while (col < endIndex) { 1342 sb.append("y{"); 1343 sb.append(bands[col++]); 1344 sb.append(","); 1345 sb.append(bands[col++]); 1346 sb.append("}["); 1347 int end = bands[col++]; 1348 end = col + end * 2; 1349 while (col < end) { 1350 sb.append("x("); 1351 sb.append(bands[col++]); 1352 sb.append(", "); 1353 sb.append(bands[col++]); 1354 sb.append(")"); 1355 } 1356 sb.append("]"); 1357 } 1358 } 1359 sb.append("]"); 1360 return sb.toString(); 1361 } 1362 1363 public int hashCode() { 1364 return (isEmpty() ? 0 : (lox * 3 + loy * 5 + hix * 7 + hiy * 9)); 1365 } 1366 1367 public boolean equals(Object o) { 1368 if (!(o instanceof Region)) { 1369 return false; 1370 } 1371 Region r = (Region) o; 1372 if (this.isEmpty()) { 1373 return r.isEmpty(); 1374 } else if (r.isEmpty()) { 1375 return false; 1376 } 1377 if (r.lox != this.lox || r.loy != this.loy || 1378 r.hix != this.hix || r.hiy != this.hiy) 1379 { 1380 return false; 1381 } 1382 if (this.bands == null) { 1383 return (r.bands == null); 1384 } else if (r.bands == null) { 1385 return false; 1386 } 1387 if (this.endIndex != r.endIndex) { 1388 return false; 1389 } 1390 int abands[] = this.bands; 1391 int bbands[] = r.bands; 1392 for (int i = 0; i < endIndex; i++) { 1393 if (abands[i] != bbands[i]) { 1394 return false; 1395 } 1396 } 1397 return true; 1398 } 1399 }