| /* |
| * 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.imageshow; |
| |
| import android.graphics.Matrix; |
| import android.graphics.RectF; |
| |
| 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() { |
| rot = 0; |
| outer = new RectF(); |
| inner = new RectF(); |
| innerRotated = new float[8]; |
| } |
| |
| 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(); |
| } |
| |
| /** |
| * 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 RectF getInner() { |
| return new RectF(inner); |
| } |
| |
| /** |
| * 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 = |
| GeometryMath.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 = GeometryMath.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 = GeometryMath.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; |
| } |
| } |