2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.gallery3d.filtershow.crop;
18 import android.graphics.Matrix;
19 import android.graphics.Rect;
20 import android.graphics.RectF;
22 import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
24 import java.util.Arrays;
27 * Maintains invariant that inner rectangle is constrained to be within the
28 * outer, rotated rectangle.
30 public class BoundedRect {
34 private float[] innerRotated;
36 public BoundedRect(float rotation, Rect outerRect, Rect innerRect) {
38 outer = new RectF(outerRect);
39 inner = new RectF(innerRect);
40 innerRotated = CropMath.getCornersFromRect(inner);
46 public BoundedRect(float rotation, RectF outerRect, RectF innerRect) {
48 outer = new RectF(outerRect);
49 inner = new RectF(innerRect);
50 innerRotated = CropMath.getCornersFromRect(inner);
56 public void resetTo(float rotation, RectF outerRect, RectF innerRect) {
60 innerRotated = CropMath.getCornersFromRect(inner);
67 * Sets inner, and re-constrains it to fit within the rotated bounding rect.
69 public void setInner(RectF newInner) {
70 if (inner.equals(newInner))
73 innerRotated = CropMath.getCornersFromRect(inner);
80 * Sets rotation, and re-constrains inner to fit within the rotated bounding rect.
82 public void setRotation(float rotation) {
86 innerRotated = CropMath.getCornersFromRect(inner);
92 public void setToInner(RectF r) {
96 public void setToOuter(RectF r) {
100 public RectF getInner() {
101 return new RectF(inner);
104 public RectF getOuter() {
105 return new RectF(outer);
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
113 public void moveInner(float dx, float dy) {
114 Matrix m0 = getInverseRotMatrix();
116 RectF translatedInner = new RectF(inner);
117 translatedInner.offset(dx, dy);
119 float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner);
120 float[] outerCorners = CropMath.getCornersFromRect(outer);
122 m0.mapPoints(translatedInnerCorners);
123 float[] correction = {
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
135 float[] nearestSide = CropMath.closestSide(badCorner, outerCorners);
136 float[] correctionVec =
137 GeometryMathUtils.shortestVectorFromPointToLine(badCorner, nearestSide);
138 correction[0] += correctionVec[0];
139 correction[1] += correctionVec[1];
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
150 CropMath.getEdgePoints(outer, correctionVec);
151 correctionVec[0] -= correctedInnerX;
152 correctionVec[1] -= correctedInnerY;
153 correction[0] += correctionVec[0];
154 correction[1] += correctionVec[1];
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;
167 innerRotated = translatedInnerCorners;
168 // reconstrain to update inner
173 * Attempts to resize the inner rectangle. If this would cause it to leave
174 * the bounding rect, clips the inner rectangle to fit.
176 public void resizeInner(RectF newInner) {
177 Matrix m = getRotMatrix();
178 Matrix m0 = getInverseRotMatrix();
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);
186 for (int i = 0; i < newInnerCorners.length; i += 2) {
188 newInnerCorners[i], newInnerCorners[i + 1]
190 float[] c0 = Arrays.copyOf(c, 2);
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]
198 float[] p = GeometryMathUtils.lineIntersect(pathOfCorner, outerSide);
200 // lines are parallel or not well defined, so don't resize
202 p[0] = oldInnerCorners[i];
203 p[1] = oldInnerCorners[i + 1];
205 // relies on corners being in same order as method
206 // getCornersFromRect
210 ret.left = (p[0] > ret.left) ? p[0] : ret.left;
211 ret.top = (p[1] > ret.top) ? p[1] : ret.top;
215 ret.right = (p[0] < ret.right) ? p[0] : ret.right;
216 ret.top = (p[1] > ret.top) ? p[1] : ret.top;
220 ret.right = (p[0] < ret.right) ? p[0] : ret.right;
221 ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
225 ret.left = (p[0] > ret.left) ? p[0] : ret.left;
226 ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
233 float[] retCorners = CropMath.getCornersFromRect(ret);
234 m0.mapPoints(retCorners);
235 innerRotated = retCorners;
236 // reconstrain to update inner
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
245 public void fixedAspectResizeInner(RectF newInner) {
246 Matrix m = getRotMatrix();
247 Matrix m0 = getInverseRotMatrix();
249 float aspectW = inner.width();
250 float aspectH = inner.height();
251 float aspRatio = aspectW / aspectH;
252 float[] corners = CropMath.getCornersFromRect(outer);
254 m.mapPoints(corners);
255 float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
256 float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
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
271 // no fixed corner, return without update
274 float widthSoFar = newInner.width();
276 for (int i = 0; i < newInnerCorners.length; i += 2) {
278 newInnerCorners[i], newInnerCorners[i + 1]
280 float[] c0 = Arrays.copyOf(c, 2);
282 if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
286 float[] l2 = CropMath.closestSide(c, corners);
288 newInnerCorners[i], newInnerCorners[i + 1],
289 oldInnerCorners[i], oldInnerCorners[i + 1]
291 float[] p = GeometryMathUtils.lineIntersect(l1, l2);
293 // lines are parallel or not well defined, so set to old
296 p[0] = oldInnerCorners[i];
297 p[1] = oldInnerCorners[i + 1];
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;
311 float heightSoFar = widthSoFar / aspRatio;
312 RectF ret = new RectF(inner);
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;
326 float[] retCorners = CropMath.getCornersFromRect(ret);
327 m0.mapPoints(retCorners);
328 innerRotated = retCorners;
329 // reconstrain to update inner
335 private boolean isConstrained() {
336 for (int i = 0; i < 8; i += 2) {
337 if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1]))
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);
352 private void rotateInner() {
353 Matrix m = getInverseRotMatrix();
354 m.mapPoints(innerRotated);
357 private Matrix getRotMatrix() {
358 Matrix m = new Matrix();
359 m.setRotate(rot, outer.centerX(), outer.centerY());
363 private Matrix getInverseRotMatrix() {
364 Matrix m = new Matrix();
365 m.setRotate(-rot, outer.centerX(), outer.centerY());