Home | History | Annotate | Download | only in crop
      1 /*
      2  * Copyright (C) 2013 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 com.android.camera.crop;
     18 
     19 import android.graphics.Rect;
     20 import android.graphics.RectF;
     21 
     22 public class CropObject {
     23     private BoundedRect mBoundedRect;
     24     private float mAspectWidth = 1;
     25     private float mAspectHeight = 1;
     26     private boolean mFixAspectRatio = false;
     27     private float mRotation = 0;
     28     private float mTouchTolerance = 45;
     29     private float mMinSideSize = 20;
     30 
     31     public static final int MOVE_NONE = 0;
     32     // Sides
     33     public static final int MOVE_LEFT = 1;
     34     public static final int MOVE_TOP = 2;
     35     public static final int MOVE_RIGHT = 4;
     36     public static final int MOVE_BOTTOM = 8;
     37     public static final int MOVE_BLOCK = 16;
     38 
     39     // Corners
     40     public static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT;
     41     public static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT;
     42     public static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT;
     43     public static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT;
     44 
     45     private int mMovingEdges = MOVE_NONE;
     46 
     47     public CropObject(Rect outerBound, Rect innerBound, int outerAngle) {
     48         mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
     49     }
     50 
     51     public CropObject(RectF outerBound, RectF innerBound, int outerAngle) {
     52         mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
     53     }
     54 
     55     public void resetBoundsTo(RectF inner, RectF outer) {
     56         mBoundedRect.resetTo(0, outer, inner);
     57     }
     58 
     59     public void getInnerBounds(RectF r) {
     60         mBoundedRect.setToInner(r);
     61     }
     62 
     63     public void getOuterBounds(RectF r) {
     64         mBoundedRect.setToOuter(r);
     65     }
     66 
     67     public RectF getInnerBounds() {
     68         return mBoundedRect.getInner();
     69     }
     70 
     71     public RectF getOuterBounds() {
     72         return mBoundedRect.getOuter();
     73     }
     74 
     75     public int getSelectState() {
     76         return mMovingEdges;
     77     }
     78 
     79     public boolean isFixedAspect() {
     80         return mFixAspectRatio;
     81     }
     82 
     83     public void rotateOuter(int angle) {
     84         mRotation = angle % 360;
     85         mBoundedRect.setRotation(mRotation);
     86         clearSelectState();
     87     }
     88 
     89     public boolean setInnerAspectRatio(float width, float height) {
     90         if (width <= 0 || height <= 0) {
     91             throw new IllegalArgumentException("Width and Height must be greater than zero");
     92         }
     93         RectF inner = mBoundedRect.getInner();
     94         CropMath.fixAspectRatioContained(inner, width, height);
     95         if (inner.width() < mMinSideSize || inner.height() < mMinSideSize) {
     96             return false;
     97         }
     98         mAspectWidth = width;
     99         mAspectHeight = height;
    100         mFixAspectRatio = true;
    101         mBoundedRect.setInner(inner);
    102         clearSelectState();
    103         return true;
    104     }
    105 
    106     public void setTouchTolerance(float tolerance) {
    107         if (tolerance <= 0) {
    108             throw new IllegalArgumentException("Tolerance must be greater than zero");
    109         }
    110         mTouchTolerance = tolerance;
    111     }
    112 
    113     public void setMinInnerSideSize(float minSide) {
    114         if (minSide <= 0) {
    115             throw new IllegalArgumentException("Min dide must be greater than zero");
    116         }
    117         mMinSideSize = minSide;
    118     }
    119 
    120     public void unsetAspectRatio() {
    121         mFixAspectRatio = false;
    122         clearSelectState();
    123     }
    124 
    125     public boolean hasSelectedEdge() {
    126         return mMovingEdges != MOVE_NONE;
    127     }
    128 
    129     public static boolean checkCorner(int selected) {
    130         return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT
    131                 || selected == BOTTOM_LEFT;
    132     }
    133 
    134     public static boolean checkEdge(int selected) {
    135         return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT
    136                 || selected == MOVE_BOTTOM;
    137     }
    138 
    139     public static boolean checkBlock(int selected) {
    140         return selected == MOVE_BLOCK;
    141     }
    142 
    143     public static boolean checkValid(int selected) {
    144         return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected)
    145                 || checkCorner(selected);
    146     }
    147 
    148     public void clearSelectState() {
    149         mMovingEdges = MOVE_NONE;
    150     }
    151 
    152     public int wouldSelectEdge(float x, float y) {
    153         int edgeSelected = calculateSelectedEdge(x, y);
    154         if (edgeSelected != MOVE_NONE && edgeSelected != MOVE_BLOCK) {
    155             return edgeSelected;
    156         }
    157         return MOVE_NONE;
    158     }
    159 
    160     public boolean selectEdge(int edge) {
    161         if (!checkValid(edge)) {
    162             // temporary
    163             throw new IllegalArgumentException("bad edge selected");
    164             // return false;
    165         }
    166         if ((mFixAspectRatio && !checkCorner(edge)) && !checkBlock(edge) && edge != MOVE_NONE) {
    167             // temporary
    168             throw new IllegalArgumentException("bad corner selected");
    169             // return false;
    170         }
    171         mMovingEdges = edge;
    172         return true;
    173     }
    174 
    175     public boolean selectEdge(float x, float y) {
    176         int edgeSelected = calculateSelectedEdge(x, y);
    177         if (mFixAspectRatio) {
    178             edgeSelected = fixEdgeToCorner(edgeSelected);
    179         }
    180         if (edgeSelected == MOVE_NONE) {
    181             return false;
    182         }
    183         return selectEdge(edgeSelected);
    184     }
    185 
    186     public boolean moveCurrentSelection(float dX, float dY) {
    187         if (mMovingEdges == MOVE_NONE) {
    188             return false;
    189         }
    190         RectF crop = mBoundedRect.getInner();
    191 
    192         float minWidthHeight = mMinSideSize;
    193 
    194         int movingEdges = mMovingEdges;
    195         if (movingEdges == MOVE_BLOCK) {
    196             mBoundedRect.moveInner(dX, dY);
    197             return true;
    198         } else {
    199             float dx = 0;
    200             float dy = 0;
    201 
    202             if ((movingEdges & MOVE_LEFT) != 0) {
    203                 dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left;
    204             }
    205             if ((movingEdges & MOVE_TOP) != 0) {
    206                 dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top;
    207             }
    208             if ((movingEdges & MOVE_RIGHT) != 0) {
    209                 dx = Math.max(crop.right + dX, crop.left + minWidthHeight)
    210                         - crop.right;
    211             }
    212             if ((movingEdges & MOVE_BOTTOM) != 0) {
    213                 dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight)
    214                         - crop.bottom;
    215             }
    216 
    217             if (mFixAspectRatio) {
    218                 float[] l1 = {
    219                         crop.left, crop.bottom
    220                 };
    221                 float[] l2 = {
    222                         crop.right, crop.top
    223                 };
    224                 if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) {
    225                     l1[1] = crop.top;
    226                     l2[1] = crop.bottom;
    227                 }
    228                 float[] b = {
    229                         l1[0] - l2[0], l1[1] - l2[1]
    230                 };
    231                 float[] disp = {
    232                         dx, dy
    233                 };
    234                 float[] bUnit = GeometryMathUtils.normalize(b);
    235                 float sp = GeometryMathUtils.scalarProjection(disp, bUnit);
    236                 dx = sp * bUnit[0];
    237                 dy = sp * bUnit[1];
    238                 RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy);
    239 
    240                 mBoundedRect.fixedAspectResizeInner(newCrop);
    241             } else {
    242                 if ((movingEdges & MOVE_LEFT) != 0) {
    243                     crop.left += dx;
    244                 }
    245                 if ((movingEdges & MOVE_TOP) != 0) {
    246                     crop.top += dy;
    247                 }
    248                 if ((movingEdges & MOVE_RIGHT) != 0) {
    249                     crop.right += dx;
    250                 }
    251                 if ((movingEdges & MOVE_BOTTOM) != 0) {
    252                     crop.bottom += dy;
    253                 }
    254                 mBoundedRect.resizeInner(crop);
    255             }
    256         }
    257         return true;
    258     }
    259 
    260     // Helper methods
    261 
    262     private int calculateSelectedEdge(float x, float y) {
    263         RectF cropped = mBoundedRect.getInner();
    264 
    265         float left = Math.abs(x - cropped.left);
    266         float right = Math.abs(x - cropped.right);
    267         float top = Math.abs(y - cropped.top);
    268         float bottom = Math.abs(y - cropped.bottom);
    269 
    270         int edgeSelected = MOVE_NONE;
    271         // Check left or right.
    272         if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
    273                 && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) {
    274             edgeSelected |= MOVE_LEFT;
    275         }
    276         else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
    277                 && ((y - mTouchTolerance) <= cropped.bottom)) {
    278             edgeSelected |= MOVE_RIGHT;
    279         }
    280 
    281         // Check top or bottom.
    282         if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
    283                 && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) {
    284             edgeSelected |= MOVE_TOP;
    285         }
    286         else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
    287                 && ((x - mTouchTolerance) <= cropped.right)) {
    288             edgeSelected |= MOVE_BOTTOM;
    289         }
    290         return edgeSelected;
    291     }
    292 
    293     private static RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) {
    294         RectF newCrop = null;
    295         // Fix opposite corner in place and move sides
    296         if (moving_corner == BOTTOM_RIGHT) {
    297             newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height()
    298                     + dy);
    299         } else if (moving_corner == BOTTOM_LEFT) {
    300             newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height()
    301                     + dy);
    302         } else if (moving_corner == TOP_LEFT) {
    303             newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy,
    304                     r.right, r.bottom);
    305         } else if (moving_corner == TOP_RIGHT) {
    306             newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left
    307                     + r.width() + dx, r.bottom);
    308         }
    309         return newCrop;
    310     }
    311 
    312     private static int fixEdgeToCorner(int moving_edges) {
    313         if (moving_edges == MOVE_LEFT) {
    314             moving_edges |= MOVE_TOP;
    315         }
    316         if (moving_edges == MOVE_TOP) {
    317             moving_edges |= MOVE_LEFT;
    318         }
    319         if (moving_edges == MOVE_RIGHT) {
    320             moving_edges |= MOVE_BOTTOM;
    321         }
    322         if (moving_edges == MOVE_BOTTOM) {
    323             moving_edges |= MOVE_RIGHT;
    324         }
    325         return moving_edges;
    326     }
    327 
    328 }
    329