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