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