/* * Copyright (C) 2013 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.camera.crop; import android.graphics.Rect; import android.graphics.RectF; public class CropObject { private BoundedRect mBoundedRect; private float mAspectWidth = 1; private float mAspectHeight = 1; private boolean mFixAspectRatio = false; private float mRotation = 0; private float mTouchTolerance = 45; private float mMinSideSize = 20; public static final int MOVE_NONE = 0; // Sides public static final int MOVE_LEFT = 1; public static final int MOVE_TOP = 2; public static final int MOVE_RIGHT = 4; public static final int MOVE_BOTTOM = 8; public static final int MOVE_BLOCK = 16; // Corners public static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT; public static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT; public static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT; public static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT; private int mMovingEdges = MOVE_NONE; public CropObject(Rect outerBound, Rect innerBound, int outerAngle) { mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound); } public CropObject(RectF outerBound, RectF innerBound, int outerAngle) { mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound); } public void resetBoundsTo(RectF inner, RectF outer) { mBoundedRect.resetTo(0, outer, inner); } public void getInnerBounds(RectF r) { mBoundedRect.setToInner(r); } public void getOuterBounds(RectF r) { mBoundedRect.setToOuter(r); } public RectF getInnerBounds() { return mBoundedRect.getInner(); } public RectF getOuterBounds() { return mBoundedRect.getOuter(); } public int getSelectState() { return mMovingEdges; } public boolean isFixedAspect() { return mFixAspectRatio; } public void rotateOuter(int angle) { mRotation = angle % 360; mBoundedRect.setRotation(mRotation); clearSelectState(); } public boolean setInnerAspectRatio(float width, float height) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("Width and Height must be greater than zero"); } RectF inner = mBoundedRect.getInner(); CropMath.fixAspectRatioContained(inner, width, height); if (inner.width() < mMinSideSize || inner.height() < mMinSideSize) { return false; } mAspectWidth = width; mAspectHeight = height; mFixAspectRatio = true; mBoundedRect.setInner(inner); clearSelectState(); return true; } public void setTouchTolerance(float tolerance) { if (tolerance <= 0) { throw new IllegalArgumentException("Tolerance must be greater than zero"); } mTouchTolerance = tolerance; } public void setMinInnerSideSize(float minSide) { if (minSide <= 0) { throw new IllegalArgumentException("Min dide must be greater than zero"); } mMinSideSize = minSide; } public void unsetAspectRatio() { mFixAspectRatio = false; clearSelectState(); } public boolean hasSelectedEdge() { return mMovingEdges != MOVE_NONE; } public static boolean checkCorner(int selected) { return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT || selected == BOTTOM_LEFT; } public static boolean checkEdge(int selected) { return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT || selected == MOVE_BOTTOM; } public static boolean checkBlock(int selected) { return selected == MOVE_BLOCK; } public static boolean checkValid(int selected) { return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected) || checkCorner(selected); } public void clearSelectState() { mMovingEdges = MOVE_NONE; } public int wouldSelectEdge(float x, float y) { int edgeSelected = calculateSelectedEdge(x, y); if (edgeSelected != MOVE_NONE && edgeSelected != MOVE_BLOCK) { return edgeSelected; } return MOVE_NONE; } public boolean selectEdge(int edge) { if (!checkValid(edge)) { // temporary throw new IllegalArgumentException("bad edge selected"); // return false; } if ((mFixAspectRatio && !checkCorner(edge)) && !checkBlock(edge) && edge != MOVE_NONE) { // temporary throw new IllegalArgumentException("bad corner selected"); // return false; } mMovingEdges = edge; return true; } public boolean selectEdge(float x, float y) { int edgeSelected = calculateSelectedEdge(x, y); if (mFixAspectRatio) { edgeSelected = fixEdgeToCorner(edgeSelected); } if (edgeSelected == MOVE_NONE) { return false; } return selectEdge(edgeSelected); } public boolean moveCurrentSelection(float dX, float dY) { if (mMovingEdges == MOVE_NONE) { return false; } RectF crop = mBoundedRect.getInner(); float minWidthHeight = mMinSideSize; int movingEdges = mMovingEdges; if (movingEdges == MOVE_BLOCK) { mBoundedRect.moveInner(dX, dY); return true; } else { float dx = 0; float dy = 0; if ((movingEdges & MOVE_LEFT) != 0) { dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left; } if ((movingEdges & MOVE_TOP) != 0) { dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top; } if ((movingEdges & MOVE_RIGHT) != 0) { dx = Math.max(crop.right + dX, crop.left + minWidthHeight) - crop.right; } if ((movingEdges & MOVE_BOTTOM) != 0) { dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight) - crop.bottom; } if (mFixAspectRatio) { 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[] bUnit = GeometryMathUtils.normalize(b); float sp = GeometryMathUtils.scalarProjection(disp, bUnit); dx = sp * bUnit[0]; dy = sp * bUnit[1]; RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy); mBoundedRect.fixedAspectResizeInner(newCrop); } else { if ((movingEdges & MOVE_LEFT) != 0) { crop.left += dx; } if ((movingEdges & MOVE_TOP) != 0) { crop.top += dy; } if ((movingEdges & MOVE_RIGHT) != 0) { crop.right += dx; } if ((movingEdges & MOVE_BOTTOM) != 0) { crop.bottom += dy; } mBoundedRect.resizeInner(crop); } } return true; } // Helper methods private int calculateSelectedEdge(float x, float y) { RectF cropped = mBoundedRect.getInner(); float left = Math.abs(x - cropped.left); float right = Math.abs(x - cropped.right); float top = Math.abs(y - cropped.top); float bottom = Math.abs(y - cropped.bottom); int edgeSelected = MOVE_NONE; // Check left or right. if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) { edgeSelected |= MOVE_LEFT; } else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) && ((y - mTouchTolerance) <= cropped.bottom)) { edgeSelected |= MOVE_RIGHT; } // Check top or bottom. if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) { edgeSelected |= MOVE_TOP; } else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) && ((x - mTouchTolerance) <= cropped.right)) { edgeSelected |= MOVE_BOTTOM; } return edgeSelected; } private static RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) { RectF newCrop = null; // 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); } else if (moving_corner == BOTTOM_LEFT) { newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height() + dy); } else if (moving_corner == TOP_LEFT) { newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy, r.right, r.bottom); } else if (moving_corner == TOP_RIGHT) { newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left + r.width() + dx, r.bottom); } return newCrop; } private static int fixEdgeToCorner(int moving_edges) { if (moving_edges == MOVE_LEFT) { moving_edges |= MOVE_TOP; } if (moving_edges == MOVE_TOP) { moving_edges |= MOVE_LEFT; } if (moving_edges == MOVE_RIGHT) { moving_edges |= MOVE_BOTTOM; } if (moving_edges == MOVE_BOTTOM) { moving_edges |= MOVE_RIGHT; } return moving_edges; } }