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