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.gallery3d.filtershow.crop;
18 
19 import android.graphics.Rect;
20 import android.graphics.RectF;
21 
22 import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
23 
24 public class CropObject {
25     private BoundedRect mBoundedRect;
26     private float mAspectWidth = 1;
27     private float mAspectHeight = 1;
28     private boolean mFixAspectRatio = false;
29     private float mRotation = 0;
30     private float mTouchTolerance = 45;
31     private float mMinSideSize = 20;
32 
33     public static final int MOVE_NONE = 0;
34     // Sides
35     public static final int MOVE_LEFT = 1;
36     public static final int MOVE_TOP = 2;
37     public static final int MOVE_RIGHT = 4;
38     public static final int MOVE_BOTTOM = 8;
39     public static final int MOVE_BLOCK = 16;
40 
41     // Corners
42     public static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT;
43     public static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT;
44     public static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT;
45     public static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT;
46 
47     private int mMovingEdges = MOVE_NONE;
48 
CropObject(Rect outerBound, Rect innerBound, int outerAngle)49     public CropObject(Rect outerBound, Rect innerBound, int outerAngle) {
50         mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
51     }
52 
CropObject(RectF outerBound, RectF innerBound, int outerAngle)53     public CropObject(RectF outerBound, RectF innerBound, int outerAngle) {
54         mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
55     }
56 
resetBoundsTo(RectF inner, RectF outer)57     public void resetBoundsTo(RectF inner, RectF outer) {
58         mBoundedRect.resetTo(0, outer, inner);
59     }
60 
getInnerBounds(RectF r)61     public void getInnerBounds(RectF r) {
62         mBoundedRect.setToInner(r);
63     }
64 
getOuterBounds(RectF r)65     public void getOuterBounds(RectF r) {
66         mBoundedRect.setToOuter(r);
67     }
68 
getInnerBounds()69     public RectF getInnerBounds() {
70         return mBoundedRect.getInner();
71     }
72 
getOuterBounds()73     public RectF getOuterBounds() {
74         return mBoundedRect.getOuter();
75     }
76 
getSelectState()77     public int getSelectState() {
78         return mMovingEdges;
79     }
80 
isFixedAspect()81     public boolean isFixedAspect() {
82         return mFixAspectRatio;
83     }
84 
rotateOuter(int angle)85     public void rotateOuter(int angle) {
86         mRotation = angle % 360;
87         mBoundedRect.setRotation(mRotation);
88         clearSelectState();
89     }
90 
setInnerAspectRatio(float width, float height)91     public boolean setInnerAspectRatio(float width, float height) {
92         if (width <= 0 || height <= 0) {
93             throw new IllegalArgumentException("Width and Height must be greater than zero");
94         }
95         RectF inner = mBoundedRect.getInner();
96         CropMath.fixAspectRatioContained(inner, width, height);
97         if (inner.width() < mMinSideSize || inner.height() < mMinSideSize) {
98             return false;
99         }
100         mAspectWidth = width;
101         mAspectHeight = height;
102         mFixAspectRatio = true;
103         mBoundedRect.setInner(inner);
104         clearSelectState();
105         return true;
106     }
107 
setTouchTolerance(float tolerance)108     public void setTouchTolerance(float tolerance) {
109         if (tolerance <= 0) {
110             throw new IllegalArgumentException("Tolerance must be greater than zero");
111         }
112         mTouchTolerance = tolerance;
113     }
114 
setMinInnerSideSize(float minSide)115     public void setMinInnerSideSize(float minSide) {
116         if (minSide <= 0) {
117             throw new IllegalArgumentException("Min dide must be greater than zero");
118         }
119         mMinSideSize = minSide;
120     }
121 
unsetAspectRatio()122     public void unsetAspectRatio() {
123         mFixAspectRatio = false;
124         clearSelectState();
125     }
126 
hasSelectedEdge()127     public boolean hasSelectedEdge() {
128         return mMovingEdges != MOVE_NONE;
129     }
130 
checkCorner(int selected)131     public static boolean checkCorner(int selected) {
132         return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT
133                 || selected == BOTTOM_LEFT;
134     }
135 
checkEdge(int selected)136     public static boolean checkEdge(int selected) {
137         return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT
138                 || selected == MOVE_BOTTOM;
139     }
140 
checkBlock(int selected)141     public static boolean checkBlock(int selected) {
142         return selected == MOVE_BLOCK;
143     }
144 
checkValid(int selected)145     public static boolean checkValid(int selected) {
146         return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected)
147                 || checkCorner(selected);
148     }
149 
clearSelectState()150     public void clearSelectState() {
151         mMovingEdges = MOVE_NONE;
152     }
153 
wouldSelectEdge(float x, float y)154     public int wouldSelectEdge(float x, float y) {
155         int edgeSelected = calculateSelectedEdge(x, y);
156         if (edgeSelected != MOVE_NONE && edgeSelected != MOVE_BLOCK) {
157             return edgeSelected;
158         }
159         return MOVE_NONE;
160     }
161 
selectEdge(int edge)162     public boolean selectEdge(int edge) {
163         if (!checkValid(edge)) {
164             // temporary
165             throw new IllegalArgumentException("bad edge selected");
166             // return false;
167         }
168         if ((mFixAspectRatio && !checkCorner(edge)) && !checkBlock(edge) && edge != MOVE_NONE) {
169             // temporary
170             throw new IllegalArgumentException("bad corner selected");
171             // return false;
172         }
173         mMovingEdges = edge;
174         return true;
175     }
176 
selectEdge(float x, float y)177     public boolean selectEdge(float x, float y) {
178         int edgeSelected = calculateSelectedEdge(x, y);
179         if (mFixAspectRatio) {
180             edgeSelected = fixEdgeToCorner(edgeSelected);
181         }
182         if (edgeSelected == MOVE_NONE) {
183             return false;
184         }
185         return selectEdge(edgeSelected);
186     }
187 
moveCurrentSelection(float dX, float dY)188     public boolean moveCurrentSelection(float dX, float dY) {
189         if (mMovingEdges == MOVE_NONE) {
190             return false;
191         }
192         RectF crop = mBoundedRect.getInner();
193 
194         float minWidthHeight = mMinSideSize;
195 
196         int movingEdges = mMovingEdges;
197         if (movingEdges == MOVE_BLOCK) {
198             mBoundedRect.moveInner(dX, dY);
199             return true;
200         } else {
201             float dx = 0;
202             float dy = 0;
203 
204             if ((movingEdges & MOVE_LEFT) != 0) {
205                 dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left;
206             }
207             if ((movingEdges & MOVE_TOP) != 0) {
208                 dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top;
209             }
210             if ((movingEdges & MOVE_RIGHT) != 0) {
211                 dx = Math.max(crop.right + dX, crop.left + minWidthHeight)
212                         - crop.right;
213             }
214             if ((movingEdges & MOVE_BOTTOM) != 0) {
215                 dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight)
216                         - crop.bottom;
217             }
218 
219             if (mFixAspectRatio) {
220                 float[] l1 = {
221                         crop.left, crop.bottom
222                 };
223                 float[] l2 = {
224                         crop.right, crop.top
225                 };
226                 if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) {
227                     l1[1] = crop.top;
228                     l2[1] = crop.bottom;
229                 }
230                 float[] b = {
231                         l1[0] - l2[0], l1[1] - l2[1]
232                 };
233                 float[] disp = {
234                         dx, dy
235                 };
236                 float[] bUnit = GeometryMathUtils.normalize(b);
237                 float sp = GeometryMathUtils.scalarProjection(disp, bUnit);
238                 dx = sp * bUnit[0];
239                 dy = sp * bUnit[1];
240                 RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy);
241 
242                 mBoundedRect.fixedAspectResizeInner(newCrop);
243             } else {
244                 if ((movingEdges & MOVE_LEFT) != 0) {
245                     crop.left += dx;
246                 }
247                 if ((movingEdges & MOVE_TOP) != 0) {
248                     crop.top += dy;
249                 }
250                 if ((movingEdges & MOVE_RIGHT) != 0) {
251                     crop.right += dx;
252                 }
253                 if ((movingEdges & MOVE_BOTTOM) != 0) {
254                     crop.bottom += dy;
255                 }
256                 mBoundedRect.resizeInner(crop);
257             }
258         }
259         return true;
260     }
261 
262     // Helper methods
263 
calculateSelectedEdge(float x, float y)264     private int calculateSelectedEdge(float x, float y) {
265         RectF cropped = mBoundedRect.getInner();
266 
267         float left = Math.abs(x - cropped.left);
268         float right = Math.abs(x - cropped.right);
269         float top = Math.abs(y - cropped.top);
270         float bottom = Math.abs(y - cropped.bottom);
271 
272         int edgeSelected = MOVE_NONE;
273         // Check left or right.
274         if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
275                 && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) {
276             edgeSelected |= MOVE_LEFT;
277         }
278         else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
279                 && ((y - mTouchTolerance) <= cropped.bottom)) {
280             edgeSelected |= MOVE_RIGHT;
281         }
282 
283         // Check top or bottom.
284         if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
285                 && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) {
286             edgeSelected |= MOVE_TOP;
287         }
288         else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
289                 && ((x - mTouchTolerance) <= cropped.right)) {
290             edgeSelected |= MOVE_BOTTOM;
291         }
292         return edgeSelected;
293     }
294 
fixedCornerResize(RectF r, int moving_corner, float dx, float dy)295     private static RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) {
296         RectF newCrop = null;
297         // Fix opposite corner in place and move sides
298         if (moving_corner == BOTTOM_RIGHT) {
299             newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height()
300                     + dy);
301         } else if (moving_corner == BOTTOM_LEFT) {
302             newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height()
303                     + dy);
304         } else if (moving_corner == TOP_LEFT) {
305             newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy,
306                     r.right, r.bottom);
307         } else if (moving_corner == TOP_RIGHT) {
308             newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left
309                     + r.width() + dx, r.bottom);
310         }
311         return newCrop;
312     }
313 
fixEdgeToCorner(int moving_edges)314     private static int fixEdgeToCorner(int moving_edges) {
315         if (moving_edges == MOVE_LEFT) {
316             moving_edges |= MOVE_TOP;
317         }
318         if (moving_edges == MOVE_TOP) {
319             moving_edges |= MOVE_LEFT;
320         }
321         if (moving_edges == MOVE_RIGHT) {
322             moving_edges |= MOVE_BOTTOM;
323         }
324         if (moving_edges == MOVE_BOTTOM) {
325             moving_edges |= MOVE_RIGHT;
326         }
327         return moving_edges;
328     }
329 
330 }
331