From 7f072c4c99331a331d2950c11baca5426fad6643 Mon Sep 17 00:00:00 2001 From: Ruben Brunk Date: Wed, 31 Oct 2012 11:44:15 -0700 Subject: Refactoring crop, made unconstrained. Bug 7401363 Change-Id: I3ef10c43fd2a01216d6d6c8cc77b31ddef3fdd9b --- .../gallery3d/filtershow/FilterShowActivity.java | 1 + .../filtershow/imageshow/BoundedRect.java | 340 +++++++++++ .../gallery3d/filtershow/imageshow/CropMath.java | 191 ++++++ .../filtershow/imageshow/GeometryMath.java | 76 ++- .../filtershow/imageshow/GeometryMetadata.java | 73 --- .../gallery3d/filtershow/imageshow/ImageCrop.java | 653 +++++++++------------ .../filtershow/imageshow/ImageGeometry.java | 206 ++----- .../filtershow/imageshow/ImageStraighten.java | 5 +- 8 files changed, 915 insertions(+), 630 deletions(-) create mode 100644 src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java create mode 100644 src/com/android/gallery3d/filtershow/imageshow/CropMath.java (limited to 'src/com/android/gallery3d') diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index 43697bfe9..9a0c69dcf 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -207,6 +207,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageCrop.setAspectTextSize((int) getPixelsFromDip(18)); ImageCrop.setTouchTolerance((int) getPixelsFromDip(25)); + ImageCrop.setMinCropSize((int) getPixelsFromDip(55)); mImageViews.add(mImageShow); mImageViews.add(mImageCurves); mImageViews.add(mImageBorders); diff --git a/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java b/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java new file mode 100644 index 000000000..e94d1ed9e --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java @@ -0,0 +1,340 @@ +/* + * 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; + } +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/CropMath.java b/src/com/android/gallery3d/filtershow/imageshow/CropMath.java new file mode 100644 index 000000000..9037ca043 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/CropMath.java @@ -0,0 +1,191 @@ +/* + * 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; + +public class CropMath { + + /** + * Gets a float array of the 2D coordinates representing a rectangles + * corners. + * The order of the corners in the float array is: + * 0------->1 + * ^ | + * | v + * 3<-------2 + * + * @param r the rectangle to get the corners of + * @return the float array of corners (8 floats) + */ + + public static float[] getCornersFromRect(RectF r) { + float[] corners = { + r.left, r.top, + r.right, r.top, + r.right, r.bottom, + r.left, r.bottom + }; + return corners; + } + + /** + * Returns true iff point (x, y) is within or on the rectangle's bounds. + * RectF's "contains" function treats points on the bottom and right bound + * as not being contained. + * + * @param r the rectangle + * @param x the x value of the point + * @param y the y value of the point + * @return + */ + public static boolean inclusiveContains(RectF r, float x, float y) { + return !(x > r.right || x < r.left || y > r.bottom || y < r.top); + } + + /** + * Takes an array of 2D coordinates representing corners and returns the + * smallest rectangle containing those coordinates. + * + * @param array array of 2D coordinates + * @return smallest rectangle containing coordinates + */ + public static RectF trapToRect(float[] array) { + RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, + Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + for (int i = 1; i < array.length; i += 2) { + float x = array[i - 1]; + float y = array[i]; + r.left = (x < r.left) ? x : r.left; + r.top = (y < r.top) ? y : r.top; + r.right = (x > r.right) ? x : r.right; + r.bottom = (y > r.bottom) ? y : r.bottom; + } + r.sort(); + return r; + } + + /** + * If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the + * image bound rectangle, clamps it to the edge of the rectangle. + * + * @param imageBound the rectangle to clamp edge points to. + * @param array an array of points to clamp to the rectangle, gets set to + * the clamped values. + */ + public static void getEdgePoints(RectF imageBound, float[] array) { + if (array.length < 2) + return; + for (int x = 0; x < array.length; x += 2) { + array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right); + array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom); + } + } + + /** + * Takes a point and the corners of a rectangle and returns the two corners + * representing the side of the rectangle closest to the point. + * + * @param point the point which is being checked + * @param corners the corners of the rectangle + * @return two corners representing the side of the rectangle + */ + public static float[] closestSide(float[] point, float[] corners) { + int len = corners.length; + float oldMag = Float.POSITIVE_INFINITY; + float[] bestLine = null; + for (int i = 0; i < len; i += 2) { + float[] line = { + corners[i], corners[(i + 1) % len], + corners[(i + 2) % len], corners[(i + 3) % len] + }; + float mag = GeometryMath.vectorLength( + GeometryMath.shortestVectorFromPointToLine(point, line)); + if (mag < oldMag) { + oldMag = mag; + bestLine = line; + } + } + return bestLine; + } + + /** + * Checks if a given point is within a rotated rectangle. + * + * @param point 2D point to check + * @param bound rectangle to rotate + * @param rot angle of rotation about rectangle center + * @return true if point is within rotated rectangle + */ + public static boolean pointInRotatedRect(float[] point, RectF bound, float rot) { + Matrix m = new Matrix(); + float[] p = Arrays.copyOf(point, 2); + m.setRotate(rot, bound.centerX(), bound.centerY()); + Matrix m0 = new Matrix(); + if (!m.invert(m0)) + return false; + m0.mapPoints(p); + return inclusiveContains(bound, p[0], p[1]); + } + + /** + * Checks if a given point is within a rotated rectangle. + * + * @param point 2D point to check + * @param rotatedRect corners of a rotated rectangle + * @param center center of the rotated rectangle + * @return true if point is within rotated rectangle + */ + public static boolean pointInRotatedRect(float[] point, float[] rotatedRect, float[] center) { + RectF unrotated = new RectF(); + float angle = getUnrotated(rotatedRect, center, unrotated); + return pointInRotatedRect(point, unrotated, angle); + } + + /** + * Resizes rectangle to have a certain aspect ratio (center remains + * stationary). + * + * @param r rectangle to resize + * @param w new width aspect + * @param h new height aspect + */ + public static void fixAspectRatio(RectF r, float w, float h) { + float scale = Math.min(r.width() / w, r.height() / h); + float centX = r.centerX(); + float centY = r.centerY(); + float hw = scale * w / 2; + float hh = scale * h / 2; + r.set(centX - hw, centY - hh, centX + hw, centY + hh); + } + + private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) { + float dy = rotatedRect[1] - rotatedRect[3]; + float dx = rotatedRect[0] - rotatedRect[2]; + float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI); + Matrix m = new Matrix(); + m.setRotate(-angle, center[0], center[1]); + float[] unrotatedRect = new float[rotatedRect.length]; + m.mapPoints(unrotatedRect, rotatedRect); + unrotated.set(trapToRect(unrotatedRect)); + return angle; + } + +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java index 55f791820..568dadfc3 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java @@ -26,11 +26,37 @@ public class GeometryMath { return Math.max(Math.min(i, high), low); } - protected static float[] shortestVectorFromPointToLine(float[] point, float[] l1, float[] l2) { - float x1 = l1[0]; - float x2 = l2[0]; - float y1 = l1[1]; - float y2 = l2[1]; + public static float[] lineIntersect(float[] line1, float[] line2) { + float a0 = line1[0]; + float a1 = line1[1]; + float b0 = line1[2]; + float b1 = line1[3]; + float c0 = line2[0]; + float c1 = line2[1]; + float d0 = line2[2]; + float d1 = line2[3]; + float t0 = a0 - b0; + float t1 = a1 - b1; + float t2 = b0 - d0; + float t3 = d1 - b1; + float t4 = c0 - d0; + float t5 = c1 - d1; + + float denom = t1 * t4 - t0 * t5; + if (denom == 0) + return null; + float u = (t3 * t4 + t5 * t2) / denom; + float[] intersect = { + b0 + u * t0, b1 + u * t1 + }; + return intersect; + } + + public static float[] shortestVectorFromPointToLine(float[] point, float[] line) { + float x1 = line[0]; + float x2 = line[2]; + float y1 = line[1]; + float y2 = line[3]; float xdelt = x2 - x1; float ydelt = y2 - y1; if (xdelt == 0 && ydelt == 0) @@ -40,67 +66,75 @@ public class GeometryMath { float[] ret = { (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1)) }; - float [] vec = {ret[0] - point[0], ret[1] - point[1] }; + float[] vec = { + ret[0] - point[0], ret[1] - point[1] + }; return vec; } // A . B - public static float dotProduct(float[] a, float[] b){ + public static float dotProduct(float[] a, float[] b) { return a[0] * b[0] + a[1] * b[1]; } - public static float[] normalize(float[] a){ + public static float[] normalize(float[] a) { float length = (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]); - float[] b = { a[0] / length, a[1] / length }; + float[] b = { + a[0] / length, a[1] / length + }; return b; } // A onto B - public static float scalarProjection(float[] a, float[] b){ + public static float scalarProjection(float[] a, float[] b) { float length = (float) Math.sqrt(b[0] * b[0] + b[1] * b[1]); return dotProduct(a, b) / length; } - public static float[] getVectorFromPoints(float [] point1, float [] point2){ - float [] p = { point2[0] - point1[0], point2[1] - point1[1] }; + public static float[] getVectorFromPoints(float[] point1, float[] point2) { + float[] p = { + point2[0] - point1[0], point2[1] - point1[1] + }; return p; } - public static float[] getUnitVectorFromPoints(float [] point1, float [] point2){ - float [] p = { point2[0] - point1[0], point2[1] - point1[1] }; + public static float[] getUnitVectorFromPoints(float[] point1, float[] point2) { + float[] p = { + point2[0] - point1[0], point2[1] - point1[1] + }; float length = (float) Math.sqrt(p[0] * p[0] + p[1] * p[1]); p[0] = p[0] / length; p[1] = p[1] / length; return p; } - public static RectF scaleRect(RectF r, float scale){ + public static RectF scaleRect(RectF r, float scale) { return new RectF(r.left * scale, r.top * scale, r.right * scale, r.bottom * scale); } // A - B - public static float[] vectorSubtract(float [] a, float [] b){ + public static float[] vectorSubtract(float[] a, float[] b) { int len = a.length; if (len != b.length) return null; - float [] ret = new float[len]; - for (int i = 0; i < len; i++){ + float[] ret = new float[len]; + for (int i = 0; i < len; i++) { ret[i] = a[i] - b[i]; } return ret; } - public static float vectorLength(float [] a){ + public static float vectorLength(float[] a) { return (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]); } public static float scale(float oldWidth, float oldHeight, float newWidth, float newHeight) { if (oldHeight == 0 || oldWidth == 0) return 1; - return Math.min(newWidth / oldWidth , newHeight / oldHeight); + return Math.min(newWidth / oldWidth, newHeight / oldHeight); } - public static Rect roundNearest(RectF r){ + public static Rect roundNearest(RectF r) { Rect q = new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), Math.round(r.bottom)); return q; diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java index dffdc2449..0deb1e1ee 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java @@ -25,8 +25,6 @@ import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.ImageFilterGeometry; public class GeometryMetadata { - // Applied in order: rotate, crop, scale. - // Do not scale saved image (presumably?). private static final ImageFilterGeometry mImageFilter = new ImageFilterGeometry(); private static final String LOGTAG = "GeometryMetadata"; private float mScaleFactor = 1.0f; @@ -36,8 +34,6 @@ public class GeometryMetadata { private final RectF mPhotoBounds = new RectF(); private FLIP mFlip = FLIP.NONE; - private RectF mBounds = new RectF(); - public enum FLIP { NONE, VERTICAL, HORIZONTAL, BOTH } @@ -86,7 +82,6 @@ public class GeometryMetadata { mCropBounds.set(g.mCropBounds); mPhotoBounds.set(g.mPhotoBounds); mFlip = g.mFlip; - mBounds = g.mBounds; } public float getScaleFactor() { @@ -184,48 +179,16 @@ public class GeometryMetadata { + ",photoRect=" + mPhotoBounds.toShortString() + "]"; } - // TODO: refactor away - protected static Matrix getHorizontalMatrix(float width) { - Matrix flipHorizontalMatrix = new Matrix(); - flipHorizontalMatrix.setScale(-1, 1); - flipHorizontalMatrix.postTranslate(width, 0); - return flipHorizontalMatrix; - } - protected static void concatHorizontalMatrix(Matrix m, float width) { m.postScale(-1, 1); m.postTranslate(width, 0); } - // TODO: refactor away - protected static Matrix getVerticalMatrix(float height) { - Matrix flipVerticalMatrix = new Matrix(); - flipVerticalMatrix.setScale(1, -1); - flipVerticalMatrix.postTranslate(0, height); - return flipVerticalMatrix; - } - protected static void concatVerticalMatrix(Matrix m, float height) { m.postScale(1, -1); m.postTranslate(0, height); } - // TODO: refactor away - public static Matrix getFlipMatrix(float width, float height, FLIP type) { - if (type == FLIP.HORIZONTAL) { - return getHorizontalMatrix(width); - } else if (type == FLIP.VERTICAL) { - return getVerticalMatrix(height); - } else if (type == FLIP.BOTH) { - Matrix flipper = getVerticalMatrix(height); - flipper.postConcat(getHorizontalMatrix(width)); - return flipper; - } else { - Matrix m = new Matrix(); - m.reset(); // identity - return m; - } - } public static void concatMirrorMatrix(Matrix m, float width, float height, FLIP type) { if (type == FLIP.HORIZONTAL) { @@ -331,46 +294,10 @@ public class GeometryMetadata { return m1; } - // TODO: refactor away - public Matrix getFlipMatrix(float width, float height) { - FLIP type = getFlipType(); - return getFlipMatrix(width, height, type); - } - public boolean hasSwitchedWidthHeight() { return (((int) (mRotation / 90)) % 2) != 0; } - // TODO: refactor away - public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, - float rotation) { - float dx0 = width / 2; - float dy0 = height / 2; - Matrix m = getFlipMatrix(width, height); - m.postTranslate(-dx0, -dy0); - m.postRotate(rotation); - m.postScale(scaling, scaling); - m.postTranslate(dx, dy); - return m; - } - - // TODO: refactor away - public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, - boolean onlyRotate) { - float rot = mRotation; - if (!onlyRotate) { - rot += mStraightenRotation; - } - return buildGeometryMatrix(width, height, scaling, dx, dy, rot); - } - - // TODO: refactor away - public Matrix buildGeometryUIMatrix(float scaling, float dx, float dy) { - float w = mPhotoBounds.width(); - float h = mPhotoBounds.height(); - return buildGeometryMatrix(w, h, scaling, dx, dy, false); - } - public static Matrix buildPhotoMatrix(RectF photo, RectF crop, float rotation, float straighten, FLIP type) { Matrix m = new Matrix(); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java index a352a16e7..f25f5b182 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java @@ -29,22 +29,25 @@ import android.util.AttributeSet; import android.util.Log; import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.imageshow.ImageGeometry.MODES; public class ImageCrop extends ImageGeometry { private static final boolean LOGV = false; + + // Sides private static final int MOVE_LEFT = 1; private static final int MOVE_TOP = 2; private static final int MOVE_RIGHT = 4; private static final int MOVE_BOTTOM = 8; private static final int MOVE_BLOCK = 16; - //Corners + // Corners private static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT; private static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT; private static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT; private static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT; - private static final float MIN_CROP_WIDTH_HEIGHT = 0.1f; + private static int mMinSideSize = 100; private static int mTouchTolerance = 45; private boolean mFirstDraw = true; @@ -53,23 +56,28 @@ public class ImageCrop extends ImageGeometry { private boolean mFixAspectRatio = false; private float mLastRot = 0; - private final Paint borderPaint; + private BoundedRect mBounded = null; private int movingEdges; private final Drawable cropIndicator; private final int indicatorSize; private final int mBorderColor = Color.argb(128, 255, 255, 255); + // Offset between crop center and photo center + private float[] mOffset = { + 0, 0 + }; + private static final String LOGTAG = "ImageCrop"; private String mAspect = ""; private int mAspectTextSize = 24; - public void setAspectTextSize(int textSize){ + public void setAspectTextSize(int textSize) { mAspectTextSize = textSize; } - public void setAspectString(String a){ + public void setAspectString(String a) { mAspect = a; } @@ -80,10 +88,6 @@ public class ImageCrop extends ImageGeometry { Resources resources = context.getResources(); cropIndicator = resources.getDrawable(R.drawable.camera_crop); indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); - borderPaint = new Paint(); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setColor(mBorderColor); - borderPaint.setStrokeWidth(2f); } public ImageCrop(Context context, AttributeSet attrs) { @@ -91,10 +95,6 @@ public class ImageCrop extends ImageGeometry { Resources resources = context.getResources(); cropIndicator = resources.getDrawable(R.drawable.camera_crop); indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); - borderPaint = new Paint(); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setColor(mBorderColor); - borderPaint.setStrokeWidth(2f); } @Override @@ -102,84 +102,35 @@ public class ImageCrop extends ImageGeometry { return getContext().getString(R.string.crop); } - private void swapAspect(){ + private void swapAspect() { float temp = mAspectWidth; mAspectWidth = mAspectHeight; mAspectHeight = temp; } - public static void setTouchTolerance(int tolerance){ + /** + * Set tolerance for crop marker selection (in pixels) + */ + public static void setTouchTolerance(int tolerance) { mTouchTolerance = tolerance; } - private boolean switchCropBounds(int moving_corner, RectF dst) { - RectF crop = getCropBoundsDisplayed(); - float dx1 = 0; - float dy1 = 0; - float dx2 = 0; - float dy2 = 0; - if ((moving_corner & MOVE_RIGHT) != 0) { - dx1 = mCurrentX - crop.right; - } else if ((moving_corner & MOVE_LEFT) != 0) { - dx1 = mCurrentX - crop.left; - } - if ((moving_corner & MOVE_BOTTOM) != 0) { - dy1 = mCurrentY - crop.bottom; - } else if ((moving_corner & MOVE_TOP) != 0) { - dy1 = mCurrentY - crop.top; - } - RectF newCrop = null; - //Fix opposite corner in place and move sides - if (moving_corner == BOTTOM_RIGHT) { - newCrop = new RectF(crop.left, crop.top, crop.left + crop.height(), crop.top - + crop.width()); - } else if (moving_corner == BOTTOM_LEFT) { - newCrop = new RectF(crop.right - crop.height(), crop.top, crop.right, crop.top - + crop.width()); - } else if (moving_corner == TOP_LEFT) { - newCrop = new RectF(crop.right - crop.height(), crop.bottom - crop.width(), - crop.right, crop.bottom); - } else if (moving_corner == TOP_RIGHT) { - newCrop = new RectF(crop.left, crop.bottom - crop.width(), crop.left - + crop.height(), crop.bottom); - } - if ((moving_corner & MOVE_RIGHT) != 0) { - dx2 = mCurrentX - newCrop.right; - } else if ((moving_corner & MOVE_LEFT) != 0) { - dx2 = mCurrentX - newCrop.left; - } - if ((moving_corner & MOVE_BOTTOM) != 0) { - dy2 = mCurrentY - newCrop.bottom; - } else if ((moving_corner & MOVE_TOP) != 0) { - dy2 = mCurrentY - newCrop.top; - } - if (Math.sqrt(dx1*dx1 + dy1*dy1) > Math.sqrt(dx2*dx2 + dy2*dy2)){ - Matrix m = getCropBoundDisplayMatrix(); - Matrix m0 = new Matrix(); - if (!m.invert(m0)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX"); - return false; - } - if (!m0.mapRect(newCrop)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE"); - return false; - } - swapAspect(); - dst.set(newCrop); - return true; - } - return false; + /** + * Set minimum side length for crop box (in pixels) + */ + public static void setMinCropSize(int minHeightWidth) { + mMinSideSize = minHeightWidth; } - public void apply(float w, float h){ + public void apply(float w, float h) { mFixAspectRatio = true; mAspectWidth = w; mAspectHeight = h; setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } @@ -194,202 +145,147 @@ public class ImageCrop extends ImageGeometry { mAspectHeight = h / scale; setLocalCropBounds(getUntranslatedStraightenCropBounds(photobounds, getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } public void applyClear() { mFixAspectRatio = false; + mAspectWidth = 1; + mAspectHeight = 1; setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } - private float getScaledMinWidthHeight() { - RectF disp = new RectF(0, 0, getWidth(), getHeight()); - float scaled = Math.min(disp.width(), disp.height()) * MIN_CROP_WIDTH_HEIGHT - / computeScale(getWidth(), getHeight()); - return scaled; - } - - protected Matrix getCropRotationMatrix(float rotation, RectF localImage) { - Matrix m = getLocalGeoFlipMatrix(localImage.width(), localImage.height()); - m.postRotate(rotation, localImage.centerX(), localImage.centerY()); - if (!m.rectStaysRect()) { - return null; - } + private Matrix getPhotoBoundDisplayedMatrix() { + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); + Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop, + getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); + m.preScale(scale, scale); return m; } - protected Matrix getCropBoundDisplayMatrix(){ - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - m = new Matrix(); - } - float zoom = computeScale(getWidth(), getHeight()); - m.postTranslate(mXOffset, mYOffset); - m.postScale(zoom, zoom, mCenterX, mCenterY); - return m; + private Matrix getCropBoundDisplayedMatrix() { + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); + Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop, + getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); + m1.preScale(scale, scale); + return m1; } - protected RectF getCropBoundsDisplayed() { - RectF bounds = getLocalCropBounds(); - RectF crop = new RectF(bounds); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - m = new Matrix(); - } else { - m.mapRect(crop); - } - m = new Matrix(); - float zoom = computeScale(getWidth(), getHeight()); - m.setScale(zoom, zoom, mCenterX, mCenterY); - m.preTranslate(mXOffset, mYOffset); - m.mapRect(crop); - return crop; - } - - private RectF getRotatedCropBounds() { - RectF bounds = getLocalCropBounds(); - RectF crop = new RectF(bounds); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - return null; - } else { - m.mapRect(crop); - } - return crop; - } - - private RectF getUnrotatedCropBounds(RectF cropBounds) { - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO GET ROTATION MATRIX"); - return null; - } - Matrix m0 = new Matrix(); - if (!m.invert(m0)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX"); - return null; - } - RectF crop = new RectF(cropBounds); - if (!m0.mapRect(crop)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS"); - return null; - } - return crop; - } - - private RectF getRotatedStraightenBounds() { - RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), - getLocalStraighten()); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP STRAIGHTEN BOUNDS TO RECTANGLE"); - return null; - } else { - m.mapRect(straightenBounds); - } - return straightenBounds; + /** + * Takes the rotated corners of a rectangle and returns the angle; sets + * unrotated to be the unrotated version of the rectangle. + */ + private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) { + float dy = rotatedRect[1] - rotatedRect[3]; + float dx = rotatedRect[0] - rotatedRect[2]; + float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI); + Matrix m = new Matrix(); + m.setRotate(-angle, center[0], center[1]); + float[] unrotatedRect = new float[rotatedRect.length]; + m.mapPoints(unrotatedRect, rotatedRect); + unrotated.set(CropMath.trapToRect(unrotatedRect)); + return angle; } /** * Sets cropped bounds; modifies the bounds if it's smaller than the allowed * dimensions. */ - public void setCropBounds(RectF bounds) { - // Avoid cropping smaller than minimum width or height. + public boolean setCropBounds(RectF bounds) { RectF cbounds = new RectF(bounds); - float minWidthHeight = getScaledMinWidthHeight(); - float aw = mAspectWidth; - float ah = mAspectHeight; - if (mFixAspectRatio) { - minWidthHeight /= aw * ah; - int r = (int) (getLocalRotation() / 90); - if (r % 2 != 0) { - float temp = aw; - aw = ah; - ah = temp; - } - } - + Matrix mc = getCropBoundDisplayedMatrix(); + Matrix mcInv = new Matrix(); + mc.invert(mcInv); + mcInv.mapRect(cbounds); + // Avoid cropping smaller than minimum float newWidth = cbounds.width(); float newHeight = cbounds.height(); - if (mFixAspectRatio) { - if (newWidth < (minWidthHeight * aw) || newHeight < (minWidthHeight * ah)) { - newWidth = minWidthHeight * aw; - newHeight = minWidthHeight * ah; - } - } else { - if (newWidth < minWidthHeight) { - newWidth = minWidthHeight; - } - if (newHeight < minWidthHeight) { - newHeight = minWidthHeight; - } - } + float scale = getTransformState(null, null, null); + float minWidthHeight = mMinSideSize / scale; RectF pbounds = getLocalPhotoBounds(); - if (pbounds.width() < minWidthHeight) { - newWidth = pbounds.width(); + + // if photo is smaller than minimum, refuse to set crop bounds + if (pbounds.width() < minWidthHeight || pbounds.height() < minWidthHeight) { + return false; } - if (pbounds.height() < minWidthHeight) { - newHeight = pbounds.height(); + + // if incoming crop is smaller than minimum, refuse to set crop bounds + if (newWidth < minWidthHeight || newHeight < minWidthHeight) { + return false; } - cbounds.set(cbounds.left, cbounds.top, cbounds.left + newWidth, cbounds.top + newHeight); - RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), - getLocalStraighten()); - cbounds.intersect(straightenBounds); + float newX = bounds.centerX() - (getWidth() / 2f); + float newY = bounds.centerY() - (getHeight() / 2f); + mOffset[0] = newX; + mOffset[1] = newY; - if (mFixAspectRatio) { - fixAspectRatio(cbounds, aw, ah); - } setLocalCropBounds(cbounds); invalidate(); + return true; + } + + private BoundedRect getBoundedCrop(RectF crop) { + RectF photo = getLocalPhotoBounds(); + Matrix mp = getPhotoBoundDisplayedMatrix(); + float[] photoCorners = CropMath.getCornersFromRect(photo); + float[] photoCenter = { + photo.centerX(), photo.centerY() + }; + mp.mapPoints(photoCorners); + mp.mapPoints(photoCenter); + RectF scaledPhoto = new RectF(); + float angle = getUnrotated(photoCorners, photoCenter, scaledPhoto); + return new BoundedRect(angle, scaledPhoto, crop); } private void detectMovingEdges(float x, float y) { - RectF cropped = getCropBoundsDisplayed(); + Matrix m = getCropBoundDisplayedMatrix(); + RectF cropped = getLocalCropBounds(); + m.mapRect(cropped); + mBounded = getBoundedCrop(cropped); movingEdges = 0; - // Check left or right. float left = Math.abs(x - cropped.left); float right = Math.abs(x - cropped.right); - if ((left <= mTouchTolerance) && (left < right)) { + float top = Math.abs(y - cropped.top); + float bottom = Math.abs(y - cropped.bottom); + + // Check left or right. + if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) + && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) { movingEdges |= MOVE_LEFT; } - else if (right <= mTouchTolerance) { + else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) + && ((y - mTouchTolerance) <= cropped.bottom)) { movingEdges |= MOVE_RIGHT; } // Check top or bottom. - float top = Math.abs(y - cropped.top); - float bottom = Math.abs(y - cropped.bottom); - if ((top <= mTouchTolerance) & (top < bottom)) { + if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) + && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) { movingEdges |= MOVE_TOP; } - else if (bottom <= mTouchTolerance) { + else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) + && ((x - mTouchTolerance) <= cropped.right)) { movingEdges |= MOVE_BOTTOM; } - // Check inside block. - if (cropped.contains(x, y) && (movingEdges == 0)) { + if (movingEdges == 0) { movingEdges = MOVE_BLOCK; } if (mFixAspectRatio && (movingEdges != MOVE_BLOCK)) { @@ -398,7 +294,7 @@ public class ImageCrop extends ImageGeometry { invalidate(); } - private int fixEdgeToCorner(int moving_edges){ + private int fixEdgeToCorner(int moving_edges) { if (moving_edges == MOVE_LEFT) { moving_edges |= MOVE_TOP; } @@ -414,9 +310,9 @@ public class ImageCrop extends ImageGeometry { return moving_edges; } - private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy){ + private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) { RectF newCrop = null; - //Fix opposite corner in place and move sides + // Fix opposite corner in place and move sides if (moving_corner == BOTTOM_RIGHT) { newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height() + dy); @@ -434,120 +330,90 @@ public class ImageCrop extends ImageGeometry { } private void moveEdges(float dX, float dY) { - RectF cropped = getRotatedCropBounds(); - float minWidthHeight = getScaledMinWidthHeight(); - float scale = computeScale(getWidth(), getHeight()); - float deltaX = dX / scale; - float deltaY = dY / scale; - int select = movingEdges; - if (mFixAspectRatio && (select != MOVE_BLOCK)) { - - // TODO: add in orientation change for fixed aspect - /*if (select == TOP_LEFT || select == TOP_RIGHT || - select == BOTTOM_LEFT || select == BOTTOM_RIGHT){ - RectF blank = new RectF(); - if(switchCropBounds(select, blank)){ - setCropBounds(blank); - return; - } - }*/ - if (select == MOVE_LEFT) { - select |= MOVE_TOP; - } - if (select == MOVE_TOP) { - select |= MOVE_LEFT; - } - if (select == MOVE_RIGHT) { - select |= MOVE_BOTTOM; - } - if (select == MOVE_BOTTOM) { - select |= MOVE_RIGHT; - } - } - - if (select == MOVE_BLOCK) { - RectF straight = getRotatedStraightenBounds(); - // Move the whole cropped bounds within the photo display bounds. - deltaX = (deltaX > 0) ? Math.min(straight.right - cropped.right, deltaX) - : Math.max(straight.left - cropped.left, deltaX); - deltaY = (deltaY > 0) ? Math.min(straight.bottom - cropped.bottom, deltaY) - : Math.max(straight.top - cropped.top, deltaY); - cropped.offset(deltaX, deltaY); + RectF crop = mBounded.getInner(); + + Matrix mc = getCropBoundDisplayedMatrix(); + + RectF photo = getLocalPhotoBounds(); + Matrix mp = getPhotoBoundDisplayedMatrix(); + float[] photoCorners = CropMath.getCornersFromRect(photo); + float[] photoCenter = { + photo.centerX(), photo.centerY() + }; + mp.mapPoints(photoCorners); + mp.mapPoints(photoCenter); + + float minWidthHeight = mMinSideSize; + + if (movingEdges == MOVE_BLOCK) { + mBounded.moveInner(-dX, -dY); + RectF r = mBounded.getInner(); + setCropBounds(r); + return; } else { float dx = 0; float dy = 0; - if ((select & MOVE_LEFT) != 0) { - dx = Math.min(cropped.left + deltaX, cropped.right - minWidthHeight) - cropped.left; + if ((movingEdges & MOVE_LEFT) != 0) { + dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left; } - if ((select & MOVE_TOP) != 0) { - dy = Math.min(cropped.top + deltaY, cropped.bottom - minWidthHeight) - cropped.top; + if ((movingEdges & MOVE_TOP) != 0) { + dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top; } - if ((select & MOVE_RIGHT) != 0) { - dx = Math.max(cropped.right + deltaX, cropped.left + minWidthHeight) - - cropped.right; + if ((movingEdges & MOVE_RIGHT) != 0) { + dx = Math.max(crop.right + dX, crop.left + minWidthHeight) + - crop.right; } - if ((select & MOVE_BOTTOM) != 0) { - dy = Math.max(cropped.bottom + deltaY, cropped.top + minWidthHeight) - - cropped.bottom; + if ((movingEdges & MOVE_BOTTOM) != 0) { + dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight) + - crop.bottom; } if (mFixAspectRatio) { - RectF crop = getCropBoundsDisplayed(); - float [] l1 = {crop.left, crop.bottom}; - float [] l2 = {crop.right, crop.top}; - if(movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT){ + float[] l1 = { + crop.left, crop.bottom + }; + float[] l2 = { + crop.right, crop.top + }; + if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) { l1[1] = crop.top; l2[1] = crop.bottom; } - float[] b = { l1[0] - l2[0], l1[1] - l2[1] }; - float[] disp = {dx, dy}; + float[] b = { + l1[0] - l2[0], l1[1] - l2[1] + }; + float[] disp = { + dx, dy + }; float[] bUnit = GeometryMath.normalize(b); float sp = GeometryMath.scalarProjection(disp, bUnit); dx = sp * bUnit[0]; dy = sp * bUnit[1]; - RectF newCrop = fixedCornerResize(crop, select, dx * scale, dy * scale); - Matrix m = getCropBoundDisplayMatrix(); - Matrix m0 = new Matrix(); - if (!m.invert(m0)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX"); - return; - } - if (!m0.mapRect(newCrop)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE"); - return; - } + RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy); + + mBounded.fixedAspectResizeInner(newCrop); + newCrop = mBounded.getInner(); setCropBounds(newCrop); return; } else { - if ((select & MOVE_LEFT) != 0) { - cropped.left += dx; + if ((movingEdges & MOVE_LEFT) != 0) { + crop.left += dx; } - if ((select & MOVE_TOP) != 0) { - cropped.top += dy; + if ((movingEdges & MOVE_TOP) != 0) { + crop.top += dy; } - if ((select & MOVE_RIGHT) != 0) { - cropped.right += dx; + if ((movingEdges & MOVE_RIGHT) != 0) { + crop.right += dx; } - if ((select & MOVE_BOTTOM) != 0) { - cropped.bottom += dy; + if ((movingEdges & MOVE_BOTTOM) != 0) { + crop.bottom += dy; } } } - movingEdges = select; - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - Matrix m0 = new Matrix(); - if (!m.invert(m0)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX"); - } - if (!m0.mapRect(cropped)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS"); - } - setCropBounds(cropped); + mBounded.resizeInner(crop); + crop = mBounded.getInner(); + setCropBounds(crop); } private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) { @@ -560,7 +426,8 @@ public class ImageCrop extends ImageGeometry { @Override protected void setActionDown(float x, float y) { super.setActionDown(x, y); - detectMovingEdges(x, y); + detectMovingEdges(x + mOffset[0], y + mOffset[1]); + } @Override @@ -571,20 +438,54 @@ public class ImageCrop extends ImageGeometry { @Override protected void setActionMove(float x, float y) { - if (movingEdges != 0){ + + if (movingEdges != 0) { moveEdges(x - mCurrentX, y - mCurrentY); } super.setActionMove(x, y); + + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + setActionUp(); + cropSetup(); + invalidate(); } private void cropSetup() { + RectF crop = getLocalCropBounds(); + Matrix m = getCropBoundDisplayedMatrix(); + m.mapRect(crop); if (mFixAspectRatio) { - RectF cb = getRotatedCropBounds(); - fixAspectRatio(cb, mAspectWidth, mAspectHeight); - RectF cb0 = getUnrotatedCropBounds(cb); - setCropBounds(cb0); - } else { - setCropBounds(getLocalCropBounds()); + CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); + } + float dCentX = getWidth() / 2; + float dCentY = getHeight() / 2; + + BoundedRect r = getBoundedCrop(crop); + crop = r.getInner(); + if (!setCropBounds(crop)) { + float h = mMinSideSize / 2; + float wScale = 1; + float hScale = mAspectHeight / mAspectWidth; + if (hScale < 1) { + wScale = mAspectWidth / mAspectHeight; + hScale = 1; + } + crop.set(dCentX - h * wScale, dCentY - h * hScale, dCentX + h * wScale, dCentY + h + * hScale); + if (mFixAspectRatio) { + CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); + } + r.setInner(crop); + crop = r.getInner(); + if (!setCropBounds(crop)) { + crop.set(dCentX - h, dCentY - h, dCentX + h, dCentY + h); + r.setInner(crop); + crop = r.getInner(); + setCropBounds(crop); + } } } @@ -600,7 +501,7 @@ public class ImageCrop extends ImageGeometry { protected void gainedVisibility() { float rot = getLocalRotation(); // if has changed orientation via rotate - if( ((int) ((rot - mLastRot) / 90)) % 2 != 0 ){ + if (((int) ((rot - mLastRot) / 90)) % 2 != 0) { swapAspect(); } cropSetup(); @@ -610,7 +511,6 @@ public class ImageCrop extends ImageGeometry { @Override public void resetParameter() { super.resetParameter(); - cropSetup(); } @Override @@ -635,7 +535,6 @@ public class ImageCrop extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { - // TODO: move style to xml gPaint.setAntiAlias(true); gPaint.setFilterBitmap(true); gPaint.setDither(true); @@ -645,32 +544,35 @@ public class ImageCrop extends ImageGeometry { cropSetup(); mFirstDraw = false; } - float rotation = getLocalRotation(); - RectF crop = drawTransformed(canvas, image, gPaint); + RectF crop = drawTransformed(canvas, image, gPaint, mOffset); gPaint.setColor(mBorderColor); gPaint.setStrokeWidth(3); gPaint.setStyle(Paint.Style.STROKE); drawRuleOfThird(canvas, crop, gPaint); - if (mFixAspectRatio){ + if (mFixAspectRatio) { float w = crop.width(); float h = crop.height(); - float diag = (float) Math.sqrt(w*w + h*h); + float diag = (float) Math.sqrt(w * w + h * h); float dash_len = 20; int num_intervals = (int) (diag / dash_len); - float [] tl = { crop.left, crop.top }; - float centX = tl[0] + w/2; - float centY = tl[1] + h/2 + 5; - float [] br = { crop.right, crop.bottom }; - float [] vec = GeometryMath.getUnitVectorFromPoints(tl, br); - - float [] counter = tl; - for (int x = 0; x < num_intervals; x++ ){ + float[] tl = { + crop.left, crop.top + }; + float centX = tl[0] + w / 2; + float centY = tl[1] + h / 2 + 5; + float[] br = { + crop.right, crop.bottom + }; + float[] vec = GeometryMath.getUnitVectorFromPoints(tl, br); + + float[] counter = tl; + for (int x = 0; x < num_intervals; x++) { float tempX = counter[0] + vec[0] * dash_len; float tempY = counter[1] + vec[1] * dash_len; - if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2){ + if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2) { canvas.drawLine(counter[0], counter[1], tempX, tempY, gPaint); } counter[0] = tempX; @@ -682,54 +584,35 @@ public class ImageCrop extends ImageGeometry { canvas.drawText(mAspect, centX, centY, gPaint); } - gPaint.setColor(mBorderColor); - gPaint.setStrokeWidth(3); - gPaint.setStyle(Paint.Style.STROKE); - drawStraighten(canvas, gPaint); - - int decoded_moving = decoder(movingEdges, rotation); - canvas.save(); - canvas.rotate(rotation, mCenterX, mCenterY); - RectF scaledCrop = unrotatedCropBounds(); - boolean notMoving = decoded_moving == 0; - if (((decoded_moving & MOVE_TOP) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); - } - if (((decoded_moving & MOVE_BOTTOM) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); - } - if (((decoded_moving & MOVE_LEFT) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); - } - if (((decoded_moving & MOVE_RIGHT) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); - } - canvas.restore(); - } - - private int bitCycleLeft(int x, int times, int d) { - int mask = (1 << d) - 1; - int mout = x & mask; - times %= d; - int hi = mout >> (d - times); - int low = (mout << times) & mask; - int ret = x & ~mask; - ret |= low; - ret |= hi; - return ret; - } - - protected int decoder(int movingEdges, float rotation) { - int rot = constrainedRotation(rotation); - switch (rot) { - case 90: - return bitCycleLeft(movingEdges, 3, 4); - case 180: - return bitCycleLeft(movingEdges, 2, 4); - case 270: - return bitCycleLeft(movingEdges, 1, 4); - default: - return movingEdges; + RectF scaledCrop = crop; + boolean notMoving = (movingEdges == 0); + if (mFixAspectRatio) { + if ((movingEdges == TOP_LEFT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.top); + } + if ((movingEdges == TOP_RIGHT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.top); + } + if ((movingEdges == BOTTOM_LEFT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.bottom); + } + if ((movingEdges == BOTTOM_RIGHT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.bottom); + } + } else { + if (((movingEdges & MOVE_TOP) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); + } + if (((movingEdges & MOVE_BOTTOM) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); + } + if (((movingEdges & MOVE_LEFT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); + } + if (((movingEdges & MOVE_RIGHT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); + } } } + } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java index 42dd139bc..c8ae444da 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java @@ -33,7 +33,7 @@ import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP; import com.android.gallery3d.filtershow.presets.ImagePreset; public abstract class ImageGeometry extends ImageSlave { - private boolean mVisibilityGained = false; + protected boolean mVisibilityGained = false; private boolean mHasDrawn = false; protected static final float MAX_STRAIGHTEN_ANGLE = 45; @@ -191,8 +191,8 @@ public abstract class ImageGeometry extends ImageSlave { return r * 90; } - protected Matrix getLocalGeoFlipMatrix(float width, float height) { - return mLocalGeometry.getFlipMatrix(width, height); + protected boolean isHeightWidthSwapped() { + return ((int) (getLocalRotation() / 90)) % 2 != 0; } protected void setLocalStraighten(float r) { @@ -217,32 +217,6 @@ public abstract class ImageGeometry extends ImageSlave { return getLocalRotation() + getLocalStraighten(); } - protected static float[] getCornersFromRect(RectF r) { - // Order is: - // 0------->1 - // ^ | - // | v - // 3<-------2 - float[] corners = { - r.left, r.top, // 0 - r.right, r.top, // 1 - r.right, r.bottom,// 2 - r.left, r.bottom // 3 - }; - return corners; - } - - // If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the - // image bound rectangle, clamps it to the edge of the rectangle. - protected static void getEdgePoints(RectF imageBound, float[] array) { - if (array.length < 2) - return; - for (int x = 0; x < array.length; x += 2) { - array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right); - array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom); - } - } - protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) { Path crop = new Path(); crop.moveTo(points[0], points[1]); @@ -254,16 +228,6 @@ public abstract class ImageGeometry extends ImageSlave { return crop; } - protected static void fixAspectRatio(RectF r, float w, float h) { - float scale = Math.min(r.width() / w, r.height() / h); - float centX = r.centerX(); - float centY = r.centerY(); - float hw = scale * w / 2; - float hh = scale * h / 2; - r.set(centX - hw, centY - hh, centX + hw, centY + hh); - - } - protected static float getNewHeightForWidthAspect(float width, float w, float h) { return width * h / w; } @@ -290,11 +254,11 @@ public abstract class ImageGeometry extends ImageSlave { } protected void gainedVisibility() { - // TODO: Override this stub. + // Override this stub. } protected void lostVisibility() { - // TODO: Override this stub. + // Override this stub. } @Override @@ -327,7 +291,7 @@ public abstract class ImageGeometry extends ImageSlave { } protected int getLocalValue() { - return 0; // TODO: Override this + return 0; // Override this } protected void setActionDown(float x, float y) { @@ -402,110 +366,19 @@ public abstract class ImageGeometry extends ImageSlave { return new RectF(left, top, right, bottom); } - protected Matrix getGeoMatrix(RectF r, boolean onlyRotate) { - RectF pbounds = getLocalPhotoBounds(); - float scale = GeometryMath - .scale(pbounds.width(), pbounds.height(), getWidth(), getHeight()); - if (((int) (getLocalRotation() / 90)) % 2 != 0) { - scale = GeometryMath.scale(pbounds.width(), pbounds.height(), getHeight(), getWidth()); - } - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - float w = r.left * 2 + r.width(); - float h = r.top * 2 + r.height(); - return mLocalGeometry.buildGeometryMatrix(w, h, scale, xoff, yoff, onlyRotate); - } - - protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) { - canvas.save(); - canvas.drawBitmap(bitmap, m, paint); - canvas.restore(); - } - - protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint) { - float scale = computeScale(getWidth(), getHeight()); - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - Matrix m = mLocalGeometry.buildGeometryUIMatrix(scale, xoff, yoff); - drawImageBitmap(canvas, bitmap, paint, m); - } - protected RectF straightenBounds() { RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten()); - Matrix m = getGeoMatrix(bounds, true); - m.mapRect(bounds); - return bounds; - } - - protected void drawStraighten(Canvas canvas, Paint paint) { - RectF bounds = straightenBounds(); - canvas.save(); - canvas.drawRect(bounds, paint); - canvas.restore(); - } - - protected RectF unrotatedCropBounds() { - RectF bounds = getLocalCropBounds(); - RectF pbounds = getLocalPhotoBounds(); float scale = computeScale(getWidth(), getHeight()); - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - Matrix m = mLocalGeometry.buildGeometryMatrix(pbounds.width(), pbounds.height(), scale, - xoff, yoff, 0); - m.mapRect(bounds); - return bounds; - } - - protected RectF cropBounds() { - RectF bounds = getLocalCropBounds(); - Matrix m = getGeoMatrix(getLocalPhotoBounds(), true); - m.mapRect(bounds); + bounds = GeometryMath.scaleRect(bounds, scale); + float dx = (getWidth() / 2) - bounds.centerX(); + float dy = (getHeight() / 2) - bounds.centerY(); + bounds.offset(dx, dy); return bounds; } - // Fails for non-90 degree - protected void drawCrop(Canvas canvas, Paint paint) { - RectF bounds = cropBounds(); - canvas.save(); - canvas.drawRect(bounds, paint); - canvas.restore(); - } - - protected void drawCropSafe(Canvas canvas, Paint paint) { - Matrix m = getGeoMatrix(getLocalPhotoBounds(), true); - RectF crop = getLocalCropBounds(); - if (!m.rectStaysRect()) { - float[] corners = getCornersFromRect(crop); - m.mapPoints(corners); - drawClosedPath(canvas, paint, corners); - } else { - m.mapRect(crop); - Path path = new Path(); - path.addRect(crop, Path.Direction.CCW); - canvas.drawPath(path, paint); - } - } - - protected void drawTransformedBitmap(Canvas canvas, Bitmap bitmap, Paint paint, boolean clip) { - paint.setARGB(255, 0, 0, 0); - drawImageBitmap(canvas, bitmap, paint); - paint.setColor(Color.WHITE); - paint.setStyle(Style.STROKE); - paint.setStrokeWidth(2); - drawCropSafe(canvas, paint); - paint.setColor(getDefaultBackgroundColor()); - paint.setStyle(Paint.Style.FILL); - drawShadows(canvas, paint, unrotatedCropBounds()); - } - - protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) { - RectF display = new RectF(0, 0, getWidth(), getHeight()); - drawShadows(canvas, p, innerBounds, display, getLocalRotation(), getWidth() / 2, - getHeight() / 2); - } - - protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds, + protected static void drawRotatedShadows(Canvas canvas, Paint p, RectF innerBounds, + RectF outerBounds, float rotation, float centerX, float centerY) { canvas.save(); canvas.rotate(rotation, centerX, centerY); @@ -527,6 +400,15 @@ public abstract class ImageGeometry extends ImageSlave { canvas.restore(); } + protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) { + float w = getWidth(); + float h = getHeight(); + canvas.drawRect(0f, 0f, w, innerBounds.top, p); + canvas.drawRect(0f, innerBounds.top, innerBounds.left, innerBounds.bottom, p); + canvas.drawRect(innerBounds.right, innerBounds.top, w, innerBounds.bottom, p); + canvas.drawRect(0f, innerBounds.bottom, w, h, p); + } + @Override public void onDraw(Canvas canvas) { if (getDirtyGeometryFlag()) { @@ -547,21 +429,38 @@ public abstract class ImageGeometry extends ImageSlave { // TODO: Override this stub. } - protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p) { - p.setARGB(255, 0, 0, 0); + /** + * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix + * and returns the scale factor. + */ + protected float getTransformState(RectF photo, RectF crop, float[] displayCenter) { RectF photoBounds = getLocalPhotoBounds(); RectF cropBounds = getLocalCropBounds(); float scale = computeScale(getWidth(), getHeight()); // checks if local rotation is an odd multiple of 90. - if (((int) (getLocalRotation() / 90)) % 2 != 0) { + if (isHeightWidthSwapped()) { scale = computeScale(getHeight(), getWidth()); } // put in screen coordinates - RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale); - RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale); - float[] displayCenter = { - getWidth() / 2f, getHeight() / 2f - }; + if (crop != null) { + crop.set(GeometryMath.scaleRect(cropBounds, scale)); + } + if (photo != null) { + photo.set(GeometryMath.scaleRect(photoBounds, scale)); + } + if (displayCenter != null && displayCenter.length >= 2) { + displayCenter[0] = getWidth() / 2f; + displayCenter[1] = getHeight() / 2f; + } + return scale; + } + + protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p, float[] offset) { + p.setARGB(255, 0, 0, 0); + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop, getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); @@ -569,9 +468,11 @@ public abstract class ImageGeometry extends ImageSlave { getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); m1.mapRect(scaledCrop); Path path = new Path(); + scaledCrop.offset(-offset[0], -offset[1]); path.addRect(scaledCrop, Path.Direction.CCW); m.preScale(scale, scale); + m.postTranslate(-offset[0], -offset[1]); canvas.save(); canvas.drawBitmap(photo, m, p); canvas.restore(); @@ -580,6 +481,11 @@ public abstract class ImageGeometry extends ImageSlave { p.setStyle(Style.STROKE); p.setStrokeWidth(2); canvas.drawPath(path, p); + + p.setColor(getDefaultBackgroundColor()); + p.setAlpha(128); + p.setStyle(Paint.Style.FILL); + drawShadows(canvas, p, scaledCrop); return scaledCrop; } @@ -590,7 +496,7 @@ public abstract class ImageGeometry extends ImageSlave { float imageHeight = cropBounds.height(); float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight()); // checks if local rotation is an odd multiple of 90. - if (((int) (getLocalRotation() / 90)) % 2 != 0) { + if (isHeightWidthSwapped()) { scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth()); } // put in screen coordinates @@ -618,6 +524,8 @@ public abstract class ImageGeometry extends ImageSlave { p.setStyle(Paint.Style.FILL); scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1] - scaledCrop.centerY()); - drawShadows(canvas, p, scaledCrop); + RectF display = new RectF(0, 0, getWidth(), getHeight()); + drawRotatedShadows(canvas, p, scaledCrop, display, getLocalRotation(), getWidth() / 2, + getHeight() / 2); } } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java index 57a22aab3..7a539da8f 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; @@ -105,10 +106,10 @@ public class ImageStraighten extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { - drawTransformed(canvas, image, gPaint); + float [] o = {0, 0}; + RectF bounds = drawTransformed(canvas, image, gPaint, o); // Draw the grid - RectF bounds = straightenBounds(); Path path = new Path(); path.addRect(bounds, Path.Direction.CCW); gPaint.setARGB(255, 255, 255, 255); -- cgit v1.2.3