Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2007 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;
     18 
     19 import com.android.gallery.R;
     20 
     21 import android.graphics.Canvas;
     22 import android.graphics.Matrix;
     23 import android.graphics.Paint;
     24 import android.graphics.Path;
     25 import android.graphics.Rect;
     26 import android.graphics.RectF;
     27 import android.graphics.Region;
     28 import android.graphics.drawable.Drawable;
     29 import android.view.View;
     30 
     31 // This class is used by CropImage to display a highlighted cropping rectangle
     32 // overlayed with the image. There are two coordinate spaces in use. One is
     33 // image, another is screen. computeLayout() uses mMatrix to map from image
     34 // space to screen space.
     35 class HighlightView {
     36 
     37     @SuppressWarnings("unused")
     38     private static final String TAG = "HighlightView";
     39     View mContext;  // The View displaying the image.
     40 
     41     public static final int GROW_NONE        = (1 << 0);
     42     public static final int GROW_LEFT_EDGE   = (1 << 1);
     43     public static final int GROW_RIGHT_EDGE  = (1 << 2);
     44     public static final int GROW_TOP_EDGE    = (1 << 3);
     45     public static final int GROW_BOTTOM_EDGE = (1 << 4);
     46     public static final int MOVE             = (1 << 5);
     47 
     48     public HighlightView(View ctx) {
     49         mContext = ctx;
     50     }
     51 
     52     private void init() {
     53         android.content.res.Resources resources = mContext.getResources();
     54         mResizeDrawableWidth =
     55                 resources.getDrawable(R.drawable.camera_crop_width);
     56         mResizeDrawableHeight =
     57                 resources.getDrawable(R.drawable.camera_crop_height);
     58         mResizeDrawableDiagonal =
     59                 resources.getDrawable(R.drawable.indicator_autocrop);
     60     }
     61 
     62     boolean mIsFocused;
     63     boolean mHidden;
     64 
     65     public boolean hasFocus() {
     66         return mIsFocused;
     67     }
     68 
     69     public void setFocus(boolean f) {
     70         mIsFocused = f;
     71     }
     72 
     73     public void setHidden(boolean hidden) {
     74         mHidden = hidden;
     75     }
     76 
     77     protected void draw(Canvas canvas) {
     78         if (mHidden) {
     79             return;
     80         }
     81         canvas.save();
     82         Path path = new Path();
     83         if (!hasFocus()) {
     84             mOutlinePaint.setColor(0xFF000000);
     85             canvas.drawRect(mDrawRect, mOutlinePaint);
     86         } else {
     87             Rect viewDrawingRect = new Rect();
     88             mContext.getDrawingRect(viewDrawingRect);
     89             if (mCircle) {
     90                 float width  = mDrawRect.width();
     91                 float height = mDrawRect.height();
     92                 path.addCircle(mDrawRect.left + (width  / 2),
     93                                mDrawRect.top + (height / 2),
     94                                width / 2,
     95                                Path.Direction.CW);
     96                 mOutlinePaint.setColor(0xFFEF04D6);
     97             } else {
     98                 path.addRect(new RectF(mDrawRect), Path.Direction.CW);
     99                 mOutlinePaint.setColor(0xFFFF8A00);
    100             }
    101             canvas.clipPath(path, Region.Op.DIFFERENCE);
    102             canvas.drawRect(viewDrawingRect,
    103                     hasFocus() ? mFocusPaint : mNoFocusPaint);
    104 
    105             canvas.restore();
    106             canvas.drawPath(path, mOutlinePaint);
    107 
    108             if (mMode == ModifyMode.Grow) {
    109                 if (mCircle) {
    110                     int width  = mResizeDrawableDiagonal.getIntrinsicWidth();
    111                     int height = mResizeDrawableDiagonal.getIntrinsicHeight();
    112 
    113                     int d  = (int) Math.round(Math.cos(/*45deg*/Math.PI / 4D)
    114                             * (mDrawRect.width() / 2D));
    115                     int x  = mDrawRect.left
    116                             + (mDrawRect.width() / 2) + d - width / 2;
    117                     int y  = mDrawRect.top
    118                             + (mDrawRect.height() / 2) - d - height / 2;
    119                     mResizeDrawableDiagonal.setBounds(x, y,
    120                             x + mResizeDrawableDiagonal.getIntrinsicWidth(),
    121                             y + mResizeDrawableDiagonal.getIntrinsicHeight());
    122                     mResizeDrawableDiagonal.draw(canvas);
    123                 } else {
    124                     int left    = mDrawRect.left   + 1;
    125                     int right   = mDrawRect.right  + 1;
    126                     int top     = mDrawRect.top    + 4;
    127                     int bottom  = mDrawRect.bottom + 3;
    128 
    129                     int widthWidth   =
    130                             mResizeDrawableWidth.getIntrinsicWidth() / 2;
    131                     int widthHeight  =
    132                             mResizeDrawableWidth.getIntrinsicHeight() / 2;
    133                     int heightHeight =
    134                             mResizeDrawableHeight.getIntrinsicHeight() / 2;
    135                     int heightWidth  =
    136                             mResizeDrawableHeight.getIntrinsicWidth() / 2;
    137 
    138                     int xMiddle = mDrawRect.left
    139                             + ((mDrawRect.right  - mDrawRect.left) / 2);
    140                     int yMiddle = mDrawRect.top
    141                             + ((mDrawRect.bottom - mDrawRect.top) / 2);
    142 
    143                     mResizeDrawableWidth.setBounds(left - widthWidth,
    144                                                    yMiddle - widthHeight,
    145                                                    left + widthWidth,
    146                                                    yMiddle + widthHeight);
    147                     mResizeDrawableWidth.draw(canvas);
    148 
    149                     mResizeDrawableWidth.setBounds(right - widthWidth,
    150                                                    yMiddle - widthHeight,
    151                                                    right + widthWidth,
    152                                                    yMiddle + widthHeight);
    153                     mResizeDrawableWidth.draw(canvas);
    154 
    155                     mResizeDrawableHeight.setBounds(xMiddle - heightWidth,
    156                                                     top - heightHeight,
    157                                                     xMiddle + heightWidth,
    158                                                     top + heightHeight);
    159                     mResizeDrawableHeight.draw(canvas);
    160 
    161                     mResizeDrawableHeight.setBounds(xMiddle - heightWidth,
    162                                                     bottom - heightHeight,
    163                                                     xMiddle + heightWidth,
    164                                                     bottom + heightHeight);
    165                     mResizeDrawableHeight.draw(canvas);
    166                 }
    167             }
    168         }
    169     }
    170 
    171     public void setMode(ModifyMode mode) {
    172         if (mode != mMode) {
    173             mMode = mode;
    174             mContext.invalidate();
    175         }
    176     }
    177 
    178     // Determines which edges are hit by touching at (x, y).
    179     public int getHit(float x, float y) {
    180         Rect r = computeLayout();
    181         final float hysteresis = 20F;
    182         int retval = GROW_NONE;
    183 
    184         if (mCircle) {
    185             float distX = x - r.centerX();
    186             float distY = y - r.centerY();
    187             int distanceFromCenter =
    188                     (int) Math.sqrt(distX * distX + distY * distY);
    189             int radius  = mDrawRect.width() / 2;
    190             int delta = distanceFromCenter - radius;
    191             if (Math.abs(delta) <= hysteresis) {
    192                 if (Math.abs(distY) > Math.abs(distX)) {
    193                     if (distY < 0) {
    194                         retval = GROW_TOP_EDGE;
    195                     } else {
    196                         retval = GROW_BOTTOM_EDGE;
    197                     }
    198                 } else {
    199                     if (distX < 0) {
    200                         retval = GROW_LEFT_EDGE;
    201                     } else {
    202                         retval = GROW_RIGHT_EDGE;
    203                     }
    204                 }
    205             } else if (distanceFromCenter < radius) {
    206                 retval = MOVE;
    207             } else {
    208                 retval = GROW_NONE;
    209             }
    210         } else {
    211             // verticalCheck makes sure the position is between the top and
    212             // the bottom edge (with some tolerance). Similar for horizCheck.
    213             boolean verticalCheck = (y >= r.top - hysteresis)
    214                     && (y < r.bottom + hysteresis);
    215             boolean horizCheck = (x >= r.left - hysteresis)
    216                     && (x < r.right + hysteresis);
    217 
    218             // Check whether the position is near some edge(s).
    219             if ((Math.abs(r.left - x)     < hysteresis)  &&  verticalCheck) {
    220                 retval |= GROW_LEFT_EDGE;
    221             }
    222             if ((Math.abs(r.right - x)    < hysteresis)  &&  verticalCheck) {
    223                 retval |= GROW_RIGHT_EDGE;
    224             }
    225             if ((Math.abs(r.top - y)      < hysteresis)  &&  horizCheck) {
    226                 retval |= GROW_TOP_EDGE;
    227             }
    228             if ((Math.abs(r.bottom - y)   < hysteresis)  &&  horizCheck) {
    229                 retval |= GROW_BOTTOM_EDGE;
    230             }
    231 
    232             // Not near any edge but inside the rectangle: move.
    233             if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
    234                 retval = MOVE;
    235             }
    236         }
    237         return retval;
    238     }
    239 
    240     // Handles motion (dx, dy) in screen space.
    241     // The "edge" parameter specifies which edges the user is dragging.
    242     void handleMotion(int edge, float dx, float dy) {
    243         Rect r = computeLayout();
    244         if (edge == GROW_NONE) {
    245             return;
    246         } else if (edge == MOVE) {
    247             // Convert to image space before sending to moveBy().
    248             moveBy(dx * (mCropRect.width() / r.width()),
    249                    dy * (mCropRect.height() / r.height()));
    250         } else {
    251             if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
    252                 dx = 0;
    253             }
    254 
    255             if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
    256                 dy = 0;
    257             }
    258 
    259             // Convert to image space before sending to growBy().
    260             float xDelta = dx * (mCropRect.width() / r.width());
    261             float yDelta = dy * (mCropRect.height() / r.height());
    262             growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,
    263                     (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
    264         }
    265     }
    266 
    267     // Grows the cropping rectange by (dx, dy) in image space.
    268     void moveBy(float dx, float dy) {
    269         Rect invalRect = new Rect(mDrawRect);
    270 
    271         mCropRect.offset(dx, dy);
    272 
    273         // Put the cropping rectangle inside image rectangle.
    274         mCropRect.offset(
    275                 Math.max(0, mImageRect.left - mCropRect.left),
    276                 Math.max(0, mImageRect.top  - mCropRect.top));
    277 
    278         mCropRect.offset(
    279                 Math.min(0, mImageRect.right  - mCropRect.right),
    280                 Math.min(0, mImageRect.bottom - mCropRect.bottom));
    281 
    282         mDrawRect = computeLayout();
    283         invalRect.union(mDrawRect);
    284         invalRect.inset(-10, -10);
    285         mContext.invalidate(invalRect);
    286     }
    287 
    288     // Grows the cropping rectange by (dx, dy) in image space.
    289     void growBy(float dx, float dy) {
    290         if (mMaintainAspectRatio) {
    291             if (dx != 0) {
    292                 dy = dx / mInitialAspectRatio;
    293             } else if (dy != 0) {
    294                 dx = dy * mInitialAspectRatio;
    295             }
    296         }
    297 
    298         // Don't let the cropping rectangle grow too fast.
    299         // Grow at most half of the difference between the image rectangle and
    300         // the cropping rectangle.
    301         RectF r = new RectF(mCropRect);
    302         if (dx > 0F && r.width() + 2 * dx > mImageRect.width()) {
    303             float adjustment = (mImageRect.width() - r.width()) / 2F;
    304             dx = adjustment;
    305             if (mMaintainAspectRatio) {
    306                 dy = dx / mInitialAspectRatio;
    307             }
    308         }
    309         if (dy > 0F && r.height() + 2 * dy > mImageRect.height()) {
    310             float adjustment = (mImageRect.height() - r.height()) / 2F;
    311             dy = adjustment;
    312             if (mMaintainAspectRatio) {
    313                 dx = dy * mInitialAspectRatio;
    314             }
    315         }
    316 
    317         r.inset(-dx, -dy);
    318 
    319         // Don't let the cropping rectangle shrink too fast.
    320         final float widthCap = 25F;
    321         if (r.width() < widthCap) {
    322             r.inset(-(widthCap - r.width()) / 2F, 0F);
    323         }
    324         float heightCap = mMaintainAspectRatio
    325                 ? (widthCap / mInitialAspectRatio)
    326                 : widthCap;
    327         if (r.height() < heightCap) {
    328             r.inset(0F, -(heightCap - r.height()) / 2F);
    329         }
    330 
    331         // Put the cropping rectangle inside the image rectangle.
    332         if (r.left < mImageRect.left) {
    333             r.offset(mImageRect.left - r.left, 0F);
    334         } else if (r.right > mImageRect.right) {
    335             r.offset(-(r.right - mImageRect.right), 0);
    336         }
    337         if (r.top < mImageRect.top) {
    338             r.offset(0F, mImageRect.top - r.top);
    339         } else if (r.bottom > mImageRect.bottom) {
    340             r.offset(0F, -(r.bottom - mImageRect.bottom));
    341         }
    342 
    343         mCropRect.set(r);
    344         mDrawRect = computeLayout();
    345         mContext.invalidate();
    346     }
    347 
    348     // Returns the cropping rectangle in image space.
    349     public Rect getCropRect() {
    350         return new Rect((int) mCropRect.left, (int) mCropRect.top,
    351                         (int) mCropRect.right, (int) mCropRect.bottom);
    352     }
    353 
    354     // Maps the cropping rectangle from image space to screen space.
    355     private Rect computeLayout() {
    356         RectF r = new RectF(mCropRect.left, mCropRect.top,
    357                             mCropRect.right, mCropRect.bottom);
    358         mMatrix.mapRect(r);
    359         return new Rect(Math.round(r.left), Math.round(r.top),
    360                         Math.round(r.right), Math.round(r.bottom));
    361     }
    362 
    363     public void invalidate() {
    364         mDrawRect = computeLayout();
    365     }
    366 
    367     public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean circle,
    368                       boolean maintainAspectRatio) {
    369         if (circle) {
    370             maintainAspectRatio = true;
    371         }
    372         mMatrix = new Matrix(m);
    373 
    374         mCropRect = cropRect;
    375         mImageRect = new RectF(imageRect);
    376         mMaintainAspectRatio = maintainAspectRatio;
    377         mCircle = circle;
    378 
    379         mInitialAspectRatio = mCropRect.width() / mCropRect.height();
    380         mDrawRect = computeLayout();
    381 
    382         mFocusPaint.setARGB(125, 50, 50, 50);
    383         mNoFocusPaint.setARGB(125, 50, 50, 50);
    384         mOutlinePaint.setStrokeWidth(3F);
    385         mOutlinePaint.setStyle(Paint.Style.STROKE);
    386         mOutlinePaint.setAntiAlias(true);
    387 
    388         mMode = ModifyMode.None;
    389         init();
    390     }
    391 
    392     enum ModifyMode { None, Move, Grow }
    393 
    394     private ModifyMode mMode = ModifyMode.None;
    395 
    396     Rect mDrawRect;  // in screen space
    397     private RectF mImageRect;  // in image space
    398     RectF mCropRect;  // in image space
    399     Matrix mMatrix;
    400 
    401     private boolean mMaintainAspectRatio = false;
    402     private float mInitialAspectRatio;
    403     private boolean mCircle = false;
    404 
    405     private Drawable mResizeDrawableWidth;
    406     private Drawable mResizeDrawableHeight;
    407     private Drawable mResizeDrawableDiagonal;
    408 
    409     private final Paint mFocusPaint = new Paint();
    410     private final Paint mNoFocusPaint = new Paint();
    411     private final Paint mOutlinePaint = new Paint();
    412 }
    413