1 /* 2 * Copyright (C) 2012 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 package com.android.gallery3d.filtershow.crop; 17 18 import android.graphics.Matrix; 19 import android.graphics.Rect; 20 import android.graphics.RectF; 21 22 import com.android.gallery3d.filtershow.imageshow.GeometryMath; 23 24 import java.util.Arrays; 25 26 /** 27 * Maintains invariant that inner rectangle is constrained to be within the 28 * outer, rotated rectangle. 29 */ 30 public class BoundedRect { 31 private float rot; 32 private RectF outer; 33 private RectF inner; 34 private float[] innerRotated; 35 36 public BoundedRect(float rotation, Rect outerRect, Rect innerRect) { 37 rot = rotation; 38 outer = new RectF(outerRect); 39 inner = new RectF(innerRect); 40 innerRotated = CropMath.getCornersFromRect(inner); 41 rotateInner(); 42 if (!isConstrained()) 43 reconstrain(); 44 } 45 46 public BoundedRect(float rotation, RectF outerRect, RectF innerRect) { 47 rot = rotation; 48 outer = new RectF(outerRect); 49 inner = new RectF(innerRect); 50 innerRotated = CropMath.getCornersFromRect(inner); 51 rotateInner(); 52 if (!isConstrained()) 53 reconstrain(); 54 } 55 56 public void resetTo(float rotation, RectF outerRect, RectF innerRect) { 57 rot = rotation; 58 outer.set(outerRect); 59 inner.set(innerRect); 60 innerRotated = CropMath.getCornersFromRect(inner); 61 rotateInner(); 62 if (!isConstrained()) 63 reconstrain(); 64 } 65 66 /** 67 * Sets inner, and re-constrains it to fit within the rotated bounding rect. 68 */ 69 public void setInner(RectF newInner) { 70 if (inner.equals(newInner)) 71 return; 72 inner = newInner; 73 innerRotated = CropMath.getCornersFromRect(inner); 74 rotateInner(); 75 if (!isConstrained()) 76 reconstrain(); 77 } 78 79 /** 80 * Sets rotation, and re-constrains inner to fit within the rotated bounding rect. 81 */ 82 public void setRotation(float rotation) { 83 if (rotation == rot) 84 return; 85 rot = rotation; 86 innerRotated = CropMath.getCornersFromRect(inner); 87 rotateInner(); 88 if (!isConstrained()) 89 reconstrain(); 90 } 91 92 public void setToInner(RectF r) { 93 r.set(inner); 94 } 95 96 public void setToOuter(RectF r) { 97 r.set(outer); 98 } 99 100 public RectF getInner() { 101 return new RectF(inner); 102 } 103 104 public RectF getOuter() { 105 return new RectF(outer); 106 } 107 108 /** 109 * Tries to move the inner rectangle by (dx, dy). If this would cause it to leave 110 * the bounding rectangle, snaps the inner rectangle to the edge of the bounding 111 * rectangle. 112 */ 113 public void moveInner(float dx, float dy) { 114 Matrix m0 = getInverseRotMatrix(); 115 116 RectF translatedInner = new RectF(inner); 117 translatedInner.offset(dx, dy); 118 119 float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner); 120 float[] outerCorners = CropMath.getCornersFromRect(outer); 121 122 m0.mapPoints(translatedInnerCorners); 123 float[] correction = { 124 0, 0 125 }; 126 127 // find correction vectors for corners that have moved out of bounds 128 for (int i = 0; i < translatedInnerCorners.length; i += 2) { 129 float correctedInnerX = translatedInnerCorners[i] + correction[0]; 130 float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; 131 if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) { 132 float[] badCorner = { 133 correctedInnerX, correctedInnerY 134 }; 135 float[] nearestSide = CropMath.closestSide(badCorner, outerCorners); 136 float[] correctionVec = 137 GeometryMath.shortestVectorFromPointToLine(badCorner, nearestSide); 138 correction[0] += correctionVec[0]; 139 correction[1] += correctionVec[1]; 140 } 141 } 142 143 for (int i = 0; i < translatedInnerCorners.length; i += 2) { 144 float correctedInnerX = translatedInnerCorners[i] + correction[0]; 145 float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; 146 if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) { 147 float[] correctionVec = { 148 correctedInnerX, correctedInnerY 149 }; 150 CropMath.getEdgePoints(outer, correctionVec); 151 correctionVec[0] -= correctedInnerX; 152 correctionVec[1] -= correctedInnerY; 153 correction[0] += correctionVec[0]; 154 correction[1] += correctionVec[1]; 155 } 156 } 157 158 // Set correction 159 for (int i = 0; i < translatedInnerCorners.length; i += 2) { 160 float correctedInnerX = translatedInnerCorners[i] + correction[0]; 161 float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; 162 // update translated corners with correction vectors 163 translatedInnerCorners[i] = correctedInnerX; 164 translatedInnerCorners[i + 1] = correctedInnerY; 165 } 166 167 innerRotated = translatedInnerCorners; 168 // reconstrain to update inner 169 reconstrain(); 170 } 171 172 /** 173 * Attempts to resize the inner rectangle. If this would cause it to leave 174 * the bounding rect, clips the inner rectangle to fit. 175 */ 176 public void resizeInner(RectF newInner) { 177 Matrix m = getRotMatrix(); 178 Matrix m0 = getInverseRotMatrix(); 179 180 float[] outerCorners = CropMath.getCornersFromRect(outer); 181 m.mapPoints(outerCorners); 182 float[] oldInnerCorners = CropMath.getCornersFromRect(inner); 183 float[] newInnerCorners = CropMath.getCornersFromRect(newInner); 184 RectF ret = new RectF(newInner); 185 186 for (int i = 0; i < newInnerCorners.length; i += 2) { 187 float[] c = { 188 newInnerCorners[i], newInnerCorners[i + 1] 189 }; 190 float[] c0 = Arrays.copyOf(c, 2); 191 m0.mapPoints(c0); 192 if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) { 193 float[] outerSide = CropMath.closestSide(c, outerCorners); 194 float[] pathOfCorner = { 195 newInnerCorners[i], newInnerCorners[i + 1], 196 oldInnerCorners[i], oldInnerCorners[i + 1] 197 }; 198 float[] p = GeometryMath.lineIntersect(pathOfCorner, outerSide); 199 if (p == null) { 200 // lines are parallel or not well defined, so don't resize 201 p = new float[2]; 202 p[0] = oldInnerCorners[i]; 203 p[1] = oldInnerCorners[i + 1]; 204 } 205 // relies on corners being in same order as method 206 // getCornersFromRect 207 switch (i) { 208 case 0: 209 case 1: 210 ret.left = (p[0] > ret.left) ? p[0] : ret.left; 211 ret.top = (p[1] > ret.top) ? p[1] : ret.top; 212 break; 213 case 2: 214 case 3: 215 ret.right = (p[0] < ret.right) ? p[0] : ret.right; 216 ret.top = (p[1] > ret.top) ? p[1] : ret.top; 217 break; 218 case 4: 219 case 5: 220 ret.right = (p[0] < ret.right) ? p[0] : ret.right; 221 ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom; 222 break; 223 case 6: 224 case 7: 225 ret.left = (p[0] > ret.left) ? p[0] : ret.left; 226 ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom; 227 break; 228 default: 229 break; 230 } 231 } 232 } 233 float[] retCorners = CropMath.getCornersFromRect(ret); 234 m0.mapPoints(retCorners); 235 innerRotated = retCorners; 236 // reconstrain to update inner 237 reconstrain(); 238 } 239 240 /** 241 * Attempts to resize the inner rectangle. If this would cause it to leave 242 * the bounding rect, clips the inner rectangle to fit while maintaining 243 * aspect ratio. 244 */ 245 public void fixedAspectResizeInner(RectF newInner) { 246 Matrix m = getRotMatrix(); 247 Matrix m0 = getInverseRotMatrix(); 248 249 float aspectW = inner.width(); 250 float aspectH = inner.height(); 251 float aspRatio = aspectW / aspectH; 252 float[] corners = CropMath.getCornersFromRect(outer); 253 254 m.mapPoints(corners); 255 float[] oldInnerCorners = CropMath.getCornersFromRect(inner); 256 float[] newInnerCorners = CropMath.getCornersFromRect(newInner); 257 258 // find fixed corner 259 int fixed = -1; 260 if (inner.top == newInner.top) { 261 if (inner.left == newInner.left) 262 fixed = 0; // top left 263 else if (inner.right == newInner.right) 264 fixed = 2; // top right 265 } else if (inner.bottom == newInner.bottom) { 266 if (inner.right == newInner.right) 267 fixed = 4; // bottom right 268 else if (inner.left == newInner.left) 269 fixed = 6; // bottom left 270 } 271 // no fixed corner, return without update 272 if (fixed == -1) 273 return; 274 float widthSoFar = newInner.width(); 275 int moved = -1; 276 for (int i = 0; i < newInnerCorners.length; i += 2) { 277 float[] c = { 278 newInnerCorners[i], newInnerCorners[i + 1] 279 }; 280 float[] c0 = Arrays.copyOf(c, 2); 281 m0.mapPoints(c0); 282 if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) { 283 moved = i; 284 if (moved == fixed) 285 continue; 286 float[] l2 = CropMath.closestSide(c, corners); 287 float[] l1 = { 288 newInnerCorners[i], newInnerCorners[i + 1], 289 oldInnerCorners[i], oldInnerCorners[i + 1] 290 }; 291 float[] p = GeometryMath.lineIntersect(l1, l2); 292 if (p == null) { 293 // lines are parallel or not well defined, so set to old 294 // corner 295 p = new float[2]; 296 p[0] = oldInnerCorners[i]; 297 p[1] = oldInnerCorners[i + 1]; 298 } 299 // relies on corners being in same order as method 300 // getCornersFromRect 301 float fixed_x = oldInnerCorners[fixed]; 302 float fixed_y = oldInnerCorners[fixed + 1]; 303 float newWidth = Math.abs(fixed_x - p[0]); 304 float newHeight = Math.abs(fixed_y - p[1]); 305 newWidth = Math.max(newWidth, aspRatio * newHeight); 306 if (newWidth < widthSoFar) 307 widthSoFar = newWidth; 308 } 309 } 310 311 float heightSoFar = widthSoFar / aspRatio; 312 RectF ret = new RectF(inner); 313 if (fixed == 0) { 314 ret.right = ret.left + widthSoFar; 315 ret.bottom = ret.top + heightSoFar; 316 } else if (fixed == 2) { 317 ret.left = ret.right - widthSoFar; 318 ret.bottom = ret.top + heightSoFar; 319 } else if (fixed == 4) { 320 ret.left = ret.right - widthSoFar; 321 ret.top = ret.bottom - heightSoFar; 322 } else if (fixed == 6) { 323 ret.right = ret.left + widthSoFar; 324 ret.top = ret.bottom - heightSoFar; 325 } 326 float[] retCorners = CropMath.getCornersFromRect(ret); 327 m0.mapPoints(retCorners); 328 innerRotated = retCorners; 329 // reconstrain to update inner 330 reconstrain(); 331 } 332 333 // internal methods 334 335 private boolean isConstrained() { 336 for (int i = 0; i < 8; i += 2) { 337 if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1])) 338 return false; 339 } 340 return true; 341 } 342 343 private void reconstrain() { 344 // innerRotated has been changed to have incorrect values 345 CropMath.getEdgePoints(outer, innerRotated); 346 Matrix m = getRotMatrix(); 347 float[] unrotated = Arrays.copyOf(innerRotated, 8); 348 m.mapPoints(unrotated); 349 inner = CropMath.trapToRect(unrotated); 350 } 351 352 private void rotateInner() { 353 Matrix m = getInverseRotMatrix(); 354 m.mapPoints(innerRotated); 355 } 356 357 private Matrix getRotMatrix() { 358 Matrix m = new Matrix(); 359 m.setRotate(rot, outer.centerX(), outer.centerY()); 360 return m; 361 } 362 363 private Matrix getInverseRotMatrix() { 364 Matrix m = new Matrix(); 365 m.setRotate(-rot, outer.centerX(), outer.centerY()); 366 return m; 367 } 368 } 369