• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.photoeditor.actions;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Paint;
24 import android.graphics.Rect;
25 import android.graphics.RectF;
26 import android.graphics.Region;
27 import android.graphics.RegionIterator;
28 import android.graphics.drawable.Drawable;
29 import android.util.AttributeSet;
30 import android.view.MotionEvent;
31 import android.view.View;
32 
33 import com.android.photoeditor.R;
34 
35 import java.util.Vector;
36 
37 /**
38  * A view that track touch motions and adjust crop bounds accordingly.
39  */
40 class CropView extends View {
41 
42     /**
43      * Listener of crop bounds.
44      */
45     public interface OnCropChangeListener {
46 
onCropChanged(RectF bounds, boolean fromUser)47         void onCropChanged(RectF bounds, boolean fromUser);
48     }
49 
50     private static final int TOUCH_AREA_NONE = 0;
51     private static final int TOUCH_AREA_LEFT = 1;
52     private static final int TOUCH_AREA_TOP = 2;
53     private static final int TOUCH_AREA_RIGHT = 4;
54     private static final int TOUCH_AREA_BOTTOM = 8;
55     private static final int TOUCH_AREA_INSIDE = 15;
56     private static final int TOUCH_AREA_OUTSIDE = 16;
57     private static final int TOUCH_AREA_TOP_LEFT = 3;
58     private static final int TOUCH_AREA_TOP_RIGHT = 6;
59     private static final int TOUCH_AREA_BOTTOM_LEFT = 9;
60     private static final int TOUCH_AREA_BOTTOM_RIGHT = 12;
61 
62     private static final int BORDER_COLOR = 0xFF008AFF;
63     private static final int OUTER_COLOR = 0xA0000000;
64     private static final int INDICATION_COLOR = 0xFFCC9900;
65     private static final int TOUCH_AREA_SPAN = 20;
66     private static final int TOUCH_AREA_SPAN2 = TOUCH_AREA_SPAN * 2;
67     private static final float BORDER_WIDTH = 2.0f;
68 
69     private final Paint outerAreaPaint;
70     private final Paint borderPaint;
71     private final Paint highlightPaint;
72 
73     private final Drawable heightIndicator;
74     private final Drawable widthIndicator;
75     private final int indicatorSize;
76 
77     private RectF cropBounds;
78     private RectF photoBounds;
79 
80     private OnCropChangeListener listener;
81 
82     private float lastX;
83     private float lastY;
84     private int currentTouchArea;
85 
CropView(Context context, AttributeSet attrs)86     public CropView(Context context, AttributeSet attrs) {
87         super(context, attrs);
88 
89         Resources resources = context.getResources();
90         heightIndicator = resources.getDrawable(R.drawable.crop_height_holo);
91         widthIndicator = resources.getDrawable(R.drawable.crop_width_holo);
92         indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size);
93 
94         outerAreaPaint = new Paint();
95         outerAreaPaint.setStyle(Paint.Style.FILL);
96         outerAreaPaint.setColor(OUTER_COLOR);
97 
98         borderPaint = new Paint();
99         borderPaint.setStyle(Paint.Style.STROKE);
100         borderPaint.setColor(BORDER_COLOR);
101         borderPaint.setStrokeWidth(BORDER_WIDTH);
102 
103         highlightPaint = new Paint();
104         highlightPaint.setStyle(Paint.Style.STROKE);
105         highlightPaint.setColor(INDICATION_COLOR);
106         highlightPaint.setStrokeWidth(BORDER_WIDTH);
107 
108         currentTouchArea = TOUCH_AREA_NONE;
109     }
110 
setOnCropChangeListener(OnCropChangeListener listener)111     public void setOnCropChangeListener(OnCropChangeListener listener) {
112         this.listener = listener;
113     }
114 
notifyCropChange(boolean fromUser)115     private void notifyCropChange(boolean fromUser) {
116         if (listener != null) {
117             listener.onCropChanged(cropBounds, fromUser);
118         }
119     }
120 
setCropBounds(RectF bounds)121     public void setCropBounds(RectF bounds) {
122         bounds.intersect(photoBounds);
123         cropBounds = bounds;
124         if (photoBounds.width() <= TOUCH_AREA_SPAN2) {
125             cropBounds.left = photoBounds.left;
126             cropBounds.right = photoBounds.right;
127         }
128         if (photoBounds.height() <= TOUCH_AREA_SPAN2) {
129             cropBounds.top = photoBounds.top;
130             cropBounds.bottom = photoBounds.bottom;
131         }
132         notifyCropChange(false);
133         invalidate();
134     }
135 
136     /**
137      * Sets bounds to crop within.
138      */
setPhotoBounds(RectF bounds)139     public void setPhotoBounds(RectF bounds) {
140         photoBounds = bounds;
141     }
142 
fullPhotoCropped()143     public boolean fullPhotoCropped() {
144         return cropBounds.contains(photoBounds);
145     }
146 
detectTouchArea(float x, float y)147     private int detectTouchArea(float x, float y) {
148         RectF area = new RectF();
149         area.set(cropBounds);
150         area.inset(-TOUCH_AREA_SPAN, -TOUCH_AREA_SPAN);
151         if (!area.contains(x, y)) {
152             return TOUCH_AREA_OUTSIDE;
153         }
154 
155         // left
156         area.set(cropBounds.left - TOUCH_AREA_SPAN, cropBounds.top  + TOUCH_AREA_SPAN,
157                 cropBounds.left + TOUCH_AREA_SPAN, cropBounds.bottom - TOUCH_AREA_SPAN);
158         if (area.contains(x, y)) {
159             return TOUCH_AREA_LEFT;
160         }
161         // right
162         area.offset(cropBounds.width(), 0f);
163         if (area.contains(x, y)) {
164             return TOUCH_AREA_RIGHT;
165         }
166         // top
167         area.set(cropBounds.left + TOUCH_AREA_SPAN, cropBounds.top - TOUCH_AREA_SPAN,
168                 cropBounds.right - TOUCH_AREA_SPAN, cropBounds.top + TOUCH_AREA_SPAN);
169         if (area.contains(x, y)) {
170             return TOUCH_AREA_TOP;
171         }
172         // bottom
173         area.offset(0f, cropBounds.height());
174         if (area.contains(x, y)) {
175             return TOUCH_AREA_BOTTOM;
176         }
177         // top left
178         area.set(cropBounds.left - TOUCH_AREA_SPAN, cropBounds.top - TOUCH_AREA_SPAN,
179                 cropBounds.left + TOUCH_AREA_SPAN, cropBounds.top + TOUCH_AREA_SPAN);
180         if (area.contains(x, y)) {
181             return TOUCH_AREA_TOP_LEFT;
182         }
183         // top right
184         area.offset(cropBounds.width(), 0f);
185         if (area.contains(x, y)) {
186             return TOUCH_AREA_TOP_RIGHT;
187         }
188         // bottom right
189         area.offset(0f, cropBounds.height());
190         if (area.contains(x, y)) {
191             return TOUCH_AREA_BOTTOM_RIGHT;
192         }
193         // bottom left
194         area.offset(-cropBounds.width(), 0f);
195         if (area.contains(x, y)) {
196             return TOUCH_AREA_BOTTOM_LEFT;
197         }
198         return TOUCH_AREA_INSIDE;
199     }
200 
performMove(float deltaX, float deltaY)201     private void performMove(float deltaX, float deltaY) {
202         if (currentTouchArea == TOUCH_AREA_INSIDE){  // moving the rect.
203             cropBounds.offset(deltaX, deltaY);
204             if (cropBounds.left < photoBounds.left) {
205                 cropBounds.offset(photoBounds.left - cropBounds.left, 0f);
206             } else if (cropBounds.right > photoBounds.right) {
207                 cropBounds.offset(photoBounds.right - cropBounds.right, 0f);
208             }
209             if (cropBounds.top < photoBounds.top) {
210                 cropBounds.offset(0f, photoBounds.top - cropBounds.top);
211             } else if (cropBounds.bottom > photoBounds.bottom) {
212                 cropBounds.offset(0f, photoBounds.bottom - cropBounds.bottom);
213             }
214         } else {  // adjusting bounds.
215             if ((currentTouchArea & TOUCH_AREA_LEFT) != 0) {
216                 cropBounds.left = Math.min(cropBounds.left + deltaX,
217                         cropBounds.right - TOUCH_AREA_SPAN2);
218             }
219             if ((currentTouchArea & TOUCH_AREA_TOP) != 0) {
220                 cropBounds.top = Math.min(cropBounds.top + deltaY,
221                         cropBounds.bottom - TOUCH_AREA_SPAN2);
222             }
223             if ((currentTouchArea & TOUCH_AREA_RIGHT) != 0) {
224                 cropBounds.right = Math.max(cropBounds.right + deltaX,
225                         cropBounds.left + TOUCH_AREA_SPAN2);
226             }
227             if ((currentTouchArea & TOUCH_AREA_BOTTOM) != 0) {
228                 cropBounds.bottom = Math.max(cropBounds.bottom + deltaY,
229                         cropBounds.top + TOUCH_AREA_SPAN2);
230             }
231             cropBounds.intersect(photoBounds);
232         }
233     }
234 
235     @Override
onTouchEvent(MotionEvent event)236     public boolean onTouchEvent(MotionEvent event) {
237         super.onTouchEvent(event);
238 
239         if (!isEnabled()) {
240             return true;
241         }
242         float x = event.getX();
243         float y = event.getY();
244 
245         switch (event.getAction()) {
246             case MotionEvent.ACTION_DOWN:
247                 currentTouchArea = detectTouchArea(x, y);
248                 lastX = x;
249                 lastY = y;
250                 invalidate();
251                 break;
252 
253             case MotionEvent.ACTION_MOVE:
254                 performMove(x - lastX, y - lastY);
255 
256                 lastX = x;
257                 lastY = y;
258                 notifyCropChange(true);
259                 invalidate();
260                 break;
261 
262             case MotionEvent.ACTION_CANCEL:
263             case MotionEvent.ACTION_UP:
264                 currentTouchArea = TOUCH_AREA_NONE;
265                 invalidate();
266                 break;
267         }
268         return true;
269     }
270 
drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY)271     private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) {
272         int left = (int) centerX - indicatorSize / 2;
273         int top = (int) centerY - indicatorSize / 2;
274         int right = left + indicatorSize;
275         int bottom = top + indicatorSize;
276         indicator.setBounds(left, top, right, bottom);
277         indicator.draw(canvas);
278     }
279 
drawIndicators(Canvas canvas)280     private void drawIndicators(Canvas canvas) {
281         drawIndicator(canvas, heightIndicator, cropBounds.centerX(), cropBounds.top);
282         drawIndicator(canvas, heightIndicator, cropBounds.centerX(), cropBounds.bottom);
283         drawIndicator(canvas, widthIndicator, cropBounds.left, cropBounds.centerY());
284         drawIndicator(canvas, widthIndicator, cropBounds.right, cropBounds.centerY());
285     }
286 
drawTouchHighlights(Canvas canvas)287     private void drawTouchHighlights(Canvas canvas) {
288         if ((currentTouchArea & TOUCH_AREA_TOP) != 0) {
289             canvas.drawLine(cropBounds.left, cropBounds.top, cropBounds.right, cropBounds.top,
290                     highlightPaint);
291         }
292         if ((currentTouchArea & TOUCH_AREA_BOTTOM) != 0) {
293             canvas.drawLine(cropBounds.left, cropBounds.bottom, cropBounds.right,
294                     cropBounds.bottom, highlightPaint);
295         }
296         if ((currentTouchArea & TOUCH_AREA_LEFT) != 0) {
297             canvas.drawLine(cropBounds.left, cropBounds.top, cropBounds.left, cropBounds.bottom,
298                     highlightPaint);
299         }
300         if ((currentTouchArea & TOUCH_AREA_RIGHT) != 0) {
301             canvas.drawLine(cropBounds.right, cropBounds.top, cropBounds.right, cropBounds.bottom,
302                     highlightPaint);
303         }
304     }
305 
drawBounds(Canvas canvas)306     private void drawBounds(Canvas canvas) {
307         Rect r = new Rect();
308         photoBounds.roundOut(r);
309         Region drawRegion = new Region(r);
310         cropBounds.roundOut(r);
311         drawRegion.op(r, Region.Op.DIFFERENCE);
312         RegionIterator iter = new RegionIterator(drawRegion);
313         while (iter.next(r)) {
314             canvas.drawRect(r, outerAreaPaint);
315         }
316 
317         canvas.drawRect(cropBounds, borderPaint);
318     }
319 
320     @Override
onDraw(Canvas canvas)321     protected void onDraw(Canvas canvas) {
322         super.onDraw(canvas);
323 
324         drawBounds(canvas);
325         if (currentTouchArea != TOUCH_AREA_NONE) {
326             drawTouchHighlights(canvas);
327             drawIndicators(canvas);
328         }
329     }
330 }
331