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