CropView.java revision c3178ca4daf7ed570f3432f3433959c208ecc105
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
17package com.android.gallery3d.filtershow.crop;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Matrix;
24import android.graphics.Paint;
25import android.graphics.Rect;
26import android.graphics.RectF;
27import android.graphics.drawable.Drawable;
28import android.graphics.drawable.NinePatchDrawable;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.view.MotionEvent;
32import android.view.View;
33
34import com.android.gallery3d.R;
35import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
36
37
38public 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 final Drawable mCropIndicator;
53    private final 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
63    private int mShadowMargin = 15;
64    private int mMargin = 32;
65    private int mOverlayShadowColor = 0xCF000000;
66    private int mMinSideSize = 90;
67    private int mTouchTolerance = 40;
68
69    private enum Mode {
70        NONE, MOVE
71    }
72
73    private Mode mState = Mode.NONE;
74
75    public CropView(Context context, AttributeSet attrs) {
76        super(context, attrs);
77        Resources rsc = context.getResources();
78        mShadow = (NinePatchDrawable) rsc.getDrawable(R.drawable.geometry_shadow);
79        mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
80        mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
81        mShadowMargin = (int) rsc.getDimension(R.dimen.shadow_margin);
82        mMargin = (int) rsc.getDimension(R.dimen.preview_margin);
83        mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
84        mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
85        mOverlayShadowColor = (int) rsc.getColor(R.color.crop_shadow_color);
86    }
87
88    public void initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation) {
89        mBitmap = image;
90        if (mCropObj != null) {
91            RectF crop = mCropObj.getInnerBounds();
92            RectF containing = mCropObj.getOuterBounds();
93            if (crop != newCropBounds || containing != newPhotoBounds
94                    || mRotation != rotation) {
95                mRotation = rotation;
96                mCropObj.resetBoundsTo(newCropBounds, newPhotoBounds);
97                clearDisplay();
98            }
99        } else {
100            mRotation = rotation;
101            mCropObj = new CropObject(newPhotoBounds, newCropBounds, 0);
102            clearDisplay();
103        }
104    }
105
106    public RectF getCrop() {
107        return mCropObj.getInnerBounds();
108    }
109
110    public RectF getPhoto() {
111        return mCropObj.getOuterBounds();
112    }
113
114    @Override
115    public boolean onTouchEvent(MotionEvent event) {
116        float x = event.getX();
117        float y = event.getY();
118        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
119            return true;
120        }
121        float[] touchPoint = {
122                x, y
123        };
124        mDisplayMatrixInverse.mapPoints(touchPoint);
125        x = touchPoint[0];
126        y = touchPoint[1];
127        switch (event.getActionMasked()) {
128            case (MotionEvent.ACTION_DOWN):
129                if (mState == Mode.NONE) {
130                    if (!mCropObj.selectEdge(x, y)) {
131                        mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
132                    }
133                    mPrevX = x;
134                    mPrevY = y;
135                    mState = Mode.MOVE;
136                }
137                break;
138            case (MotionEvent.ACTION_UP):
139                if (mState == Mode.MOVE) {
140                    mCropObj.selectEdge(CropObject.MOVE_NONE);
141                    mMovingBlock = false;
142                    mPrevX = x;
143                    mPrevY = y;
144                    mState = Mode.NONE;
145                }
146                break;
147            case (MotionEvent.ACTION_MOVE):
148                if (mState == Mode.MOVE) {
149                    float dx = x - mPrevX;
150                    float dy = y - mPrevY;
151                    mCropObj.moveCurrentSelection(dx, dy);
152                    mPrevX = x;
153                    mPrevY = y;
154                }
155                break;
156            default:
157                break;
158        }
159        invalidate();
160        return true;
161    }
162
163    private void reset() {
164        Log.w(LOGTAG, "crop reset called");
165        mState = Mode.NONE;
166        mCropObj = null;
167        mRotation = 0;
168        mMovingBlock = false;
169        clearDisplay();
170    }
171
172    private void clearDisplay() {
173        mDisplayMatrix = null;
174        mDisplayMatrixInverse = null;
175        invalidate();
176    }
177
178    protected void configChanged() {
179        mDirty = true;
180    }
181
182    public void applyFreeAspect() {
183        mCropObj.unsetAspectRatio();
184        invalidate();
185    }
186
187    public void applyOriginalAspect() {
188        RectF outer = mCropObj.getOuterBounds();
189        if (!mCropObj.setInnerAspectRatio((int) outer.width(), (int) outer.height())) {
190            Log.w(LOGTAG, "failed to set aspect ratio original");
191        }
192        mCropObj.resetBoundsTo(outer, outer);
193        invalidate();
194    }
195
196    public void applySquareAspect() {
197        if (!mCropObj.setInnerAspectRatio(1, 1)) {
198            Log.w(LOGTAG, "failed to set aspect ratio square");
199        }
200        invalidate();
201    }
202
203    @Override
204    public void onDraw(Canvas canvas) {
205        if (mBitmap == null) {
206            return;
207        }
208        if (mDirty) {
209            mDirty = false;
210            clearDisplay();
211        }
212
213        mImageBounds = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
214        mScreenBounds = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
215        mScreenBounds.inset(mMargin, mMargin);
216
217        // If crop object doesn't exist, create it and update it from master
218        // state
219        if (mCropObj == null) {
220            reset();
221            mCropObj = new CropObject(mImageBounds, mImageBounds, 0);
222        }
223
224        // If display matrix doesn't exist, create it and its dependencies
225        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
226            mDisplayMatrix = new Matrix();
227            mDisplayMatrix.reset();
228            if (!CropDrawingUtils.setImageToScreenMatrix(mDisplayMatrix, mImageBounds, mScreenBounds,
229                    mRotation)) {
230                Log.w(LOGTAG, "failed to get screen matrix");
231                mDisplayMatrix = null;
232                return;
233            }
234            mDisplayMatrixInverse = new Matrix();
235            mDisplayMatrixInverse.reset();
236            if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
237                Log.w(LOGTAG, "could not invert display matrix");
238                mDisplayMatrixInverse = null;
239                return;
240            }
241            // Scale min side and tolerance by display matrix scale factor
242            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
243            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
244        }
245
246        mScreenImageBounds.set(mImageBounds);
247
248        // Draw background shadow
249        if (mDisplayMatrix.mapRect(mScreenImageBounds)) {
250            int margin = (int) mDisplayMatrix.mapRadius(mShadowMargin);
251            mScreenImageBounds.roundOut(mShadowBounds);
252            mShadowBounds.set(mShadowBounds.left - margin, mShadowBounds.top -
253                    margin, mShadowBounds.right + margin, mShadowBounds.bottom + margin);
254            mShadow.setBounds(mShadowBounds);
255            mShadow.draw(canvas);
256        }
257
258        // Draw actual bitmap
259        canvas.drawBitmap(mBitmap, mDisplayMatrix, mPaint);
260
261        mCropObj.getInnerBounds(mScreenCropBounds);
262
263        if (mDisplayMatrix.mapRect(mScreenCropBounds)) {
264
265            // Draw overlay shadows
266            Paint p = new Paint();
267            p.setColor(mOverlayShadowColor);
268            p.setStyle(Paint.Style.FILL);
269            CropDrawingUtils.drawShadows(canvas, p, mScreenCropBounds, mScreenImageBounds);
270
271            // Draw crop rect and markers
272            CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
273            CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
274            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
275                    mScreenCropBounds, mCropObj.isFixedAspect(), mCropObj.getSelectState());
276        }
277
278    }
279}
280