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