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.gallery3d.filtershow.crop;
18
19import android.graphics.Rect;
20import android.graphics.RectF;
21
22import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
23
24public 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
49    public CropObject(Rect outerBound, Rect innerBound, int outerAngle) {
50        mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
51    }
52
53    public CropObject(RectF outerBound, RectF innerBound, int outerAngle) {
54        mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
55    }
56
57    public void resetBoundsTo(RectF inner, RectF outer) {
58        mBoundedRect.resetTo(0, outer, inner);
59    }
60
61    public void getInnerBounds(RectF r) {
62        mBoundedRect.setToInner(r);
63    }
64
65    public void getOuterBounds(RectF r) {
66        mBoundedRect.setToOuter(r);
67    }
68
69    public RectF getInnerBounds() {
70        return mBoundedRect.getInner();
71    }
72
73    public RectF getOuterBounds() {
74        return mBoundedRect.getOuter();
75    }
76
77    public int getSelectState() {
78        return mMovingEdges;
79    }
80
81    public boolean isFixedAspect() {
82        return mFixAspectRatio;
83    }
84
85    public void rotateOuter(int angle) {
86        mRotation = angle % 360;
87        mBoundedRect.setRotation(mRotation);
88        clearSelectState();
89    }
90
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
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
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
122    public void unsetAspectRatio() {
123        mFixAspectRatio = false;
124        clearSelectState();
125    }
126
127    public boolean hasSelectedEdge() {
128        return mMovingEdges != MOVE_NONE;
129    }
130
131    public static boolean checkCorner(int selected) {
132        return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT
133                || selected == BOTTOM_LEFT;
134    }
135
136    public static boolean checkEdge(int selected) {
137        return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT
138                || selected == MOVE_BOTTOM;
139    }
140
141    public static boolean checkBlock(int selected) {
142        return selected == MOVE_BLOCK;
143    }
144
145    public static boolean checkValid(int selected) {
146        return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected)
147                || checkCorner(selected);
148    }
149
150    public void clearSelectState() {
151        mMovingEdges = MOVE_NONE;
152    }
153
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
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
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
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
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
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
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