• 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.gallery3d.photoeditor.actions;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.RectF;
24 import android.graphics.drawable.Drawable;
25 import android.util.AttributeSet;
26 import android.view.MotionEvent;
27 
28 import com.android.gallery3d.R;
29 
30 /**
31  * A view that tracks touch motions and adjusts crop bounds accordingly.
32  */
33 class CropView extends FullscreenToolView {
34 
35     /**
36      * Listener of crop bounds.
37      */
38     public interface OnCropChangeListener {
39 
onCropChanged(RectF cropBounds, boolean fromUser)40         void onCropChanged(RectF cropBounds, boolean fromUser);
41     }
42 
43     private static final int MOVE_LEFT = 1;
44     private static final int MOVE_TOP = 2;
45     private static final int MOVE_RIGHT = 4;
46     private static final int MOVE_BOTTOM = 8;
47     private static final int MOVE_BLOCK = 16;
48 
49     private static final int MIN_CROP_WIDTH_HEIGHT = 2;
50     private static final int TOUCH_TOLERANCE = 25;
51     private static final int SHADOW_ALPHA = 160;
52 
53     private final Paint borderPaint;
54     private final Drawable cropIndicator;
55     private final int indicatorSize;
56     private final RectF cropBounds = new RectF(0, 0, 1, 1);
57 
58     private float lastX;
59     private float lastY;
60     private int movingEdges;
61     private OnCropChangeListener listener;
62 
CropView(Context context, AttributeSet attrs)63     public CropView(Context context, AttributeSet attrs) {
64         super(context, attrs);
65 
66         Resources resources = context.getResources();
67         cropIndicator = resources.getDrawable(R.drawable.camera_crop_holo);
68         indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size);
69         int borderColor = resources.getColor(R.color.opaque_cyan);
70 
71         borderPaint = new Paint();
72         borderPaint.setStyle(Paint.Style.STROKE);
73         borderPaint.setColor(borderColor);
74         borderPaint.setStrokeWidth(2f);
75     }
76 
setOnCropChangeListener(OnCropChangeListener listener)77     public void setOnCropChangeListener(OnCropChangeListener listener) {
78         this.listener = listener;
79     }
80 
refreshByCropChange(boolean fromUser)81     private void refreshByCropChange(boolean fromUser) {
82         if (listener != null) {
83             listener.onCropChanged(new RectF(cropBounds), fromUser);
84         }
85         invalidate();
86     }
87 
88     /**
89      * Sets cropped bounds; modifies the bounds if it's smaller than the allowed dimensions.
90      */
setCropBounds(RectF bounds)91     public void setCropBounds(RectF bounds) {
92         // Avoid cropping smaller than minimum width or height.
93         if (bounds.width() * getPhotoWidth() < MIN_CROP_WIDTH_HEIGHT) {
94             bounds.set(0, bounds.top, 1, bounds.bottom);
95         }
96         if (bounds.height() * getPhotoHeight() < MIN_CROP_WIDTH_HEIGHT) {
97             bounds.set(bounds.left, 0, bounds.right, 1);
98         }
99         cropBounds.set(bounds);
100         refreshByCropChange(false);
101     }
102 
getCropBoundsDisplayed()103     private RectF getCropBoundsDisplayed() {
104         float width = displayBounds.width();
105         float height = displayBounds.height();
106         RectF cropped = new RectF(cropBounds.left * width, cropBounds.top * height,
107                 cropBounds.right * width, cropBounds.bottom * height);
108         cropped.offset(displayBounds.left, displayBounds.top);
109         return cropped;
110     }
111 
detectMovingEdges(float x, float y)112     private void detectMovingEdges(float x, float y) {
113         RectF cropped = getCropBoundsDisplayed();
114         movingEdges = 0;
115 
116         // Check left or right.
117         float left = Math.abs(x - cropped.left);
118         float right = Math.abs(x - cropped.right);
119         if ((left <= TOUCH_TOLERANCE) && (left < right)) {
120             movingEdges |= MOVE_LEFT;
121         }
122         else if (right <= TOUCH_TOLERANCE) {
123             movingEdges |= MOVE_RIGHT;
124         }
125 
126         // Check top or bottom.
127         float top = Math.abs(y - cropped.top);
128         float bottom = Math.abs(y - cropped.bottom);
129         if ((top <= TOUCH_TOLERANCE) & (top < bottom)) {
130             movingEdges |= MOVE_TOP;
131         }
132         else if (bottom <= TOUCH_TOLERANCE) {
133             movingEdges |= MOVE_BOTTOM;
134         }
135 
136         // Check inside block.
137         if (cropped.contains(x, y) && (movingEdges == 0)) {
138             movingEdges = MOVE_BLOCK;
139         }
140         invalidate();
141     }
142 
moveEdges(float deltaX, float deltaY)143     private void moveEdges(float deltaX, float deltaY) {
144         RectF cropped = getCropBoundsDisplayed();
145         if (movingEdges == MOVE_BLOCK) {
146             // Move the whole cropped bounds within the photo display bounds.
147             deltaX = (deltaX > 0) ? Math.min(displayBounds.right - cropped.right, deltaX)
148                     : Math.max(displayBounds.left - cropped.left, deltaX);
149             deltaY = (deltaY > 0) ? Math.min(displayBounds.bottom - cropped.bottom, deltaY)
150                     : Math.max(displayBounds.top - cropped.top, deltaY);
151             cropped.offset(deltaX, deltaY);
152         } else {
153             // Adjust cropped bound dimensions within the photo display bounds.
154             float minWidth = MIN_CROP_WIDTH_HEIGHT * displayBounds.width() / getPhotoWidth();
155             float minHeight = MIN_CROP_WIDTH_HEIGHT * displayBounds.height() / getPhotoHeight();
156             if ((movingEdges & MOVE_LEFT) != 0) {
157                 cropped.left = Math.min(cropped.left + deltaX, cropped.right - minWidth);
158             }
159             if ((movingEdges & MOVE_TOP) != 0) {
160                 cropped.top = Math.min(cropped.top + deltaY, cropped.bottom - minHeight);
161             }
162             if ((movingEdges & MOVE_RIGHT) != 0) {
163                 cropped.right = Math.max(cropped.right + deltaX, cropped.left + minWidth);
164             }
165             if ((movingEdges & MOVE_BOTTOM) != 0) {
166                 cropped.bottom = Math.max(cropped.bottom + deltaY, cropped.top + minHeight);
167             }
168             cropped.intersect(displayBounds);
169         }
170         mapPhotoRect(cropped, cropBounds);
171         refreshByCropChange(true);
172     }
173 
174     @Override
onTouchEvent(MotionEvent event)175     public boolean onTouchEvent(MotionEvent event) {
176         super.onTouchEvent(event);
177 
178         if (isEnabled()) {
179             float x = event.getX();
180             float y = event.getY();
181 
182             switch (event.getAction()) {
183                 case MotionEvent.ACTION_DOWN:
184                     detectMovingEdges(x, y);
185                     lastX = x;
186                     lastY = y;
187                     break;
188 
189                 case MotionEvent.ACTION_MOVE:
190                     if (movingEdges != 0) {
191                         moveEdges(x - lastX, y - lastY);
192                     }
193                     lastX = x;
194                     lastY = y;
195                     break;
196 
197                 case MotionEvent.ACTION_CANCEL:
198                 case MotionEvent.ACTION_UP:
199                     movingEdges = 0;
200                     invalidate();
201                     break;
202             }
203         }
204         return true;
205     }
206 
drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY)207     private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) {
208         int left = (int) centerX - indicatorSize / 2;
209         int top = (int) centerY - indicatorSize / 2;
210         indicator.setBounds(left, top, left + indicatorSize, top + indicatorSize);
211         indicator.draw(canvas);
212     }
213 
drawShadow(Canvas canvas, float left, float top, float right, float bottom)214     private void drawShadow(Canvas canvas, float left, float top, float right, float bottom) {
215         canvas.save();
216         canvas.clipRect(left, top, right, bottom);
217         canvas.drawARGB(SHADOW_ALPHA, 0, 0, 0);
218         canvas.restore();
219     }
220 
221     @Override
onDraw(Canvas canvas)222     protected void onDraw(Canvas canvas) {
223         super.onDraw(canvas);
224 
225         // Draw shadow on non-cropped bounds and the border around cropped bounds.
226         RectF cropped = getCropBoundsDisplayed();
227         drawShadow(canvas, displayBounds.left, displayBounds.top, displayBounds.right, cropped.top);
228         drawShadow(canvas, displayBounds.left, cropped.top, cropped.left, displayBounds.bottom);
229         drawShadow(canvas, cropped.right, cropped.top, displayBounds.right, displayBounds.bottom);
230         drawShadow(canvas, cropped.left, cropped.bottom, cropped.right, displayBounds.bottom);
231         canvas.drawRect(cropped, borderPaint);
232 
233         boolean notMoving = movingEdges == 0;
234         if (((movingEdges & MOVE_TOP) != 0) || notMoving) {
235             drawIndicator(canvas, cropIndicator, cropped.centerX(), cropped.top);
236         }
237         if (((movingEdges & MOVE_BOTTOM) != 0) || notMoving) {
238             drawIndicator(canvas, cropIndicator, cropped.centerX(), cropped.bottom);
239         }
240         if (((movingEdges & MOVE_LEFT) != 0) || notMoving) {
241             drawIndicator(canvas, cropIndicator, cropped.left, cropped.centerY());
242         }
243         if (((movingEdges & MOVE_RIGHT) != 0) || notMoving) {
244             drawIndicator(canvas, cropIndicator, cropped.right, cropped.centerY());
245         }
246     }
247 }
248