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 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