• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CropView(Context context)81     public CropView(Context context) {
82         super(context);
83         setup(context);
84     }
85 
CropView(Context context, AttributeSet attrs)86     public CropView(Context context, AttributeSet attrs) {
87         super(context, attrs);
88         setup(context);
89     }
90 
CropView(Context context, AttributeSet attrs, int defStyle)91     public CropView(Context context, AttributeSet attrs, int defStyle) {
92         super(context, attrs, defStyle);
93         setup(context);
94     }
95 
setup(Context context)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 
initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation)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 
getCrop()130     public RectF getCrop() {
131         return mCropObj.getInnerBounds();
132     }
133 
getPhoto()134     public RectF getPhoto() {
135         return mCropObj.getOuterBounds();
136     }
137 
138     @Override
onTouchEvent(MotionEvent event)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 
reset()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 
clearDisplay()196     private void clearDisplay() {
197         mDisplayMatrix = null;
198         mDisplayMatrixInverse = null;
199         invalidate();
200     }
201 
configChanged()202     protected void configChanged() {
203         mDirty = true;
204     }
205 
applyFreeAspect()206     public void applyFreeAspect() {
207         mCropObj.unsetAspectRatio();
208         invalidate();
209     }
210 
applyOriginalAspect()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 
applySquareAspect()223     public void applySquareAspect() {
224         applyAspect(1, 1);
225     }
226 
applyAspect(float x, float y)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 
setWallpaperSpotlight(float spotlightX, float spotlightY)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 
unsetWallpaperSpotlight()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      */
bitCycleLeft(int x, int times, int d)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      */
decode(int movingEdges, float rotation)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
onDraw(Canvas canvas)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