blob: 13b8d6de1fae6085f415e5c9332f6d01ccc56d1b [file] [log] [blame]
/*
* 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;
}
}