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