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.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Canvas;
     23 import android.graphics.DashPathEffect;
     24 import android.graphics.Matrix;
     25 import android.graphics.Paint;
     26 import android.graphics.Rect;
     27 import android.graphics.RectF;
     28 import android.graphics.drawable.Drawable;
     29 import android.graphics.drawable.NinePatchDrawable;
     30 import android.util.AttributeSet;
     31 import android.util.Log;
     32 import android.view.MotionEvent;
     33 import android.view.View;
     34 
     35 import com.android.camera2.R;
     36 
     37 public class CropView extends View {
     38     private static final String LOGTAG = "CropView";
     39 
     40     private RectF mImageBounds = new RectF();
     41     private RectF mScreenBounds = new RectF();
     42     private RectF mScreenImageBounds = new RectF();
     43     private RectF mScreenCropBounds = new RectF();
     44     private Rect mShadowBounds = new Rect();
     45 
     46     private Bitmap mBitmap;
     47     private Paint mPaint = new Paint();
     48 
     49     private NinePatchDrawable mShadow;
     50     private CropObject mCropObj = null;
     51     private Drawable mCropIndicator;
     52     private int mIndicatorSize;
     53     private int mRotation = 0;
     54     private boolean mMovingBlock = false;
     55     private Matrix mDisplayMatrix = null;
     56     private Matrix mDisplayMatrixInverse = null;
     57     private boolean mDirty = false;
     58 
     59     private float mPrevX = 0;
     60     private float mPrevY = 0;
     61     private float mSpotX = 0;
     62     private float mSpotY = 0;
     63     private boolean mDoSpot = false;
     64 
     65     private int mShadowMargin = 15;
     66     private int mMargin = 32;
     67     private int mOverlayShadowColor = 0xCF000000;
     68     private int mOverlayWPShadowColor = 0x5F000000;
     69     private int mWPMarkerColor = 0x7FFFFFFF;
     70     private int mMinSideSize = 90;
     71     private int mTouchTolerance = 40;
     72     private float mDashOnLength = 20;
     73     private float mDashOffLength = 10;
     74 
     75     private enum Mode {
     76         NONE, MOVE
     77     }
     78 
     79     private Mode mState = Mode.NONE;
     80 
     81     public CropView(Context context) {
     82         super(context);
     83         setup(context);
     84     }
     85 
     86     public CropView(Context context, AttributeSet attrs) {
     87         super(context, attrs);
     88         setup(context);
     89     }
     90 
     91     public CropView(Context context, AttributeSet attrs, int defStyle) {
     92         super(context, attrs, defStyle);
     93         setup(context);
     94     }
     95 
     96     private void setup(Context context) {
     97         Resources rsc = context.getResources();
     98         mShadow = (NinePatchDrawable) rsc.getDrawable(R.drawable.geometry_shadow);
     99         mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
    100         mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
    101         mShadowMargin = (int) rsc.getDimension(R.dimen.shadow_margin);
    102         mMargin = (int) rsc.getDimension(R.dimen.preview_margin);
    103         mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
    104         mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
    105         mOverlayShadowColor = (int) rsc.getColor(R.color.crop_shadow_color);
    106         mOverlayWPShadowColor = (int) rsc.getColor(R.color.crop_shadow_wp_color);
    107         mWPMarkerColor = (int) rsc.getColor(R.color.crop_wp_markers);
    108         mDashOnLength = rsc.getDimension(R.dimen.wp_selector_dash_length);
    109         mDashOffLength = rsc.getDimension(R.dimen.wp_selector_off_length);
    110     }
    111 
    112     public void initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation) {
    113         mBitmap = image;
    114         if (mCropObj != null) {
    115             RectF crop = mCropObj.getInnerBounds();
    116             RectF containing = mCropObj.getOuterBounds();
    117             if (crop != newCropBounds || containing != newPhotoBounds
    118                     || mRotation != rotation) {
    119                 mRotation = rotation;
    120                 mCropObj.resetBoundsTo(newCropBounds, newPhotoBounds);
    121                 clearDisplay();
    122             }
    123         } else {
    124             mRotation = rotation;
    125             mCropObj = new CropObject(newPhotoBounds, newCropBounds, 0);
    126             clearDisplay();
    127         }
    128     }
    129 
    130     public RectF getCrop() {
    131         return mCropObj.getInnerBounds();
    132     }
    133 
    134     public RectF getPhoto() {
    135         return mCropObj.getOuterBounds();
    136     }
    137 
    138     @Override
    139     public boolean onTouchEvent(MotionEvent event) {
    140         float x = event.getX();
    141         float y = event.getY();
    142         if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
    143             return true;
    144         }
    145         float[] touchPoint = {
    146                 x, y
    147         };
    148         mDisplayMatrixInverse.mapPoints(touchPoint);
    149         x = touchPoint[0];
    150         y = touchPoint[1];
    151         switch (event.getActionMasked()) {
    152             case (MotionEvent.ACTION_DOWN):
    153                 if (mState == Mode.NONE) {
    154                     if (!mCropObj.selectEdge(x, y)) {
    155                         mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
    156                     }
    157                     mPrevX = x;
    158                     mPrevY = y;
    159                     mState = Mode.MOVE;
    160                 }
    161                 break;
    162             case (MotionEvent.ACTION_UP):
    163                 if (mState == Mode.MOVE) {
    164                     mCropObj.selectEdge(CropObject.MOVE_NONE);
    165                     mMovingBlock = false;
    166                     mPrevX = x;
    167                     mPrevY = y;
    168                     mState = Mode.NONE;
    169                 }
    170                 break;
    171             case (MotionEvent.ACTION_MOVE):
    172                 if (mState == Mode.MOVE) {
    173                     float dx = x - mPrevX;
    174                     float dy = y - mPrevY;
    175                     mCropObj.moveCurrentSelection(dx, dy);
    176                     mPrevX = x;
    177                     mPrevY = y;
    178                 }
    179                 break;
    180             default:
    181                 break;
    182         }
    183         invalidate();
    184         return true;
    185     }
    186 
    187     private void reset() {
    188         Log.w(LOGTAG, "crop reset called");
    189         mState = Mode.NONE;
    190         mCropObj = null;
    191         mRotation = 0;
    192         mMovingBlock = false;
    193         clearDisplay();
    194     }
    195 
    196     private void clearDisplay() {
    197         mDisplayMatrix = null;
    198         mDisplayMatrixInverse = null;
    199         invalidate();
    200     }
    201 
    202     protected void configChanged() {
    203         mDirty = true;
    204     }
    205 
    206     public void applyFreeAspect() {
    207         mCropObj.unsetAspectRatio();
    208         invalidate();
    209     }
    210 
    211     public void applyOriginalAspect() {
    212         RectF outer = mCropObj.getOuterBounds();
    213         float w = outer.width();
    214         float h = outer.height();
    215         if (w > 0 && h > 0) {
    216             applyAspect(w, h);
    217             mCropObj.resetBoundsTo(outer, outer);
    218         } else {
    219             Log.w(LOGTAG, "failed to set aspect ratio original");
    220         }
    221     }
    222 
    223     public void applySquareAspect() {
    224         applyAspect(1, 1);
    225     }
    226 
    227     public void applyAspect(float x, float y) {
    228         if (x <= 0 || y <= 0) {
    229             throw new IllegalArgumentException("Bad arguments to applyAspect");
    230         }
    231         // If we are rotated by 90 degrees from horizontal, swap x and y
    232         if (((mRotation < 0) ? -mRotation : mRotation) % 180 == 90) {
    233             float tmp = x;
    234             x = y;
    235             y = tmp;
    236         }
    237         if (!mCropObj.setInnerAspectRatio(x, y)) {
    238             Log.w(LOGTAG, "failed to set aspect ratio");
    239         }
    240         invalidate();
    241     }
    242 
    243     public void setWallpaperSpotlight(float spotlightX, float spotlightY) {
    244         mSpotX = spotlightX;
    245         mSpotY = spotlightY;
    246         if (mSpotX > 0 && mSpotY > 0) {
    247             mDoSpot = true;
    248         }
    249     }
    250 
    251     public void unsetWallpaperSpotlight() {
    252         mDoSpot = false;
    253     }
    254 
    255     /**
    256      * Rotates first d bits in integer x to the left some number of times.
    257      */
    258     private int bitCycleLeft(int x, int times, int d) {
    259         int mask = (1 << d) - 1;
    260         int mout = x & mask;
    261         times %= d;
    262         int hi = mout >> (d - times);
    263         int low = (mout << times) & mask;
    264         int ret = x & ~mask;
    265         ret |= low;
    266         ret |= hi;
    267         return ret;
    268     }
    269 
    270     /**
    271      * Find the selected edge or corner in screen coordinates.
    272      */
    273     private int decode(int movingEdges, float rotation) {
    274         int rot = CropMath.constrainedRotation(rotation);
    275         switch (rot) {
    276             case 90:
    277                 return bitCycleLeft(movingEdges, 1, 4);
    278             case 180:
    279                 return bitCycleLeft(movingEdges, 2, 4);
    280             case 270:
    281                 return bitCycleLeft(movingEdges, 3, 4);
    282             default:
    283                 return movingEdges;
    284         }
    285     }
    286 
    287     @Override
    288     public void onDraw(Canvas canvas) {
    289         if (mBitmap == null) {
    290             return;
    291         }
    292         if (mDirty) {
    293             mDirty = false;
    294             clearDisplay();
    295         }
    296 
    297         mImageBounds = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
    298         mScreenBounds = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
    299         mScreenBounds.inset(mMargin, mMargin);
    300 
    301         // If crop object doesn't exist, create it and update it from master
    302         // state
    303         if (mCropObj == null) {
    304             reset();
    305             mCropObj = new CropObject(mImageBounds, mImageBounds, 0);
    306         }
    307 
    308         // If display matrix doesn't exist, create it and its dependencies
    309         if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
    310             mDisplayMatrix = new Matrix();
    311             mDisplayMatrix.reset();
    312             if (!CropDrawingUtils.setImageToScreenMatrix(mDisplayMatrix, mImageBounds, mScreenBounds,
    313                     mRotation)) {
    314                 Log.w(LOGTAG, "failed to get screen matrix");
    315                 mDisplayMatrix = null;
    316                 return;
    317             }
    318             mDisplayMatrixInverse = new Matrix();
    319             mDisplayMatrixInverse.reset();
    320             if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
    321                 Log.w(LOGTAG, "could not invert display matrix");
    322                 mDisplayMatrixInverse = null;
    323                 return;
    324             }
    325             // Scale min side and tolerance by display matrix scale factor
    326             mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
    327             mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
    328         }
    329 
    330         mScreenImageBounds.set(mImageBounds);
    331 
    332         // Draw background shadow
    333         if (mDisplayMatrix.mapRect(mScreenImageBounds)) {
    334             int margin = (int) mDisplayMatrix.mapRadius(mShadowMargin);
    335             mScreenImageBounds.roundOut(mShadowBounds);
    336             mShadowBounds.set(mShadowBounds.left - margin, mShadowBounds.top -
    337                     margin, mShadowBounds.right + margin, mShadowBounds.bottom + margin);
    338             mShadow.setBounds(mShadowBounds);
    339             mShadow.draw(canvas);
    340         }
    341 
    342         mPaint.setAntiAlias(true);
    343         mPaint.setFilterBitmap(true);
    344         // Draw actual bitmap
    345         canvas.drawBitmap(mBitmap, mDisplayMatrix, mPaint);
    346 
    347         mCropObj.getInnerBounds(mScreenCropBounds);
    348 
    349         if (mDisplayMatrix.mapRect(mScreenCropBounds)) {
    350 
    351             // Draw overlay shadows
    352             Paint p = new Paint();
    353             p.setColor(mOverlayShadowColor);
    354             p.setStyle(Paint.Style.FILL);
    355             CropDrawingUtils.drawShadows(canvas, p, mScreenCropBounds, mScreenImageBounds);
    356 
    357             // Draw crop rect and markers
    358             CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
    359             if (!mDoSpot) {
    360                 CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
    361             } else {
    362                 Paint wpPaint = new Paint();
    363                 wpPaint.setColor(mWPMarkerColor);
    364                 wpPaint.setStrokeWidth(3);
    365                 wpPaint.setStyle(Paint.Style.STROKE);
    366                 wpPaint.setPathEffect(new DashPathEffect(new float[]
    367                         {mDashOnLength, mDashOnLength + mDashOffLength}, 0));
    368                 p.setColor(mOverlayWPShadowColor);
    369                 CropDrawingUtils.drawWallpaperSelectionFrame(canvas, mScreenCropBounds,
    370                         mSpotX, mSpotY, wpPaint, p);
    371             }
    372             CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
    373                     mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mRotation));
    374         }
    375 
    376     }
    377 }
    378