CropView.java revision 0f8a40e4cfdc5f6cd47c22e81f69ed0446067c54
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
17package com.android.photoeditor.actions;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.graphics.Canvas;
22import android.graphics.Color;
23import android.graphics.Paint;
24import android.graphics.Rect;
25import android.graphics.RectF;
26import android.graphics.Region;
27import android.graphics.RegionIterator;
28import android.graphics.drawable.Drawable;
29import android.util.AttributeSet;
30import android.view.MotionEvent;
31import android.view.View;
32
33import com.android.photoeditor.R;
34
35import java.util.Vector;
36
37/**
38 * A view that track touch motions and adjust crop bounds accordingly.
39 */
40class CropView extends View {
41
42    /**
43     * Listener of crop bounds.
44     */
45    public interface OnCropChangeListener {
46
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
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
111    public void setOnCropChangeListener(OnCropChangeListener listener) {
112        this.listener = listener;
113    }
114
115    private void notifyCropChange(boolean fromUser) {
116        if (listener != null) {
117            listener.onCropChanged(cropBounds, fromUser);
118        }
119    }
120
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     */
139    public void setPhotoBounds(RectF bounds) {
140        photoBounds = bounds;
141    }
142
143    public boolean fullPhotoCropped() {
144        return cropBounds.contains(photoBounds);
145    }
146
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
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
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
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
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
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
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
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