diff options
Diffstat (limited to 'src/com/android/gallery3d/filtershow/crop/BoundedRect.java')
-rw-r--r-- | src/com/android/gallery3d/filtershow/crop/BoundedRect.java | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/filtershow/crop/BoundedRect.java b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java new file mode 100644 index 000000000..13b8d6de1 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.gallery3d.filtershow.crop; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; + +import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils; + +import java.util.Arrays; + +/** + * Maintains invariant that inner rectangle is constrained to be within the + * outer, rotated rectangle. + */ +public class BoundedRect { + private float rot; + private RectF outer; + private RectF inner; + private float[] innerRotated; + + public BoundedRect(float rotation, Rect outerRect, Rect innerRect) { + rot = rotation; + outer = new RectF(outerRect); + inner = new RectF(innerRect); + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + public BoundedRect(float rotation, RectF outerRect, RectF innerRect) { + rot = rotation; + outer = new RectF(outerRect); + inner = new RectF(innerRect); + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + public void resetTo(float rotation, RectF outerRect, RectF innerRect) { + rot = rotation; + outer.set(outerRect); + inner.set(innerRect); + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + /** + * Sets inner, and re-constrains it to fit within the rotated bounding rect. + */ + public void setInner(RectF newInner) { + if (inner.equals(newInner)) + return; + inner = newInner; + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + /** + * Sets rotation, and re-constrains inner to fit within the rotated bounding rect. + */ + public void setRotation(float rotation) { + if (rotation == rot) + return; + rot = rotation; + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + public void setToInner(RectF r) { + r.set(inner); + } + + public void setToOuter(RectF r) { + r.set(outer); + } + + public RectF getInner() { + return new RectF(inner); + } + + public RectF getOuter() { + return new RectF(outer); + } + + /** + * Tries to move the inner rectangle by (dx, dy). If this would cause it to leave + * the bounding rectangle, snaps the inner rectangle to the edge of the bounding + * rectangle. + */ + public void moveInner(float dx, float dy) { + Matrix m0 = getInverseRotMatrix(); + + RectF translatedInner = new RectF(inner); + translatedInner.offset(dx, dy); + + float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner); + float[] outerCorners = CropMath.getCornersFromRect(outer); + + m0.mapPoints(translatedInnerCorners); + float[] correction = { + 0, 0 + }; + + // find correction vectors for corners that have moved out of bounds + for (int i = 0; i < translatedInnerCorners.length; i += 2) { + float correctedInnerX = translatedInnerCorners[i] + correction[0]; + float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; + if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) { + float[] badCorner = { + correctedInnerX, correctedInnerY + }; + float[] nearestSide = CropMath.closestSide(badCorner, outerCorners); + float[] correctionVec = + GeometryMathUtils.shortestVectorFromPointToLine(badCorner, nearestSide); + correction[0] += correctionVec[0]; + correction[1] += correctionVec[1]; + } + } + + for (int i = 0; i < translatedInnerCorners.length; i += 2) { + float correctedInnerX = translatedInnerCorners[i] + correction[0]; + float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; + if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) { + float[] correctionVec = { + correctedInnerX, correctedInnerY + }; + CropMath.getEdgePoints(outer, correctionVec); + correctionVec[0] -= correctedInnerX; + correctionVec[1] -= correctedInnerY; + correction[0] += correctionVec[0]; + correction[1] += correctionVec[1]; + } + } + + // Set correction + for (int i = 0; i < translatedInnerCorners.length; i += 2) { + float correctedInnerX = translatedInnerCorners[i] + correction[0]; + float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; + // update translated corners with correction vectors + translatedInnerCorners[i] = correctedInnerX; + translatedInnerCorners[i + 1] = correctedInnerY; + } + + innerRotated = translatedInnerCorners; + // reconstrain to update inner + reconstrain(); + } + + /** + * Attempts to resize the inner rectangle. If this would cause it to leave + * the bounding rect, clips the inner rectangle to fit. + */ + public void resizeInner(RectF newInner) { + Matrix m = getRotMatrix(); + Matrix m0 = getInverseRotMatrix(); + + float[] outerCorners = CropMath.getCornersFromRect(outer); + m.mapPoints(outerCorners); + float[] oldInnerCorners = CropMath.getCornersFromRect(inner); + float[] newInnerCorners = CropMath.getCornersFromRect(newInner); + RectF ret = new RectF(newInner); + + for (int i = 0; i < newInnerCorners.length; i += 2) { + float[] c = { + newInnerCorners[i], newInnerCorners[i + 1] + }; + float[] c0 = Arrays.copyOf(c, 2); + m0.mapPoints(c0); + if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) { + float[] outerSide = CropMath.closestSide(c, outerCorners); + float[] pathOfCorner = { + newInnerCorners[i], newInnerCorners[i + 1], + oldInnerCorners[i], oldInnerCorners[i + 1] + }; + float[] p = GeometryMathUtils.lineIntersect(pathOfCorner, outerSide); + if (p == null) { + // lines are parallel or not well defined, so don't resize + p = new float[2]; + p[0] = oldInnerCorners[i]; + p[1] = oldInnerCorners[i + 1]; + } + // relies on corners being in same order as method + // getCornersFromRect + switch (i) { + case 0: + case 1: + ret.left = (p[0] > ret.left) ? p[0] : ret.left; + ret.top = (p[1] > ret.top) ? p[1] : ret.top; + break; + case 2: + case 3: + ret.right = (p[0] < ret.right) ? p[0] : ret.right; + ret.top = (p[1] > ret.top) ? p[1] : ret.top; + break; + case 4: + case 5: + ret.right = (p[0] < ret.right) ? p[0] : ret.right; + ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom; + break; + case 6: + case 7: + ret.left = (p[0] > ret.left) ? p[0] : ret.left; + ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom; + break; + default: + break; + } + } + } + float[] retCorners = CropMath.getCornersFromRect(ret); + m0.mapPoints(retCorners); + innerRotated = retCorners; + // reconstrain to update inner + reconstrain(); + } + + /** + * Attempts to resize the inner rectangle. If this would cause it to leave + * the bounding rect, clips the inner rectangle to fit while maintaining + * aspect ratio. + */ + public void fixedAspectResizeInner(RectF newInner) { + Matrix m = getRotMatrix(); + Matrix m0 = getInverseRotMatrix(); + + float aspectW = inner.width(); + float aspectH = inner.height(); + float aspRatio = aspectW / aspectH; + float[] corners = CropMath.getCornersFromRect(outer); + + m.mapPoints(corners); + float[] oldInnerCorners = CropMath.getCornersFromRect(inner); + float[] newInnerCorners = CropMath.getCornersFromRect(newInner); + + // find fixed corner + int fixed = -1; + if (inner.top == newInner.top) { + if (inner.left == newInner.left) + fixed = 0; // top left + else if (inner.right == newInner.right) + fixed = 2; // top right + } else if (inner.bottom == newInner.bottom) { + if (inner.right == newInner.right) + fixed = 4; // bottom right + else if (inner.left == newInner.left) + fixed = 6; // bottom left + } + // no fixed corner, return without update + if (fixed == -1) + return; + float widthSoFar = newInner.width(); + int moved = -1; + for (int i = 0; i < newInnerCorners.length; i += 2) { + float[] c = { + newInnerCorners[i], newInnerCorners[i + 1] + }; + float[] c0 = Arrays.copyOf(c, 2); + m0.mapPoints(c0); + if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) { + moved = i; + if (moved == fixed) + continue; + float[] l2 = CropMath.closestSide(c, corners); + float[] l1 = { + newInnerCorners[i], newInnerCorners[i + 1], + oldInnerCorners[i], oldInnerCorners[i + 1] + }; + float[] p = GeometryMathUtils.lineIntersect(l1, l2); + if (p == null) { + // lines are parallel or not well defined, so set to old + // corner + p = new float[2]; + p[0] = oldInnerCorners[i]; + p[1] = oldInnerCorners[i + 1]; + } + // relies on corners being in same order as method + // getCornersFromRect + float fixed_x = oldInnerCorners[fixed]; + float fixed_y = oldInnerCorners[fixed + 1]; + float newWidth = Math.abs(fixed_x - p[0]); + float newHeight = Math.abs(fixed_y - p[1]); + newWidth = Math.max(newWidth, aspRatio * newHeight); + if (newWidth < widthSoFar) + widthSoFar = newWidth; + } + } + + float heightSoFar = widthSoFar / aspRatio; + RectF ret = new RectF(inner); + if (fixed == 0) { + ret.right = ret.left + widthSoFar; + ret.bottom = ret.top + heightSoFar; + } else if (fixed == 2) { + ret.left = ret.right - widthSoFar; + ret.bottom = ret.top + heightSoFar; + } else if (fixed == 4) { + ret.left = ret.right - widthSoFar; + ret.top = ret.bottom - heightSoFar; + } else if (fixed == 6) { + ret.right = ret.left + widthSoFar; + ret.top = ret.bottom - heightSoFar; + } + float[] retCorners = CropMath.getCornersFromRect(ret); + m0.mapPoints(retCorners); + innerRotated = retCorners; + // reconstrain to update inner + reconstrain(); + } + + // internal methods + + private boolean isConstrained() { + for (int i = 0; i < 8; i += 2) { + if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1])) + return false; + } + return true; + } + + private void reconstrain() { + // innerRotated has been changed to have incorrect values + CropMath.getEdgePoints(outer, innerRotated); + Matrix m = getRotMatrix(); + float[] unrotated = Arrays.copyOf(innerRotated, 8); + m.mapPoints(unrotated); + inner = CropMath.trapToRect(unrotated); + } + + private void rotateInner() { + Matrix m = getInverseRotMatrix(); + m.mapPoints(innerRotated); + } + + private Matrix getRotMatrix() { + Matrix m = new Matrix(); + m.setRotate(rot, outer.centerX(), outer.centerY()); + return m; + } + + private Matrix getInverseRotMatrix() { + Matrix m = new Matrix(); + m.setRotate(-rot, outer.centerX(), outer.centerY()); + return m; + } +} |