/*
* Copyright 2013, Edmodo, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License.
* You may obtain a copy of the License in the LICENSE file, or 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.itbox.grzl.cropper;
import android.graphics.Rect;
import android.view.View;
/**
* Enum representing an edge in the crop window.
*/
public enum Edge {
LEFT,
TOP,
RIGHT,
BOTTOM;
// Private Constants ///////////////////////////////////////////////////////
// Minimum distance in pixels that one edge can get to its opposing edge.
// This is an arbitrary value that simply prevents the crop window from
// becoming too small.
public static final int MIN_CROP_LENGTH_PX = 40;
// Member Variables ////////////////////////////////////////////////////////
private float mCoordinate;
// Public Methods //////////////////////////////////////////////////////////
/**
* Sets the coordinate of the Edge. The coordinate will represent the
* x-coordinate for LEFT and RIGHT Edges and the y-coordinate for TOP and
* BOTTOM edges.
*
* @param coordinate the position of the edge
*/
public void setCoordinate(float coordinate) {
mCoordinate = coordinate;
}
/**
* Add the given number of pixels to the current coordinate position of this
* Edge.
*
* @param distance the number of pixels to add
*/
public void offset(float distance) {
mCoordinate += distance;
}
/**
* Gets the coordinate of the Edge
*
* @return the Edge coordinate (x-coordinate for LEFT and RIGHT Edges and
* the y-coordinate for TOP and BOTTOM edges)
*/
public float getCoordinate() {
return mCoordinate;
}
/**
* Sets the Edge to the given x-y coordinate but also adjusting for snapping
* to the image bounds and parent view border constraints.
*
* @param x the x-coordinate
* @param y the y-coordinate
* @param imageRect the bounding rectangle of the image
* @param imageSnapRadius the radius (in pixels) at which the edge should
* snap to the image
*/
public void adjustCoordinate(float x, float y, Rect imageRect, float imageSnapRadius, float aspectRatio) {
switch (this) {
case LEFT:
mCoordinate = adjustLeft(x, imageRect, imageSnapRadius, aspectRatio);
break;
case TOP:
mCoordinate = adjustTop(y, imageRect, imageSnapRadius, aspectRatio);
break;
case RIGHT:
mCoordinate = adjustRight(x, imageRect, imageSnapRadius, aspectRatio);
break;
case BOTTOM:
mCoordinate = adjustBottom(y, imageRect, imageSnapRadius, aspectRatio);
break;
}
}
/**
* Adjusts this Edge position such that the resulting window will have the
* given aspect ratio.
*
* @param aspectRatio the aspect ratio to achieve
*/
public void adjustCoordinate(float aspectRatio) {
final float left = Edge.LEFT.getCoordinate();
final float top = Edge.TOP.getCoordinate();
final float right = Edge.RIGHT.getCoordinate();
final float bottom = Edge.BOTTOM.getCoordinate();
switch (this) {
case LEFT:
mCoordinate = AspectRatioUtil.calculateLeft(top, right, bottom, aspectRatio);
break;
case TOP:
mCoordinate = AspectRatioUtil.calculateTop(left, right, bottom, aspectRatio);
break;
case RIGHT:
mCoordinate = AspectRatioUtil.calculateRight(left, top, bottom, aspectRatio);
break;
case BOTTOM:
mCoordinate = AspectRatioUtil.calculateBottom(left, top, right, aspectRatio);
break;
}
}
/**
* Returns whether or not you can re-scale the image based on whether any edge would be out of bounds.
* Checks all the edges for a possibility of jumping out of bounds.
*
* @param Edge the Edge that is about to be expanded
* @param imageRect the rectangle of the picture
* @param aspectratio the desired aspectRatio of the picture.
*
* @return whether or not the new image would be out of bounds.
*/
public boolean isNewRectangleOutOfBounds(Edge edge, Rect imageRect, float aspectRatio) {
float offset = edge.snapOffset(imageRect);
switch (this) {
case LEFT:
if (edge.equals(Edge.TOP)) {
float top = imageRect.top;
float bottom = Edge.BOTTOM.getCoordinate() - offset;
float right = Edge.RIGHT.getCoordinate();
float left = AspectRatioUtil.calculateLeft(top, right, bottom, aspectRatio);
return isOutOfBounds(top, left, bottom, right, imageRect);
}
else if (edge.equals(Edge.BOTTOM)) {
float bottom = imageRect.bottom;
float top = Edge.TOP.getCoordinate() - offset;
float right = Edge.RIGHT.getCoordinate();
float left = AspectRatioUtil.calculateLeft(top, right, bottom, aspectRatio);
return isOutOfBounds(top, left, bottom, right, imageRect);
}
break;
case TOP:
if (edge.equals(Edge.LEFT)) {
float left = imageRect.left;
float right = Edge.RIGHT.getCoordinate() - offset;
float bottom = Edge.BOTTOM.getCoordinate();
float top = AspectRatioUtil.calculateTop(left, right, bottom, aspectRatio);
return isOutOfBounds(top, left, bottom, right, imageRect);
}
else if (edge.equals(Edge.RIGHT)) {
float right = imageRect.right;
float left = Edge.LEFT.getCoordinate() - offset;
float bottom = Edge.BOTTOM.getCoordinate();
float top = AspectRatioUtil.calculateTop(left, right, bottom, aspectRatio);
return isOutOfBounds(top, left, bottom, right, imageRect);
}
break;
case RIGHT:
if (edge.equals(Edge.TOP)) {
float top = imageRect.top;
float bottom = Edge.BOTTOM.getCoordinate() - offset;
float left = Edge.LEFT.getCoordinate();
float right = AspectRatioUtil.calculateRight(left, top, bottom, aspectRatio);
return isOutOfBounds(top, left, bottom, right, imageRect);
}
else if (edge.equals(Edge.BOTTOM)) {
float bottom = imageRect.bottom;
float top = Edge.TOP.getCoordinate() - offset;
float left = Edge.LEFT.getCoordinate();
float right = AspectRatioUtil.calculateRight(left, top, bottom, aspectRatio);
return isOutOfBounds(top, left, bottom, right, imageRect);
}
break;
case BOTTOM:
if (edge.equals(Edge.LEFT)) {
float left = imageRect.left;
float right = Edge.RIGHT.getCoordinate() - offset;
float top = Edge.TOP.getCoordinate();
float bottom = AspectRatioUtil.calculateBottom(left, top, right, aspectRatio);
return isOutOfBounds(top, left, bottom, right, imageRect);
}
else if (edge.equals(Edge.RIGHT)) {
float right = imageRect.right;
float left = Edge.LEFT.getCoordinate() - offset;
float top = Edge.TOP.getCoordinate();
float bottom = AspectRatioUtil.calculateBottom(left, top, right, aspectRatio);
return isOutOfBounds(top, left, bottom, right, imageRect);
}
break;
}
return true;
}
/**
* Returns whether the new rectangle would be out of bounds.
*
* @param top
* @param left
* @param bottom
* @param right
* @param imageRect the Image to be compared with.
* @return whether it would be out of bounds
*/
private boolean isOutOfBounds(float top, float left, float bottom, float right, Rect imageRect) {
return (top < imageRect.top || left < imageRect.left || bottom > imageRect.bottom || right > imageRect.right);
}
/**
* Snap this Edge to the given image boundaries.
*
* @param imageRect the bounding rectangle of the image to snap to
* @return the amount (in pixels) that this coordinate was changed (i.e. the
* new coordinate minus the old coordinate value)
*/
public float snapToRect(Rect imageRect) {
final float oldCoordinate = mCoordinate;
switch (this) {
case LEFT:
mCoordinate = imageRect.left;
break;
case TOP:
mCoordinate = imageRect.top;
break;
case RIGHT:
mCoordinate = imageRect.right;
break;
case BOTTOM:
mCoordinate = imageRect.bottom;
break;
}
final float offset = mCoordinate - oldCoordinate;
return offset;
}
/**
* Returns the potential snap offset of snaptoRect, without changing the coordinate.
*
* @param imageRect the bounding rectangle of the image to snap to
* @return the amount (in pixels) that this coordinate was changed (i.e. the
* new coordinate minus the old coordinate value)
*/
public float snapOffset(Rect imageRect) {
final float oldCoordinate = mCoordinate;
float newCoordinate = oldCoordinate;
switch (this) {
case LEFT:
newCoordinate = imageRect.left;
break;
case TOP:
newCoordinate = imageRect.top;
break;
case RIGHT:
newCoordinate = imageRect.right;
break;
case BOTTOM:
newCoordinate = imageRect.bottom;
break;
}
final float offset = newCoordinate - oldCoordinate;
return offset;
}
/**
* Snap this Edge to the given View boundaries.
*
* @param view the View to snap to
*/
public void snapToView(View view) {
switch (this) {
case LEFT:
mCoordinate = 0;
break;
case TOP:
mCoordinate = 0;
break;
case RIGHT:
mCoordinate = view.getWidth();
break;
case BOTTOM:
mCoordinate = view.getHeight();
break;
}
}
/**
* Gets the current width of the crop window.
*/
public static float getWidth() {
return Edge.RIGHT.getCoordinate() - Edge.LEFT.getCoordinate();
}
/**
* Gets the current height of the crop window.
*/
public static float getHeight() {
return Edge.BOTTOM.getCoordinate() - Edge.TOP.getCoordinate();
}
/**
* Determines if this Edge is outside the inner margins of the given bounding
* rectangle. The margins come inside the actual frame by SNAPRADIUS amount;
* therefore, determines if the point is outside the inner "margin" frame.
*
*/
public boolean isOutsideMargin(Rect rect, float margin) {
boolean result = false;
switch (this) {
case LEFT:
result = mCoordinate - rect.left < margin;
break;
case TOP:
result = mCoordinate - rect.top < margin;
break;
case RIGHT:
result = rect.right - mCoordinate < margin;
break;
case BOTTOM:
result = rect.bottom - mCoordinate < margin;
break;
}
return result;
}
/**
* Determines if this Edge is outside the image frame of the given bounding
* rectangle.
*/
public boolean isOutsideFrame(Rect rect) {
double margin = 0;
boolean result = false;
switch (this) {
case LEFT:
result = mCoordinate - rect.left < margin;
break;
case TOP:
result = mCoordinate - rect.top < margin;
break;
case RIGHT:
result = rect.right - mCoordinate < margin;
break;
case BOTTOM:
result = rect.bottom - mCoordinate < margin;
break;
}
return result;
}
// Private Methods /////////////////////////////////////////////////////////
/**
* Get the resulting x-position of the left edge of the crop window given
* the handle's position and the image's bounding box and snap radius.
*
* @param x the x-position that the left edge is dragged to
* @param imageRect the bounding box of the image that is being cropped
* @param imageSnapRadius the snap distance to the image edge (in pixels)
* @return the actual x-position of the left edge
*/
private static float adjustLeft(float x, Rect imageRect, float imageSnapRadius, float aspectRatio) {
float resultX = x;
if (x - imageRect.left < imageSnapRadius)
resultX = imageRect.left;
else
{
// Select the minimum of the three possible values to use
float resultXHoriz = Float.POSITIVE_INFINITY;
float resultXVert = Float.POSITIVE_INFINITY;
// Checks if the window is too small horizontally
if (x >= Edge.RIGHT.getCoordinate() - MIN_CROP_LENGTH_PX)
resultXHoriz = Edge.RIGHT.getCoordinate() - MIN_CROP_LENGTH_PX;
// Checks if the window is too small vertically
if (((Edge.RIGHT.getCoordinate() - x) / aspectRatio) <= MIN_CROP_LENGTH_PX)
resultXVert = Edge.RIGHT.getCoordinate() - (MIN_CROP_LENGTH_PX * aspectRatio);
resultX = Math.min(resultX, Math.min(resultXHoriz, resultXVert));
}
return resultX;
}
/**
* Get the resulting x-position of the right edge of the crop window given
* the handle's position and the image's bounding box and snap radius.
*
* @param x the x-position that the right edge is dragged to
* @param imageRect the bounding box of the image that is being cropped
* @param imageSnapRadius the snap distance to the image edge (in pixels)
* @return the actual x-position of the right edge
*/
private static float adjustRight(float x, Rect imageRect, float imageSnapRadius, float aspectRatio) {
float resultX = x;
// If close to the edge
if (imageRect.right - x < imageSnapRadius)
resultX = imageRect.right;
else
{
// Select the maximum of the three possible values to use
float resultXHoriz = Float.NEGATIVE_INFINITY;
float resultXVert = Float.NEGATIVE_INFINITY;
// Checks if the window is too small horizontally
if (x <= Edge.LEFT.getCoordinate() + MIN_CROP_LENGTH_PX)
resultXHoriz = Edge.LEFT.getCoordinate() + MIN_CROP_LENGTH_PX;
// Checks if the window is too small vertically
if (((x - Edge.LEFT.getCoordinate()) / aspectRatio) <= MIN_CROP_LENGTH_PX) {
resultXVert = Edge.LEFT.getCoordinate() + (MIN_CROP_LENGTH_PX * aspectRatio);
}
resultX = Math.max(resultX, Math.max(resultXHoriz, resultXVert));
}
return resultX;
}
/**
* Get the resulting y-position of the top edge of the crop window given the
* handle's position and the image's bounding box and snap radius.
*
* @param y the x-position that the top edge is dragged to
* @param imageRect the bounding box of the image that is being cropped
* @param imageSnapRadius the snap distance to the image edge (in pixels)
* @return the actual y-position of the top edge
*/
private static float adjustTop(float y, Rect imageRect, float imageSnapRadius, float aspectRatio) {
float resultY = y;
if (y - imageRect.top < imageSnapRadius)
resultY = imageRect.top;
else
{
// Select the minimum of the three possible values to use
float resultYVert = Float.POSITIVE_INFINITY;
float resultYHoriz = Float.POSITIVE_INFINITY;
// Checks if the window is too small vertically
if (y >= Edge.BOTTOM.getCoordinate() - MIN_CROP_LENGTH_PX)
resultYHoriz = Edge.BOTTOM.getCoordinate() - MIN_CROP_LENGTH_PX;
// Checks if the window is too small horizontally
if (((Edge.BOTTOM.getCoordinate() - y) * aspectRatio) <= MIN_CROP_LENGTH_PX)
resultYVert = Edge.BOTTOM.getCoordinate() - (MIN_CROP_LENGTH_PX / aspectRatio);
resultY = Math.min(resultY, Math.min(resultYHoriz, resultYVert));
}
return resultY;
}
/**
* Get the resulting y-position of the bottom edge of the crop window given
* the handle's position and the image's bounding box and snap radius.
*
* @param y the x-position that the bottom edge is dragged to
* @param imageRect the bounding box of the image that is being cropped
* @param imageSnapRadius the snap distance to the image edge (in pixels)
* @return the actual y-position of the bottom edge
*/
private static float adjustBottom(float y, Rect imageRect, float imageSnapRadius, float aspectRatio) {
float resultY = y;
if (imageRect.bottom - y < imageSnapRadius)
resultY = imageRect.bottom;
else
{
// Select the maximum of the three possible values to use
float resultYVert = Float.NEGATIVE_INFINITY;
float resultYHoriz = Float.NEGATIVE_INFINITY;
// Checks if the window is too small vertically
if (y <= Edge.TOP.getCoordinate() + MIN_CROP_LENGTH_PX)
resultYVert = Edge.TOP.getCoordinate() + MIN_CROP_LENGTH_PX;
// Checks if the window is too small horizontally
if (((y - Edge.TOP.getCoordinate()) * aspectRatio) <= MIN_CROP_LENGTH_PX)
resultYHoriz = Edge.TOP.getCoordinate() + (MIN_CROP_LENGTH_PX / aspectRatio);
resultY = Math.max(resultY, Math.max(resultYHoriz, resultYVert));
}
return resultY;
}
}