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