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