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