Rect.java revision 0a932141980b576e0b9bcec9a077f55b7b269a02
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.graphics; 18 19import android.annotation.CheckResult; 20import android.os.Parcel; 21import android.os.Parcelable; 22 23import java.io.PrintWriter; 24import java.util.regex.Matcher; 25import java.util.regex.Pattern; 26 27/** 28 * Rect holds four integer coordinates for a rectangle. The rectangle is 29 * represented by the coordinates of its 4 edges (left, top, right bottom). 30 * These fields can be accessed directly. Use width() and height() to retrieve 31 * the rectangle's width and height. Note: most methods do not check to see that 32 * the coordinates are sorted correctly (i.e. left <= right and top <= bottom). 33 */ 34public final class Rect implements Parcelable { 35 public int left; 36 public int top; 37 public int right; 38 public int bottom; 39 40 /** 41 * A helper class for flattened rectange pattern recognition. A separate 42 * class to avoid an initialization dependency on a regular expression 43 * causing Rect to not be initializable with an ahead-of-time compilation 44 * scheme. 45 */ 46 private static final class UnflattenHelper { 47 private static final Pattern FLATTENED_PATTERN = Pattern.compile( 48 "(-?\\d+) (-?\\d+) (-?\\d+) (-?\\d+)"); 49 50 static Matcher getMatcher(String str) { 51 return FLATTENED_PATTERN.matcher(str); 52 } 53 } 54 55 /** 56 * Create a new empty Rect. All coordinates are initialized to 0. 57 */ 58 public Rect() {} 59 60 /** 61 * Create a new rectangle with the specified coordinates. Note: no range 62 * checking is performed, so the caller must ensure that left <= right and 63 * top <= bottom. 64 * 65 * @param left The X coordinate of the left side of the rectangle 66 * @param top The Y coordinate of the top of the rectangle 67 * @param right The X coordinate of the right side of the rectangle 68 * @param bottom The Y coordinate of the bottom of the rectangle 69 */ 70 public Rect(int left, int top, int right, int bottom) { 71 this.left = left; 72 this.top = top; 73 this.right = right; 74 this.bottom = bottom; 75 } 76 77 /** 78 * Create a new rectangle, initialized with the values in the specified 79 * rectangle (which is left unmodified). 80 * 81 * @param r The rectangle whose coordinates are copied into the new 82 * rectangle. 83 */ 84 public Rect(Rect r) { 85 if (r == null) { 86 left = top = right = bottom = 0; 87 } else { 88 left = r.left; 89 top = r.top; 90 right = r.right; 91 bottom = r.bottom; 92 } 93 } 94 95 @Override 96 public boolean equals(Object o) { 97 if (this == o) return true; 98 if (o == null || getClass() != o.getClass()) return false; 99 100 Rect r = (Rect) o; 101 return left == r.left && top == r.top && right == r.right && bottom == r.bottom; 102 } 103 104 @Override 105 public int hashCode() { 106 int result = left; 107 result = 31 * result + top; 108 result = 31 * result + right; 109 result = 31 * result + bottom; 110 return result; 111 } 112 113 @Override 114 public String toString() { 115 StringBuilder sb = new StringBuilder(32); 116 sb.append("Rect("); sb.append(left); sb.append(", "); 117 sb.append(top); sb.append(" - "); sb.append(right); 118 sb.append(", "); sb.append(bottom); sb.append(")"); 119 return sb.toString(); 120 } 121 122 /** 123 * Return a string representation of the rectangle in a compact form. 124 */ 125 public String toShortString() { 126 return toShortString(new StringBuilder(32)); 127 } 128 129 /** 130 * Return a string representation of the rectangle in a compact form. 131 * @hide 132 */ 133 public String toShortString(StringBuilder sb) { 134 sb.setLength(0); 135 sb.append('['); sb.append(left); sb.append(','); 136 sb.append(top); sb.append("]["); sb.append(right); 137 sb.append(','); sb.append(bottom); sb.append(']'); 138 return sb.toString(); 139 } 140 141 /** 142 * Return a string representation of the rectangle in a well-defined format. 143 * 144 * <p>You can later recover the Rect from this string through 145 * {@link #unflattenFromString(String)}. 146 * 147 * @return Returns a new String of the form "left top right bottom" 148 */ 149 public String flattenToString() { 150 StringBuilder sb = new StringBuilder(32); 151 // WARNING: Do not change the format of this string, it must be 152 // preserved because Rects are saved in this flattened format. 153 sb.append(left); 154 sb.append(' '); 155 sb.append(top); 156 sb.append(' '); 157 sb.append(right); 158 sb.append(' '); 159 sb.append(bottom); 160 return sb.toString(); 161 } 162 163 /** 164 * Returns a Rect from a string of the form returned by {@link #flattenToString}, 165 * or null if the string is not of that form. 166 */ 167 public static Rect unflattenFromString(String str) { 168 Matcher matcher = UnflattenHelper.getMatcher(str); 169 if (!matcher.matches()) { 170 return null; 171 } 172 return new Rect(Integer.parseInt(matcher.group(1)), 173 Integer.parseInt(matcher.group(2)), 174 Integer.parseInt(matcher.group(3)), 175 Integer.parseInt(matcher.group(4))); 176 } 177 178 /** 179 * Print short representation to given writer. 180 * @hide 181 */ 182 public void printShortString(PrintWriter pw) { 183 pw.print('['); pw.print(left); pw.print(','); 184 pw.print(top); pw.print("]["); pw.print(right); 185 pw.print(','); pw.print(bottom); pw.print(']'); 186 } 187 188 /** 189 * Returns true if the rectangle is empty (left >= right or top >= bottom) 190 */ 191 public final boolean isEmpty() { 192 return left >= right || top >= bottom; 193 } 194 195 /** 196 * @return the rectangle's width. This does not check for a valid rectangle 197 * (i.e. left <= right) so the result may be negative. 198 */ 199 public final int width() { 200 return right - left; 201 } 202 203 /** 204 * @return the rectangle's height. This does not check for a valid rectangle 205 * (i.e. top <= bottom) so the result may be negative. 206 */ 207 public final int height() { 208 return bottom - top; 209 } 210 211 /** 212 * @return the horizontal center of the rectangle. If the computed value 213 * is fractional, this method returns the largest integer that is 214 * less than the computed value. 215 */ 216 public final int centerX() { 217 return (left + right) >> 1; 218 } 219 220 /** 221 * @return the vertical center of the rectangle. If the computed value 222 * is fractional, this method returns the largest integer that is 223 * less than the computed value. 224 */ 225 public final int centerY() { 226 return (top + bottom) >> 1; 227 } 228 229 /** 230 * @return the exact horizontal center of the rectangle as a float. 231 */ 232 public final float exactCenterX() { 233 return (left + right) * 0.5f; 234 } 235 236 /** 237 * @return the exact vertical center of the rectangle as a float. 238 */ 239 public final float exactCenterY() { 240 return (top + bottom) * 0.5f; 241 } 242 243 /** 244 * Set the rectangle to (0,0,0,0) 245 */ 246 public void setEmpty() { 247 left = right = top = bottom = 0; 248 } 249 250 /** 251 * Set the rectangle's coordinates to the specified values. Note: no range 252 * checking is performed, so it is up to the caller to ensure that 253 * left <= right and top <= bottom. 254 * 255 * @param left The X coordinate of the left side of the rectangle 256 * @param top The Y coordinate of the top of the rectangle 257 * @param right The X coordinate of the right side of the rectangle 258 * @param bottom The Y coordinate of the bottom of the rectangle 259 */ 260 public void set(int left, int top, int right, int bottom) { 261 this.left = left; 262 this.top = top; 263 this.right = right; 264 this.bottom = bottom; 265 } 266 267 /** 268 * Copy the coordinates from src into this rectangle. 269 * 270 * @param src The rectangle whose coordinates are copied into this 271 * rectangle. 272 */ 273 public void set(Rect src) { 274 this.left = src.left; 275 this.top = src.top; 276 this.right = src.right; 277 this.bottom = src.bottom; 278 } 279 280 /** 281 * Offset the rectangle by adding dx to its left and right coordinates, and 282 * adding dy to its top and bottom coordinates. 283 * 284 * @param dx The amount to add to the rectangle's left and right coordinates 285 * @param dy The amount to add to the rectangle's top and bottom coordinates 286 */ 287 public void offset(int dx, int dy) { 288 left += dx; 289 top += dy; 290 right += dx; 291 bottom += dy; 292 } 293 294 /** 295 * Offset the rectangle to a specific (left, top) position, 296 * keeping its width and height the same. 297 * 298 * @param newLeft The new "left" coordinate for the rectangle 299 * @param newTop The new "top" coordinate for the rectangle 300 */ 301 public void offsetTo(int newLeft, int newTop) { 302 right += newLeft - left; 303 bottom += newTop - top; 304 left = newLeft; 305 top = newTop; 306 } 307 308 /** 309 * Inset the rectangle by (dx,dy). If dx is positive, then the sides are 310 * moved inwards, making the rectangle narrower. If dx is negative, then the 311 * sides are moved outwards, making the rectangle wider. The same holds true 312 * for dy and the top and bottom. 313 * 314 * @param dx The amount to add(subtract) from the rectangle's left(right) 315 * @param dy The amount to add(subtract) from the rectangle's top(bottom) 316 */ 317 public void inset(int dx, int dy) { 318 left += dx; 319 top += dy; 320 right -= dx; 321 bottom -= dy; 322 } 323 324 /** 325 * Insets the rectangle on all sides specified by the dimensions of the {@code insets} 326 * rectangle. 327 * @hide 328 * @param insets The rectangle specifying the insets on all side. 329 */ 330 public void inset(Rect insets) { 331 left += insets.left; 332 top += insets.top; 333 right -= insets.right; 334 bottom -= insets.bottom; 335 } 336 337 /** 338 * Insets the rectangle on all sides specified by the insets. 339 * @hide 340 * @param left The amount to add from the rectangle's left 341 * @param top The amount to add from the rectangle's top 342 * @param right The amount to subtract from the rectangle's right 343 * @param bottom The amount to subtract from the rectangle's bottom 344 */ 345 public void inset(int left, int top, int right, int bottom) { 346 this.left += left; 347 this.top += top; 348 this.right -= right; 349 this.bottom -= bottom; 350 } 351 352 /** 353 * Returns true if (x,y) is inside the rectangle. The left and top are 354 * considered to be inside, while the right and bottom are not. This means 355 * that for a x,y to be contained: left <= x < right and top <= y < bottom. 356 * An empty rectangle never contains any point. 357 * 358 * @param x The X coordinate of the point being tested for containment 359 * @param y The Y coordinate of the point being tested for containment 360 * @return true iff (x,y) are contained by the rectangle, where containment 361 * means left <= x < right and top <= y < bottom 362 */ 363 public boolean contains(int x, int y) { 364 return left < right && top < bottom // check for empty first 365 && x >= left && x < right && y >= top && y < bottom; 366 } 367 368 /** 369 * Returns true iff the 4 specified sides of a rectangle are inside or equal 370 * to this rectangle. i.e. is this rectangle a superset of the specified 371 * rectangle. An empty rectangle never contains another rectangle. 372 * 373 * @param left The left side of the rectangle being tested for containment 374 * @param top The top of the rectangle being tested for containment 375 * @param right The right side of the rectangle being tested for containment 376 * @param bottom The bottom of the rectangle being tested for containment 377 * @return true iff the the 4 specified sides of a rectangle are inside or 378 * equal to this rectangle 379 */ 380 public boolean contains(int left, int top, int right, int bottom) { 381 // check for empty first 382 return this.left < this.right && this.top < this.bottom 383 // now check for containment 384 && this.left <= left && this.top <= top 385 && this.right >= right && this.bottom >= bottom; 386 } 387 388 /** 389 * Returns true iff the specified rectangle r is inside or equal to this 390 * rectangle. An empty rectangle never contains another rectangle. 391 * 392 * @param r The rectangle being tested for containment. 393 * @return true iff the specified rectangle r is inside or equal to this 394 * rectangle 395 */ 396 public boolean contains(Rect r) { 397 // check for empty first 398 return this.left < this.right && this.top < this.bottom 399 // now check for containment 400 && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom; 401 } 402 403 /** 404 * If the rectangle specified by left,top,right,bottom intersects this 405 * rectangle, return true and set this rectangle to that intersection, 406 * otherwise return false and do not change this rectangle. No check is 407 * performed to see if either rectangle is empty. Note: To just test for 408 * intersection, use {@link #intersects(Rect, Rect)}. 409 * 410 * @param left The left side of the rectangle being intersected with this 411 * rectangle 412 * @param top The top of the rectangle being intersected with this rectangle 413 * @param right The right side of the rectangle being intersected with this 414 * rectangle. 415 * @param bottom The bottom of the rectangle being intersected with this 416 * rectangle. 417 * @return true if the specified rectangle and this rectangle intersect 418 * (and this rectangle is then set to that intersection) else 419 * return false and do not change this rectangle. 420 */ 421 @CheckResult 422 public boolean intersect(int left, int top, int right, int bottom) { 423 if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) { 424 if (this.left < left) this.left = left; 425 if (this.top < top) this.top = top; 426 if (this.right > right) this.right = right; 427 if (this.bottom > bottom) this.bottom = bottom; 428 return true; 429 } 430 return false; 431 } 432 433 /** 434 * If the specified rectangle intersects this rectangle, return true and set 435 * this rectangle to that intersection, otherwise return false and do not 436 * change this rectangle. No check is performed to see if either rectangle 437 * is empty. To just test for intersection, use intersects() 438 * 439 * @param r The rectangle being intersected with this rectangle. 440 * @return true if the specified rectangle and this rectangle intersect 441 * (and this rectangle is then set to that intersection) else 442 * return false and do not change this rectangle. 443 */ 444 @CheckResult 445 public boolean intersect(Rect r) { 446 return intersect(r.left, r.top, r.right, r.bottom); 447 } 448 449 /** 450 * If rectangles a and b intersect, return true and set this rectangle to 451 * that intersection, otherwise return false and do not change this 452 * rectangle. No check is performed to see if either rectangle is empty. 453 * To just test for intersection, use intersects() 454 * 455 * @param a The first rectangle being intersected with 456 * @param b The second rectangle being intersected with 457 * @return true iff the two specified rectangles intersect. If they do, set 458 * this rectangle to that intersection. If they do not, return 459 * false and do not change this rectangle. 460 */ 461 @CheckResult 462 public boolean setIntersect(Rect a, Rect b) { 463 if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) { 464 left = Math.max(a.left, b.left); 465 top = Math.max(a.top, b.top); 466 right = Math.min(a.right, b.right); 467 bottom = Math.min(a.bottom, b.bottom); 468 return true; 469 } 470 return false; 471 } 472 473 /** 474 * Returns true if this rectangle intersects the specified rectangle. 475 * In no event is this rectangle modified. No check is performed to see 476 * if either rectangle is empty. To record the intersection, use intersect() 477 * or setIntersect(). 478 * 479 * @param left The left side of the rectangle being tested for intersection 480 * @param top The top of the rectangle being tested for intersection 481 * @param right The right side of the rectangle being tested for 482 * intersection 483 * @param bottom The bottom of the rectangle being tested for intersection 484 * @return true iff the specified rectangle intersects this rectangle. In 485 * no event is this rectangle modified. 486 */ 487 public boolean intersects(int left, int top, int right, int bottom) { 488 return this.left < right && left < this.right && this.top < bottom && top < this.bottom; 489 } 490 491 /** 492 * Returns true iff the two specified rectangles intersect. In no event are 493 * either of the rectangles modified. To record the intersection, 494 * use {@link #intersect(Rect)} or {@link #setIntersect(Rect, Rect)}. 495 * 496 * @param a The first rectangle being tested for intersection 497 * @param b The second rectangle being tested for intersection 498 * @return true iff the two specified rectangles intersect. In no event are 499 * either of the rectangles modified. 500 */ 501 public static boolean intersects(Rect a, Rect b) { 502 return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom; 503 } 504 505 /** 506 * Update this Rect to enclose itself and the specified rectangle. If the 507 * specified rectangle is empty, nothing is done. If this rectangle is empty 508 * it is set to the specified rectangle. 509 * 510 * @param left The left edge being unioned with this rectangle 511 * @param top The top edge being unioned with this rectangle 512 * @param right The right edge being unioned with this rectangle 513 * @param bottom The bottom edge being unioned with this rectangle 514 */ 515 public void union(int left, int top, int right, int bottom) { 516 if ((left < right) && (top < bottom)) { 517 if ((this.left < this.right) && (this.top < this.bottom)) { 518 if (this.left > left) this.left = left; 519 if (this.top > top) this.top = top; 520 if (this.right < right) this.right = right; 521 if (this.bottom < bottom) this.bottom = bottom; 522 } else { 523 this.left = left; 524 this.top = top; 525 this.right = right; 526 this.bottom = bottom; 527 } 528 } 529 } 530 531 /** 532 * Update this Rect to enclose itself and the specified rectangle. If the 533 * specified rectangle is empty, nothing is done. If this rectangle is empty 534 * it is set to the specified rectangle. 535 * 536 * @param r The rectangle being unioned with this rectangle 537 */ 538 public void union(Rect r) { 539 union(r.left, r.top, r.right, r.bottom); 540 } 541 542 /** 543 * Update this Rect to enclose itself and the [x,y] coordinate. There is no 544 * check to see that this rectangle is non-empty. 545 * 546 * @param x The x coordinate of the point to add to the rectangle 547 * @param y The y coordinate of the point to add to the rectangle 548 */ 549 public void union(int x, int y) { 550 if (x < left) { 551 left = x; 552 } else if (x > right) { 553 right = x; 554 } 555 if (y < top) { 556 top = y; 557 } else if (y > bottom) { 558 bottom = y; 559 } 560 } 561 562 /** 563 * Swap top/bottom or left/right if there are flipped (i.e. left > right 564 * and/or top > bottom). This can be called if 565 * the edges are computed separately, and may have crossed over each other. 566 * If the edges are already correct (i.e. left <= right and top <= bottom) 567 * then nothing is done. 568 */ 569 public void sort() { 570 if (left > right) { 571 int temp = left; 572 left = right; 573 right = temp; 574 } 575 if (top > bottom) { 576 int temp = top; 577 top = bottom; 578 bottom = temp; 579 } 580 } 581 582 /** 583 * Parcelable interface methods 584 */ 585 public int describeContents() { 586 return 0; 587 } 588 589 /** 590 * Write this rectangle to the specified parcel. To restore a rectangle from 591 * a parcel, use readFromParcel() 592 * @param out The parcel to write the rectangle's coordinates into 593 */ 594 public void writeToParcel(Parcel out, int flags) { 595 out.writeInt(left); 596 out.writeInt(top); 597 out.writeInt(right); 598 out.writeInt(bottom); 599 } 600 601 public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() { 602 /** 603 * Return a new rectangle from the data in the specified parcel. 604 */ 605 public Rect createFromParcel(Parcel in) { 606 Rect r = new Rect(); 607 r.readFromParcel(in); 608 return r; 609 } 610 611 /** 612 * Return an array of rectangles of the specified size. 613 */ 614 public Rect[] newArray(int size) { 615 return new Rect[size]; 616 } 617 }; 618 619 /** 620 * Set the rectangle's coordinates from the data stored in the specified 621 * parcel. To write a rectangle to a parcel, call writeToParcel(). 622 * 623 * @param in The parcel to read the rectangle's coordinates from 624 */ 625 public void readFromParcel(Parcel in) { 626 left = in.readInt(); 627 top = in.readInt(); 628 right = in.readInt(); 629 bottom = in.readInt(); 630 } 631 632 /** 633 * Scales up the rect by the given scale. 634 * @hide 635 */ 636 public void scale(float scale) { 637 if (scale != 1.0f) { 638 left = (int) (left * scale + 0.5f); 639 top = (int) (top * scale + 0.5f); 640 right = (int) (right * scale + 0.5f); 641 bottom = (int) (bottom * scale + 0.5f); 642 } 643 } 644 645 /** 646 * Scales up the rect by the given scale, rounding values toward the inside. 647 * @hide 648 */ 649 public void scaleRoundIn(float scale) { 650 if (scale != 1.0f) { 651 left = (int) Math.ceil(left * scale); 652 top = (int) Math.ceil(top * scale); 653 right = (int) Math.floor(right * scale); 654 bottom = (int) Math.floor(bottom * scale); 655 } 656 } 657} 658