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</code> to the coordinate 108 * <code>start</code> with appropriate clipping. If 109 * <code>dim</code> 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</code>. 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</code> to be applied to the 183 * coordinates as they are returned in the iteration, or 184 * <code>null</code> 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</code> to be applied to the 209 * coordinates as they are returned in the iteration, or 210 * <code>null</code> 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</code> to be applied to the 242 * coordinates as they are returned in the iteration, or 243 * <code>null</code> 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 * <p> 275 * Note that we trust to the edges array, which means that edges should be: 276 * {@code edges + (lox, loy) < (hix, hiy)}, otherwise result is undefined 277 * 278 * @see TransformHelper 279 */ 280 static Region getInstance(final int lox, final int loy, final int hix, 281 final int hiy, final int edges[]) { 282 // rowsNum * (3 + 1 * 2), plus one byte for endIndex 283 final int[] bands = new int[(edges[1] - edges[0]) * 5 + 1]; 284 int end = 0; 285 int index = 2; 286 for (int y = edges[0]; end < bands.length && y < edges[1]; ++y) { 287 final int x1 = edges[index++]; 288 final int x2 = edges[index++]; 289 if (x1 < x2) { 290 bands[end++] = loy + y; // spanloy 291 bands[end++] = loy + y + 1; // spanhiy 292 bands[end++] = 1; // 1 span per row 293 bands[end++] = lox + x1; // spanlox 294 bands[end++] = lox + x2; // spanhix 295 } 296 } 297 return end != 0 ? new Region(lox, loy, hix, hiy, bands, end) 298 : EMPTY_REGION; 299 } 300 301 /** 302 * Returns a Region object with a rectangle of interest specified 303 * by the indicated Rectangle object. 304 * <p> 305 * This method can also be used to create a simple rectangular 306 * region. 307 */ 308 public static Region getInstance(Rectangle r) { 309 return Region.getInstanceXYWH(r.x, r.y, r.width, r.height); 310 } 311 312 /** 313 * Returns a Region object with a rectangle of interest specified 314 * by the indicated rectangular area in x, y, width, height format. 315 * <p> 316 * This method can also be used to create a simple rectangular 317 * region. 318 */ 319 public static Region getInstanceXYWH(int x, int y, int w, int h) { 320 return Region.getInstanceXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 321 } 322 323 /** 324 * Returns a Region object with a rectangle of interest specified 325 * by the indicated span array. 326 * <p> 327 * This method can also be used to create a simple rectangular 328 * region. 329 */ 330 public static Region getInstance(int box[]) { 331 return new Region(box[0], box[1], box[2], box[3]); 332 } 333 334 /** 335 * Returns a Region object with a rectangle of interest specified 336 * by the indicated rectangular area in lox, loy, hix, hiy format. 337 * <p> 338 * This method can also be used to create a simple rectangular 339 * region. 340 */ 341 public static Region getInstanceXYXY(int lox, int loy, int hix, int hiy) { 342 return new Region(lox, loy, hix, hiy); 343 } 344 345 /** 346 * Sets the rectangle of interest for storing and returning 347 * region bands. 348 * <p> 349 * This method can also be used to initialize a simple rectangular 350 * region. 351 */ 352 public void setOutputArea(Rectangle r) { 353 setOutputAreaXYWH(r.x, r.y, r.width, r.height); 354 } 355 356 /** 357 * Sets the rectangle of interest for storing and returning 358 * region bands. The rectangle is specified in x, y, width, height 359 * format and appropriate clipping is performed as per the method 360 * <code>dimAdd</code>. 361 * <p> 362 * This method can also be used to initialize a simple rectangular 363 * region. 364 */ 365 public void setOutputAreaXYWH(int x, int y, int w, int h) { 366 setOutputAreaXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 367 } 368 369 /** 370 * Sets the rectangle of interest for storing and returning 371 * region bands. The rectangle is specified as a span array. 372 * <p> 373 * This method can also be used to initialize a simple rectangular 374 * region. 375 */ 376 public void setOutputArea(int box[]) { 377 this.lox = box[0]; 378 this.loy = box[1]; 379 this.hix = box[2]; 380 this.hiy = box[3]; 381 } 382 383 /** 384 * Sets the rectangle of interest for storing and returning 385 * region bands. The rectangle is specified in lox, loy, 386 * hix, hiy format. 387 * <p> 388 * This method can also be used to initialize a simple rectangular 389 * region. 390 */ 391 public void setOutputAreaXYXY(int lox, int loy, int hix, int hiy) { 392 this.lox = lox; 393 this.loy = loy; 394 this.hix = hix; 395 this.hiy = hiy; 396 } 397 398 /** 399 * Appends the list of spans returned from the indicated 400 * SpanIterator. Each span must be at a higher starting 401 * Y coordinate than the previous data or it must have a 402 * Y range equal to the highest Y band in the region and a 403 * higher X coordinate than any of the spans in that band. 404 */ 405 public void appendSpans(SpanIterator si) { 406 int[] box = new int[6]; 407 408 while (si.nextSpan(box)) { 409 appendSpan(box); 410 } 411 412 endRow(box); 413 calcBBox(); 414 } 415 416 /** 417 * Returns a Region object that represents the same list of rectangles as 418 * the current Region object, scaled by the specified sx, sy factors. 419 */ 420 public Region getScaledRegion(final double sx, final double sy) { 421 if (sx == 0 || sy == 0 || this == EMPTY_REGION) { 422 return EMPTY_REGION; 423 } 424 if ((sx == 1.0 && sy == 1.0) || (this == WHOLE_REGION)) { 425 return this; 426 } 427 428 int tlox = clipScale(lox, sx); 429 int tloy = clipScale(loy, sy); 430 int thix = clipScale(hix, sx); 431 int thiy = clipScale(hiy, sy); 432 Region ret = new Region(tlox, tloy, thix, thiy); 433 int bands[] = this.bands; 434 if (bands != null) { 435 int end = endIndex; 436 int newbands[] = new int[end]; 437 int i = 0; // index for source bands 438 int j = 0; // index for translated newbands 439 int ncol; 440 while (i < end) { 441 int y1, y2; 442 newbands[j++] = y1 = clipScale(bands[i++], sy); 443 newbands[j++] = y2 = clipScale(bands[i++], sy); 444 newbands[j++] = ncol = bands[i++]; 445 int savej = j; 446 if (y1 < y2) { 447 while (--ncol >= 0) { 448 int x1 = clipScale(bands[i++], sx); 449 int x2 = clipScale(bands[i++], sx); 450 if (x1 < x2) { 451 newbands[j++] = x1; 452 newbands[j++] = x2; 453 } 454 } 455 } else { 456 i += ncol * 2; 457 } 458 // Did we get any non-empty bands in this row? 459 if (j > savej) { 460 newbands[savej-1] = (j - savej) / 2; 461 } else { 462 j = savej - 3; 463 } 464 } 465 if (j <= 5) { 466 if (j < 5) { 467 // No rows or bands were generated... 468 ret.lox = ret.loy = ret.hix = ret.hiy = 0; 469 } else { 470 // Only generated one single rect in the end... 471 ret.loy = newbands[0]; 472 ret.hiy = newbands[1]; 473 ret.lox = newbands[3]; 474 ret.hix = newbands[4]; 475 } 476 // ret.endIndex and ret.bands were never initialized... 477 // ret.endIndex = 0; 478 // ret.newbands = null; 479 } else { 480 // Generated multiple bands and/or multiple rows... 481 ret.endIndex = j; 482 ret.bands = newbands; 483 } 484 } 485 return ret; 486 } 487 488 489 /** 490 * Returns a Region object that represents the same list of 491 * rectangles as the current Region object, translated by 492 * the specified dx, dy translation factors. 493 */ 494 public Region getTranslatedRegion(int dx, int dy) { 495 if ((dx | dy) == 0) { 496 return this; 497 } 498 int tlox = lox + dx; 499 int tloy = loy + dy; 500 int thix = hix + dx; 501 int thiy = hiy + dy; 502 if ((tlox > lox) != (dx > 0) || 503 (tloy > loy) != (dy > 0) || 504 (thix > hix) != (dx > 0) || 505 (thiy > hiy) != (dy > 0)) 506 { 507 return getSafeTranslatedRegion(dx, dy); 508 } 509 Region ret = new Region(tlox, tloy, thix, thiy); 510 int bands[] = this.bands; 511 if (bands != null) { 512 int end = endIndex; 513 ret.endIndex = end; 514 int newbands[] = new int[end]; 515 ret.bands = newbands; 516 int i = 0; 517 int ncol; 518 while (i < end) { 519 newbands[i] = bands[i] + dy; i++; 520 newbands[i] = bands[i] + dy; i++; 521 newbands[i] = ncol = bands[i]; i++; 522 while (--ncol >= 0) { 523 newbands[i] = bands[i] + dx; i++; 524 newbands[i] = bands[i] + dx; i++; 525 } 526 } 527 } 528 return ret; 529 } 530 531 private Region getSafeTranslatedRegion(int dx, int dy) { 532 int tlox = clipAdd(lox, dx); 533 int tloy = clipAdd(loy, dy); 534 int thix = clipAdd(hix, dx); 535 int thiy = clipAdd(hiy, dy); 536 Region ret = new Region(tlox, tloy, thix, thiy); 537 int bands[] = this.bands; 538 if (bands != null) { 539 int end = endIndex; 540 int newbands[] = new int[end]; 541 int i = 0; // index for source bands 542 int j = 0; // index for translated newbands 543 int ncol; 544 while (i < end) { 545 int y1, y2; 546 newbands[j++] = y1 = clipAdd(bands[i++], dy); 547 newbands[j++] = y2 = clipAdd(bands[i++], dy); 548 newbands[j++] = ncol = bands[i++]; 549 int savej = j; 550 if (y1 < y2) { 551 while (--ncol >= 0) { 552 int x1 = clipAdd(bands[i++], dx); 553 int x2 = clipAdd(bands[i++], dx); 554 if (x1 < x2) { 555 newbands[j++] = x1; 556 newbands[j++] = x2; 557 } 558 } 559 } else { 560 i += ncol * 2; 561 } 562 // Did we get any non-empty bands in this row? 563 if (j > savej) { 564 newbands[savej-1] = (j - savej) / 2; 565 } else { 566 j = savej - 3; 567 } 568 } 569 if (j <= 5) { 570 if (j < 5) { 571 // No rows or bands were generated... 572 ret.lox = ret.loy = ret.hix = ret.hiy = 0; 573 } else { 574 // Only generated one single rect in the end... 575 ret.loy = newbands[0]; 576 ret.hiy = newbands[1]; 577 ret.lox = newbands[3]; 578 ret.hix = newbands[4]; 579 } 580 // ret.endIndex and ret.bands were never initialized... 581 // ret.endIndex = 0; 582 // ret.newbands = null; 583 } else { 584 // Generated multiple bands and/or multiple rows... 585 ret.endIndex = j; 586 ret.bands = newbands; 587 } 588 } 589 return ret; 590 } 591 592 /** 593 * Returns a Region object that represents the intersection of 594 * this object with the specified Rectangle. The return value 595 * may be this same object if no clipping occurs. 596 */ 597 public Region getIntersection(Rectangle r) { 598 return getIntersectionXYWH(r.x, r.y, r.width, r.height); 599 } 600 601 /** 602 * Returns a Region object that represents the intersection of 603 * this object with the specified rectangular area. The return 604 * value may be this same object if no clipping occurs. 605 */ 606 public Region getIntersectionXYWH(int x, int y, int w, int h) { 607 return getIntersectionXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 608 } 609 610 /** 611 * Returns a Region object that represents the intersection of 612 * this object with the specified rectangular area. The return 613 * value may be this same object if no clipping occurs. 614 */ 615 public Region getIntersectionXYXY(int lox, int loy, int hix, int hiy) { 616 if (isInsideXYXY(lox, loy, hix, hiy)) { 617 return this; 618 } 619 Region ret = new Region((lox < this.lox) ? this.lox : lox, 620 (loy < this.loy) ? this.loy : loy, 621 (hix > this.hix) ? this.hix : hix, 622 (hiy > this.hiy) ? this.hiy : hiy); 623 if (bands != null) { 624 ret.appendSpans(this.getSpanIterator()); 625 } 626 return ret; 627 } 628 629 /** 630 * Returns a Region object that represents the intersection of this 631 * object with the specified Region object. 632 * <p> 633 * If {@code A} and {@code B} are both Region Objects and 634 * <code>C = A.getIntersection(B);</code> then a point will 635 * be contained in {@code C} iff it is contained in both 636 * {@code A} and {@code B}. 637 * <p> 638 * The return value may be this same object or the argument 639 * Region object if no clipping occurs. 640 */ 641 public Region getIntersection(Region r) { 642 if (this.isInsideQuickCheck(r)) { 643 return this; 644 } 645 if (r.isInsideQuickCheck(this)) { 646 return r; 647 } 648 Region ret = new Region((r.lox < this.lox) ? this.lox : r.lox, 649 (r.loy < this.loy) ? this.loy : r.loy, 650 (r.hix > this.hix) ? this.hix : r.hix, 651 (r.hiy > this.hiy) ? this.hiy : r.hiy); 652 if (!ret.isEmpty()) { 653 ret.filterSpans(this, r, INCLUDE_COMMON); 654 } 655 return ret; 656 } 657 658 /** 659 * Returns a Region object that represents the union of this 660 * object with the specified Region object. 661 * <p> 662 * If {@code A} and {@code B} are both Region Objects and 663 * <code>C = A.getUnion(B);</code> then a point will 664 * be contained in {@code C} iff it is contained in either 665 * {@code A} or {@code B}. 666 * <p> 667 * The return value may be this same object or the argument 668 * Region object if no augmentation occurs. 669 */ 670 public Region getUnion(Region r) { 671 if (r.isEmpty() || r.isInsideQuickCheck(this)) { 672 return this; 673 } 674 if (this.isEmpty() || this.isInsideQuickCheck(r)) { 675 return r; 676 } 677 Region ret = new Region((r.lox > this.lox) ? this.lox : r.lox, 678 (r.loy > this.loy) ? this.loy : r.loy, 679 (r.hix < this.hix) ? this.hix : r.hix, 680 (r.hiy < this.hiy) ? this.hiy : r.hiy); 681 ret.filterSpans(this, r, INCLUDE_A | INCLUDE_B | INCLUDE_COMMON); 682 return ret; 683 } 684 685 /** 686 * Returns a Region object that represents the difference of the 687 * specified Region object subtracted from this object. 688 * <p> 689 * If {@code A} and {@code B} are both Region Objects and 690 * <code>C = A.getDifference(B);</code> then a point will 691 * be contained in {@code C} iff it is contained in 692 * {@code A} but not contained in {@code B}. 693 * <p> 694 * The return value may be this same object or the argument 695 * Region object if no clipping occurs. 696 */ 697 public Region getDifference(Region r) { 698 if (!r.intersectsQuickCheck(this)) { 699 return this; 700 } 701 if (this.isInsideQuickCheck(r)) { 702 return EMPTY_REGION; 703 } 704 Region ret = new Region(this.lox, this.loy, this.hix, this.hiy); 705 ret.filterSpans(this, r, INCLUDE_A); 706 return ret; 707 } 708 709 /** 710 * Returns a Region object that represents the exclusive or of this 711 * object with the specified Region object. 712 * <p> 713 * If {@code A} and {@code B} are both Region Objects and 714 * <code>C = A.getExclusiveOr(B);</code> then a point will 715 * be contained in {@code C} iff it is contained in either 716 * {@code A} or {@code B}, but not if it is contained in both. 717 * <p> 718 * The return value may be this same object or the argument 719 * Region object if either is empty. 720 */ 721 public Region getExclusiveOr(Region r) { 722 if (r.isEmpty()) { 723 return this; 724 } 725 if (this.isEmpty()) { 726 return r; 727 } 728 Region ret = new Region((r.lox > this.lox) ? this.lox : r.lox, 729 (r.loy > this.loy) ? this.loy : r.loy, 730 (r.hix < this.hix) ? this.hix : r.hix, 731 (r.hiy < this.hiy) ? this.hiy : r.hiy); 732 ret.filterSpans(this, r, INCLUDE_A | INCLUDE_B); 733 return ret; 734 } 735 736 static final int INCLUDE_A = 1; 737 static final int INCLUDE_B = 2; 738 static final int INCLUDE_COMMON = 4; 739 740 private void filterSpans(Region ra, Region rb, int flags) { 741 int abands[] = ra.bands; 742 int bbands[] = rb.bands; 743 if (abands == null) { 744 abands = new int[] {ra.loy, ra.hiy, 1, ra.lox, ra.hix}; 745 } 746 if (bbands == null) { 747 bbands = new int[] {rb.loy, rb.hiy, 1, rb.lox, rb.hix}; 748 } 749 int box[] = new int[6]; 750 int acolstart = 0; 751 int ay1 = abands[acolstart++]; 752 int ay2 = abands[acolstart++]; 753 int acolend = abands[acolstart++]; 754 acolend = acolstart + 2 * acolend; 755 int bcolstart = 0; 756 int by1 = bbands[bcolstart++]; 757 int by2 = bbands[bcolstart++]; 758 int bcolend = bbands[bcolstart++]; 759 bcolend = bcolstart + 2 * bcolend; 760 int y = loy; 761 while (y < hiy) { 762 if (y >= ay2) { 763 if (acolend < ra.endIndex) { 764 acolstart = acolend; 765 ay1 = abands[acolstart++]; 766 ay2 = abands[acolstart++]; 767 acolend = abands[acolstart++]; 768 acolend = acolstart + 2 * acolend; 769 } else { 770 if ((flags & INCLUDE_B) == 0) break; 771 ay1 = ay2 = hiy; 772 } 773 continue; 774 } 775 if (y >= by2) { 776 if (bcolend < rb.endIndex) { 777 bcolstart = bcolend; 778 by1 = bbands[bcolstart++]; 779 by2 = bbands[bcolstart++]; 780 bcolend = bbands[bcolstart++]; 781 bcolend = bcolstart + 2 * bcolend; 782 } else { 783 if ((flags & INCLUDE_A) == 0) break; 784 by1 = by2 = hiy; 785 } 786 continue; 787 } 788 int yend; 789 if (y < by1) { 790 if (y < ay1) { 791 y = Math.min(ay1, by1); 792 continue; 793 } 794 // We are in a set of rows that belong only to A 795 yend = Math.min(ay2, by1); 796 if ((flags & INCLUDE_A) != 0) { 797 box[1] = y; 798 box[3] = yend; 799 int acol = acolstart; 800 while (acol < acolend) { 801 box[0] = abands[acol++]; 802 box[2] = abands[acol++]; 803 appendSpan(box); 804 } 805 } 806 } else if (y < ay1) { 807 // We are in a set of rows that belong only to B 808 yend = Math.min(by2, ay1); 809 if ((flags & INCLUDE_B) != 0) { 810 box[1] = y; 811 box[3] = yend; 812 int bcol = bcolstart; 813 while (bcol < bcolend) { 814 box[0] = bbands[bcol++]; 815 box[2] = bbands[bcol++]; 816 appendSpan(box); 817 } 818 } 819 } else { 820 // We are in a set of rows that belong to both A and B 821 yend = Math.min(ay2, by2); 822 box[1] = y; 823 box[3] = yend; 824 int acol = acolstart; 825 int bcol = bcolstart; 826 int ax1 = abands[acol++]; 827 int ax2 = abands[acol++]; 828 int bx1 = bbands[bcol++]; 829 int bx2 = bbands[bcol++]; 830 int x = Math.min(ax1, bx1); 831 if (x < lox) x = lox; 832 while (x < hix) { 833 if (x >= ax2) { 834 if (acol < acolend) { 835 ax1 = abands[acol++]; 836 ax2 = abands[acol++]; 837 } else { 838 if ((flags & INCLUDE_B) == 0) break; 839 ax1 = ax2 = hix; 840 } 841 continue; 842 } 843 if (x >= bx2) { 844 if (bcol < bcolend) { 845 bx1 = bbands[bcol++]; 846 bx2 = bbands[bcol++]; 847 } else { 848 if ((flags & INCLUDE_A) == 0) break; 849 bx1 = bx2 = hix; 850 } 851 continue; 852 } 853 int xend; 854 boolean appendit; 855 if (x < bx1) { 856 if (x < ax1) { 857 xend = Math.min(ax1, bx1); 858 appendit = false; 859 } else { 860 xend = Math.min(ax2, bx1); 861 appendit = ((flags & INCLUDE_A) != 0); 862 } 863 } else if (x < ax1) { 864 xend = Math.min(ax1, bx2); 865 appendit = ((flags & INCLUDE_B) != 0); 866 } else { 867 xend = Math.min(ax2, bx2); 868 appendit = ((flags & INCLUDE_COMMON) != 0); 869 } 870 if (appendit) { 871 box[0] = x; 872 box[2] = xend; 873 appendSpan(box); 874 } 875 x = xend; 876 } 877 } 878 y = yend; 879 } 880 endRow(box); 881 calcBBox(); 882 } 883 884 /** 885 * Returns a Region object that represents the bounds of the 886 * intersection of this object with the bounds of the specified 887 * Region object. 888 * <p> 889 * The return value may be this same object if no clipping occurs 890 * and this Region is rectangular. 891 */ 892 public Region getBoundsIntersection(Rectangle r) { 893 return getBoundsIntersectionXYWH(r.x, r.y, r.width, r.height); 894 } 895 896 /** 897 * Returns a Region object that represents the bounds of the 898 * intersection of this object with the bounds of the specified 899 * rectangular area in x, y, width, height format. 900 * <p> 901 * The return value may be this same object if no clipping occurs 902 * and this Region is rectangular. 903 */ 904 public Region getBoundsIntersectionXYWH(int x, int y, int w, int h) { 905 return getBoundsIntersectionXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 906 } 907 908 /** 909 * Returns a Region object that represents the bounds of the 910 * intersection of this object with the bounds of the specified 911 * rectangular area in lox, loy, hix, hiy format. 912 * <p> 913 * The return value may be this same object if no clipping occurs 914 * and this Region is rectangular. 915 */ 916 public Region getBoundsIntersectionXYXY(int lox, int loy, 917 int hix, int hiy) 918 { 919 if (this.bands == null && 920 this.lox >= lox && this.loy >= loy && 921 this.hix <= hix && this.hiy <= hiy) 922 { 923 return this; 924 } 925 return new Region((lox < this.lox) ? this.lox : lox, 926 (loy < this.loy) ? this.loy : loy, 927 (hix > this.hix) ? this.hix : hix, 928 (hiy > this.hiy) ? this.hiy : hiy); 929 } 930 931 /** 932 * Returns a Region object that represents the intersection of 933 * this object with the bounds of the specified Region object. 934 * <p> 935 * The return value may be this same object or the argument 936 * Region object if no clipping occurs and the Regions are 937 * rectangular. 938 */ 939 public Region getBoundsIntersection(Region r) { 940 if (this.encompasses(r)) { 941 return r; 942 } 943 if (r.encompasses(this)) { 944 return this; 945 } 946 return new Region((r.lox < this.lox) ? this.lox : r.lox, 947 (r.loy < this.loy) ? this.loy : r.loy, 948 (r.hix > this.hix) ? this.hix : r.hix, 949 (r.hiy > this.hiy) ? this.hiy : r.hiy); 950 } 951 952 /** 953 * Appends a single span defined by the 4 parameters 954 * spanlox, spanloy, spanhix, spanhiy. 955 * This span must be at a higher starting Y coordinate than 956 * the previous data or it must have a Y range equal to the 957 * highest Y band in the region and a higher X coordinate 958 * than any of the spans in that band. 959 */ 960 private void appendSpan(int box[]) { 961 int spanlox, spanloy, spanhix, spanhiy; 962 if ((spanlox = box[0]) < lox) spanlox = lox; 963 if ((spanloy = box[1]) < loy) spanloy = loy; 964 if ((spanhix = box[2]) > hix) spanhix = hix; 965 if ((spanhiy = box[3]) > hiy) spanhiy = hiy; 966 if (spanhix <= spanlox || spanhiy <= spanloy) { 967 return; 968 } 969 970 int curYrow = box[4]; 971 if (endIndex == 0 || spanloy >= bands[curYrow + 1]) { 972 if (bands == null) { 973 bands = new int[INIT_SIZE]; 974 } else { 975 needSpace(5); 976 endRow(box); 977 curYrow = box[4]; 978 } 979 bands[endIndex++] = spanloy; 980 bands[endIndex++] = spanhiy; 981 bands[endIndex++] = 0; 982 } else if (spanloy == bands[curYrow] && 983 spanhiy == bands[curYrow + 1] && 984 spanlox >= bands[endIndex - 1]) { 985 if (spanlox == bands[endIndex - 1]) { 986 bands[endIndex - 1] = spanhix; 987 return; 988 } 989 needSpace(2); 990 } else { 991 throw new InternalError("bad span"); 992 } 993 bands[endIndex++] = spanlox; 994 bands[endIndex++] = spanhix; 995 bands[curYrow + 2]++; 996 } 997 998 private void needSpace(int num) { 999 if (endIndex + num >= bands.length) { 1000 int[] newbands = new int[bands.length + GROW_SIZE]; 1001 System.arraycopy(bands, 0, newbands, 0, endIndex); 1002 bands = newbands; 1003 } 1004 } 1005 1006 private void endRow(int box[]) { 1007 int cur = box[4]; 1008 int prev = box[5]; 1009 if (cur > prev) { 1010 int[] bands = this.bands; 1011 if (bands[prev + 1] == bands[cur] && 1012 bands[prev + 2] == bands[cur + 2]) 1013 { 1014 int num = bands[cur + 2] * 2; 1015 cur += 3; 1016 prev += 3; 1017 while (num > 0) { 1018 if (bands[cur++] != bands[prev++]) { 1019 break; 1020 } 1021 num--; 1022 } 1023 if (num == 0) { 1024 // prev == box[4] 1025 bands[box[5] + 1] = bands[prev + 1]; 1026 endIndex = prev; 1027 return; 1028 } 1029 } 1030 } 1031 box[5] = box[4]; 1032 box[4] = endIndex; 1033 } 1034 1035 private void calcBBox() { 1036 int[] bands = this.bands; 1037 if (endIndex <= 5) { 1038 if (endIndex == 0) { 1039 lox = loy = hix = hiy = 0; 1040 } else { 1041 loy = bands[0]; 1042 hiy = bands[1]; 1043 lox = bands[3]; 1044 hix = bands[4]; 1045 endIndex = 0; 1046 } 1047 this.bands = null; 1048 return; 1049 } 1050 int lox = this.hix; 1051 int hix = this.lox; 1052 int hiyindex = 0; 1053 1054 int i = 0; 1055 while (i < endIndex) { 1056 hiyindex = i; 1057 int numbands = bands[i + 2]; 1058 i += 3; 1059 if (lox > bands[i]) { 1060 lox = bands[i]; 1061 } 1062 i += numbands * 2; 1063 if (hix < bands[i - 1]) { 1064 hix = bands[i - 1]; 1065 } 1066 } 1067 1068 this.lox = lox; 1069 this.loy = bands[0]; 1070 this.hix = hix; 1071 this.hiy = bands[hiyindex + 1]; 1072 } 1073 1074 /** 1075 * Returns the lowest X coordinate in the Region. 1076 */ 1077 public final int getLoX() { 1078 return lox; 1079 } 1080 1081 /** 1082 * Returns the lowest Y coordinate in the Region. 1083 */ 1084 public final int getLoY() { 1085 return loy; 1086 } 1087 1088 /** 1089 * Returns the highest X coordinate in the Region. 1090 */ 1091 public final int getHiX() { 1092 return hix; 1093 } 1094 1095 /** 1096 * Returns the highest Y coordinate in the Region. 1097 */ 1098 public final int getHiY() { 1099 return hiy; 1100 } 1101 1102 /** 1103 * Returns the width of this Region clipped to the range (0 - MAX_INT). 1104 */ 1105 public final int getWidth() { 1106 if (hix < lox) return 0; 1107 int w; 1108 if ((w = hix - lox) < 0) { 1109 w = Integer.MAX_VALUE; 1110 } 1111 return w; 1112 } 1113 1114 /** 1115 * Returns the height of this Region clipped to the range (0 - MAX_INT). 1116 */ 1117 public final int getHeight() { 1118 if (hiy < loy) return 0; 1119 int h; 1120 if ((h = hiy - loy) < 0) { 1121 h = Integer.MAX_VALUE; 1122 } 1123 return h; 1124 } 1125 1126 /** 1127 * Returns true iff this Region encloses no area. 1128 */ 1129 public boolean isEmpty() { 1130 return (hix <= lox || hiy <= loy); 1131 } 1132 1133 /** 1134 * Returns true iff this Region represents a single simple 1135 * rectangular area. 1136 */ 1137 public boolean isRectangular() { 1138 return (bands == null); 1139 } 1140 1141 /** 1142 * Returns true iff this Region contains the specified coordinate. 1143 */ 1144 public boolean contains(int x, int y) { 1145 if (x < lox || x >= hix || y < loy || y >= hiy) return false; 1146 if (bands == null) return true; 1147 int i = 0; 1148 while (i < endIndex) { 1149 if (y < bands[i++]) { 1150 return false; 1151 } 1152 if (y >= bands[i++]) { 1153 int numspans = bands[i++]; 1154 i += numspans * 2; 1155 } else { 1156 int end = bands[i++]; 1157 end = i + end * 2; 1158 while (i < end) { 1159 if (x < bands[i++]) return false; 1160 if (x < bands[i++]) return true; 1161 } 1162 return false; 1163 } 1164 } 1165 return false; 1166 } 1167 1168 /** 1169 * Returns true iff this Region lies inside the indicated 1170 * rectangular area specified in x, y, width, height format 1171 * with appropriate clipping performed as per the dimAdd method. 1172 */ 1173 public boolean isInsideXYWH(int x, int y, int w, int h) { 1174 return isInsideXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 1175 } 1176 1177 /** 1178 * Returns true iff this Region lies inside the indicated 1179 * rectangular area specified in lox, loy, hix, hiy format. 1180 */ 1181 public boolean isInsideXYXY(int lox, int loy, int hix, int hiy) { 1182 return (this.lox >= lox && this.loy >= loy && 1183 this.hix <= hix && this.hiy <= hiy); 1184 1185 } 1186 1187 /** 1188 * Quickly checks if this Region lies inside the specified 1189 * Region object. 1190 * <p> 1191 * This method will return false if the specified Region 1192 * object is not a simple rectangle. 1193 */ 1194 public boolean isInsideQuickCheck(Region r) { 1195 return (r.bands == null && 1196 r.lox <= this.lox && r.loy <= this.loy && 1197 r.hix >= this.hix && r.hiy >= this.hiy); 1198 } 1199 1200 /** 1201 * Quickly checks if this Region intersects the specified 1202 * rectangular area specified in lox, loy, hix, hiy format. 1203 * <p> 1204 * This method tests only against the bounds of this region 1205 * and does not bother to test if the rectangular region 1206 * actually intersects any bands. 1207 */ 1208 public boolean intersectsQuickCheckXYXY(int lox, int loy, 1209 int hix, int hiy) 1210 { 1211 return (hix > this.lox && lox < this.hix && 1212 hiy > this.loy && loy < this.hiy); 1213 } 1214 1215 /** 1216 * Quickly checks if this Region intersects the specified 1217 * Region object. 1218 * <p> 1219 * This method tests only against the bounds of this region 1220 * and does not bother to test if the rectangular region 1221 * actually intersects any bands. 1222 */ 1223 public boolean intersectsQuickCheck(Region r) { 1224 return (r.hix > this.lox && r.lox < this.hix && 1225 r.hiy > this.loy && r.loy < this.hiy); 1226 } 1227 1228 /** 1229 * Quickly checks if this Region surrounds the specified 1230 * Region object. 1231 * <p> 1232 * This method will return false if this Region object is 1233 * not a simple rectangle. 1234 */ 1235 public boolean encompasses(Region r) { 1236 return (this.bands == null && 1237 this.lox <= r.lox && this.loy <= r.loy && 1238 this.hix >= r.hix && this.hiy >= r.hiy); 1239 } 1240 1241 /** 1242 * Quickly checks if this Region surrounds the specified 1243 * rectangular area specified in x, y, width, height format. 1244 * <p> 1245 * This method will return false if this Region object is 1246 * not a simple rectangle. 1247 */ 1248 public boolean encompassesXYWH(int x, int y, int w, int h) { 1249 return encompassesXYXY(x, y, dimAdd(x, w), dimAdd(y, h)); 1250 } 1251 1252 /** 1253 * Quickly checks if this Region surrounds the specified 1254 * rectangular area specified in lox, loy, hix, hiy format. 1255 * <p> 1256 * This method will return false if this Region object is 1257 * not a simple rectangle. 1258 */ 1259 public boolean encompassesXYXY(int lox, int loy, int hix, int hiy) { 1260 return (this.bands == null && 1261 this.lox <= lox && this.loy <= loy && 1262 this.hix >= hix && this.hiy >= hiy); 1263 } 1264 1265 /** 1266 * Gets the bbox of the available spans, clipped to the OutputArea. 1267 */ 1268 public void getBounds(int pathbox[]) { 1269 pathbox[0] = lox; 1270 pathbox[1] = loy; 1271 pathbox[2] = hix; 1272 pathbox[3] = hiy; 1273 } 1274 1275 /** 1276 * Clips the indicated bbox array to the bounds of this Region. 1277 */ 1278 public void clipBoxToBounds(int bbox[]) { 1279 if (bbox[0] < lox) bbox[0] = lox; 1280 if (bbox[1] < loy) bbox[1] = loy; 1281 if (bbox[2] > hix) bbox[2] = hix; 1282 if (bbox[3] > hiy) bbox[3] = hiy; 1283 } 1284 1285 /** 1286 * Gets an iterator object to iterate over the spans in this region. 1287 */ 1288 public RegionIterator getIterator() { 1289 return new RegionIterator(this); 1290 } 1291 1292 /** 1293 * Gets a span iterator object that iterates over the spans in this region 1294 */ 1295 public SpanIterator getSpanIterator() { 1296 return new RegionSpanIterator(this); 1297 } 1298 1299 /** 1300 * Gets a span iterator object that iterates over the spans in this region 1301 * but clipped to the bounds given in the argument (xlo, ylo, xhi, yhi). 1302 */ 1303 public SpanIterator getSpanIterator(int bbox[]) { 1304 SpanIterator result = getSpanIterator(); 1305 result.intersectClipBox(bbox[0], bbox[1], bbox[2], bbox[3]); 1306 return result; 1307 } 1308 1309 /** 1310 * Returns a SpanIterator that is the argument iterator filtered by 1311 * this region. 1312 */ 1313 public SpanIterator filter(SpanIterator si) { 1314 if (bands == null) { 1315 si.intersectClipBox(lox, loy, hix, hiy); 1316 } else { 1317 si = new RegionClipSpanIterator(this, si); 1318 } 1319 return si; 1320 } 1321 1322 public String toString() { 1323 StringBuilder sb = new StringBuilder(); 1324 sb.append("Region[["); 1325 sb.append(lox); 1326 sb.append(", "); 1327 sb.append(loy); 1328 sb.append(" => "); 1329 sb.append(hix); 1330 sb.append(", "); 1331 sb.append(hiy); 1332 sb.append("]"); 1333 if (bands != null) { 1334 int col = 0; 1335 while (col < endIndex) { 1336 sb.append("y{"); 1337 sb.append(bands[col++]); 1338 sb.append(","); 1339 sb.append(bands[col++]); 1340 sb.append("}["); 1341 int end = bands[col++]; 1342 end = col + end * 2; 1343 while (col < end) { 1344 sb.append("x("); 1345 sb.append(bands[col++]); 1346 sb.append(", "); 1347 sb.append(bands[col++]); 1348 sb.append(")"); 1349 } 1350 sb.append("]"); 1351 } 1352 } 1353 sb.append("]"); 1354 return sb.toString(); 1355 } 1356 1357 public int hashCode() { 1358 return (isEmpty() ? 0 : (lox * 3 + loy * 5 + hix * 7 + hiy * 9)); 1359 } 1360 1361 public boolean equals(Object o) { 1362 if (!(o instanceof Region)) { 1363 return false; 1364 } 1365 Region r = (Region) o; 1366 if (this.isEmpty()) { 1367 return r.isEmpty(); 1368 } else if (r.isEmpty()) { 1369 return false; 1370 } 1371 if (r.lox != this.lox || r.loy != this.loy || 1372 r.hix != this.hix || r.hiy != this.hiy) 1373 { 1374 return false; 1375 } 1376 if (this.bands == null) { 1377 return (r.bands == null); 1378 } else if (r.bands == null) { 1379 return false; 1380 } 1381 if (this.endIndex != r.endIndex) { 1382 return false; 1383 } 1384 int abands[] = this.bands; 1385 int bbands[] = r.bands; 1386 for (int i = 0; i < endIndex; i++) { 1387 if (abands[i] != bbands[i]) { 1388 return false; 1389 } 1390 } 1391 return true; 1392 } 1393 }