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