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