Rect.java revision c6c89a82144f59475242c75d67529fed943ae30b
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     * Returns true if (x,y) is inside the rectangle. The left and top are
339     * considered to be inside, while the right and bottom are not. This means
340     * that for a x,y to be contained: left <= x < right and top <= y < bottom.
341     * An empty rectangle never contains any point.
342     *
343     * @param x The X coordinate of the point being tested for containment
344     * @param y The Y coordinate of the point being tested for containment
345     * @return true iff (x,y) are contained by the rectangle, where containment
346     *              means left <= x < right and top <= y < bottom
347     */
348    public boolean contains(int x, int y) {
349        return left < right && top < bottom  // check for empty first
350               && x >= left && x < right && y >= top && y < bottom;
351    }
352
353    /**
354     * Returns true iff the 4 specified sides of a rectangle are inside or equal
355     * to this rectangle. i.e. is this rectangle a superset of the specified
356     * rectangle. An empty rectangle never contains another rectangle.
357     *
358     * @param left The left side of the rectangle being tested for containment
359     * @param top The top of the rectangle being tested for containment
360     * @param right The right side of the rectangle being tested for containment
361     * @param bottom The bottom of the rectangle being tested for containment
362     * @return true iff the the 4 specified sides of a rectangle are inside or
363     *              equal to this rectangle
364     */
365    public boolean contains(int left, int top, int right, int bottom) {
366               // check for empty first
367        return this.left < this.right && this.top < this.bottom
368               // now check for containment
369                && this.left <= left && this.top <= top
370                && this.right >= right && this.bottom >= bottom;
371    }
372
373    /**
374     * Returns true iff the specified rectangle r is inside or equal to this
375     * rectangle. An empty rectangle never contains another rectangle.
376     *
377     * @param r The rectangle being tested for containment.
378     * @return true iff the specified rectangle r is inside or equal to this
379     *              rectangle
380     */
381    public boolean contains(Rect r) {
382               // check for empty first
383        return this.left < this.right && this.top < this.bottom
384               // now check for containment
385               && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom;
386    }
387
388    /**
389     * If the rectangle specified by left,top,right,bottom intersects this
390     * rectangle, return true and set this rectangle to that intersection,
391     * otherwise return false and do not change this rectangle. No check is
392     * performed to see if either rectangle is empty. Note: To just test for
393     * intersection, use {@link #intersects(Rect, Rect)}.
394     *
395     * @param left The left side of the rectangle being intersected with this
396     *             rectangle
397     * @param top The top of the rectangle being intersected with this rectangle
398     * @param right The right side of the rectangle being intersected with this
399     *              rectangle.
400     * @param bottom The bottom of the rectangle being intersected with this
401     *             rectangle.
402     * @return true if the specified rectangle and this rectangle intersect
403     *              (and this rectangle is then set to that intersection) else
404     *              return false and do not change this rectangle.
405     */
406    @CheckResult
407    public boolean intersect(int left, int top, int right, int bottom) {
408        if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {
409            if (this.left < left) this.left = left;
410            if (this.top < top) this.top = top;
411            if (this.right > right) this.right = right;
412            if (this.bottom > bottom) this.bottom = bottom;
413            return true;
414        }
415        return false;
416    }
417
418    /**
419     * If the specified rectangle intersects this rectangle, return true and set
420     * this rectangle to that intersection, otherwise return false and do not
421     * change this rectangle. No check is performed to see if either rectangle
422     * is empty. To just test for intersection, use intersects()
423     *
424     * @param r The rectangle being intersected with this rectangle.
425     * @return true if the specified rectangle and this rectangle intersect
426     *              (and this rectangle is then set to that intersection) else
427     *              return false and do not change this rectangle.
428     */
429    @CheckResult
430    public boolean intersect(Rect r) {
431        return intersect(r.left, r.top, r.right, r.bottom);
432    }
433
434    /**
435     * If rectangles a and b intersect, return true and set this rectangle to
436     * that intersection, otherwise return false and do not change this
437     * rectangle. No check is performed to see if either rectangle is empty.
438     * To just test for intersection, use intersects()
439     *
440     * @param a The first rectangle being intersected with
441     * @param b The second rectangle being intersected with
442     * @return true iff the two specified rectangles intersect. If they do, set
443     *              this rectangle to that intersection. If they do not, return
444     *              false and do not change this rectangle.
445     */
446    @CheckResult
447    public boolean setIntersect(Rect a, Rect b) {
448        if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) {
449            left = Math.max(a.left, b.left);
450            top = Math.max(a.top, b.top);
451            right = Math.min(a.right, b.right);
452            bottom = Math.min(a.bottom, b.bottom);
453            return true;
454        }
455        return false;
456    }
457
458    /**
459     * Returns true if this rectangle intersects the specified rectangle.
460     * In no event is this rectangle modified. No check is performed to see
461     * if either rectangle is empty. To record the intersection, use intersect()
462     * or setIntersect().
463     *
464     * @param left The left side of the rectangle being tested for intersection
465     * @param top The top of the rectangle being tested for intersection
466     * @param right The right side of the rectangle being tested for
467     *              intersection
468     * @param bottom The bottom of the rectangle being tested for intersection
469     * @return true iff the specified rectangle intersects this rectangle. In
470     *              no event is this rectangle modified.
471     */
472    public boolean intersects(int left, int top, int right, int bottom) {
473        return this.left < right && left < this.right && this.top < bottom && top < this.bottom;
474    }
475
476    /**
477     * Returns true iff the two specified rectangles intersect. In no event are
478     * either of the rectangles modified. To record the intersection,
479     * use {@link #intersect(Rect)} or {@link #setIntersect(Rect, Rect)}.
480     *
481     * @param a The first rectangle being tested for intersection
482     * @param b The second rectangle being tested for intersection
483     * @return true iff the two specified rectangles intersect. In no event are
484     *              either of the rectangles modified.
485     */
486    public static boolean intersects(Rect a, Rect b) {
487        return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
488    }
489
490    /**
491     * Update this Rect to enclose itself and the specified rectangle. If the
492     * specified rectangle is empty, nothing is done. If this rectangle is empty
493     * it is set to the specified rectangle.
494     *
495     * @param left The left edge being unioned with this rectangle
496     * @param top The top edge being unioned with this rectangle
497     * @param right The right edge being unioned with this rectangle
498     * @param bottom The bottom edge being unioned with this rectangle
499     */
500    public void union(int left, int top, int right, int bottom) {
501        if ((left < right) && (top < bottom)) {
502            if ((this.left < this.right) && (this.top < this.bottom)) {
503                if (this.left > left) this.left = left;
504                if (this.top > top) this.top = top;
505                if (this.right < right) this.right = right;
506                if (this.bottom < bottom) this.bottom = bottom;
507            } else {
508                this.left = left;
509                this.top = top;
510                this.right = right;
511                this.bottom = bottom;
512            }
513        }
514    }
515
516    /**
517     * Update this Rect to enclose itself and the specified rectangle. If the
518     * specified rectangle is empty, nothing is done. If this rectangle is empty
519     * it is set to the specified rectangle.
520     *
521     * @param r The rectangle being unioned with this rectangle
522     */
523    public void union(Rect r) {
524        union(r.left, r.top, r.right, r.bottom);
525    }
526
527    /**
528     * Update this Rect to enclose itself and the [x,y] coordinate. There is no
529     * check to see that this rectangle is non-empty.
530     *
531     * @param x The x coordinate of the point to add to the rectangle
532     * @param y The y coordinate of the point to add to the rectangle
533     */
534    public void union(int x, int y) {
535        if (x < left) {
536            left = x;
537        } else if (x > right) {
538            right = x;
539        }
540        if (y < top) {
541            top = y;
542        } else if (y > bottom) {
543            bottom = y;
544        }
545    }
546
547    /**
548     * Swap top/bottom or left/right if there are flipped (i.e. left > right
549     * and/or top > bottom). This can be called if
550     * the edges are computed separately, and may have crossed over each other.
551     * If the edges are already correct (i.e. left <= right and top <= bottom)
552     * then nothing is done.
553     */
554    public void sort() {
555        if (left > right) {
556            int temp = left;
557            left = right;
558            right = temp;
559        }
560        if (top > bottom) {
561            int temp = top;
562            top = bottom;
563            bottom = temp;
564        }
565    }
566
567    /**
568     * Parcelable interface methods
569     */
570    public int describeContents() {
571        return 0;
572    }
573
574    /**
575     * Write this rectangle to the specified parcel. To restore a rectangle from
576     * a parcel, use readFromParcel()
577     * @param out The parcel to write the rectangle's coordinates into
578     */
579    public void writeToParcel(Parcel out, int flags) {
580        out.writeInt(left);
581        out.writeInt(top);
582        out.writeInt(right);
583        out.writeInt(bottom);
584    }
585
586    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
587        /**
588         * Return a new rectangle from the data in the specified parcel.
589         */
590        public Rect createFromParcel(Parcel in) {
591            Rect r = new Rect();
592            r.readFromParcel(in);
593            return r;
594        }
595
596        /**
597         * Return an array of rectangles of the specified size.
598         */
599        public Rect[] newArray(int size) {
600            return new Rect[size];
601        }
602    };
603
604    /**
605     * Set the rectangle's coordinates from the data stored in the specified
606     * parcel. To write a rectangle to a parcel, call writeToParcel().
607     *
608     * @param in The parcel to read the rectangle's coordinates from
609     */
610    public void readFromParcel(Parcel in) {
611        left = in.readInt();
612        top = in.readInt();
613        right = in.readInt();
614        bottom = in.readInt();
615    }
616
617    /**
618     * Scales up the rect by the given scale.
619     * @hide
620     */
621    public void scale(float scale) {
622        if (scale != 1.0f) {
623            left = (int) (left * scale + 0.5f);
624            top = (int) (top * scale + 0.5f);
625            right = (int) (right * scale + 0.5f);
626            bottom = (int) (bottom * scale + 0.5f);
627        }
628    }
629
630    /**
631     * Scales up the rect by the given scale, rounding values toward the inside.
632     * @hide
633     */
634    public void scaleRoundIn(float scale) {
635        if (scale != 1.0f) {
636            left = (int) Math.ceil(left * scale);
637            top = (int) Math.ceil(top * scale);
638            right = (int) Math.floor(right * scale);
639            bottom = (int) Math.floor(bottom * scale);
640        }
641    }
642}
643