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