/*
* Copyright (C) 2012 www.amsoft.cn
*
* 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.ab.view.cropimage;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.RegionIterator;
import android.graphics.drawable.Drawable;
import android.view.View;
import com.ab.util.AbFileUtil;
import com.ab.util.AbImageUtil;
// TODO: Auto-generated Javadoc
// This class is used by CropImage to display a highlighted cropping rectangle
// overlayed with the image. There are two coordinate spaces in use. One is
// image, another is screen. computeLayout() uses mMatrix to map from image
// space to screen space.
/**
* The Class HighlightView.
*/
public class HighlightView {
/** The Constant TAG. */
@SuppressWarnings("unused")
private static final String TAG = "HighlightView";
/** The m context. */
View mContext; // The View displaying the image.
/** The Constant GROW_NONE. */
public static final int GROW_NONE = (1 << 0);
/** The Constant GROW_LEFT_EDGE. */
public static final int GROW_LEFT_EDGE = (1 << 1);
/** The Constant GROW_RIGHT_EDGE. */
public static final int GROW_RIGHT_EDGE = (1 << 2);
/** The Constant GROW_TOP_EDGE. */
public static final int GROW_TOP_EDGE = (1 << 3);
/** The Constant GROW_BOTTOM_EDGE. */
public static final int GROW_BOTTOM_EDGE = (1 << 4);
/** The Constant MOVE. */
public static final int MOVE = (1 << 5);
/**
* Instantiates a new highlight view.
*
* @param ctx the ctx
*/
public HighlightView(View ctx) {
mContext = ctx;
}
/**
* Inits the.
*/
private void init() {
mResizeDrawableDiagonal = AbImageUtil.bitmapToDrawable(AbFileUtil.getBitmapFromSrc("image/crop_big.png"));
mResizeDrawableDiagonal2 = AbImageUtil.bitmapToDrawable(AbFileUtil.getBitmapFromSrc("image/crop_small.png"));
}
/** The m is focused. */
public boolean mIsFocused;
/** The m hidden. */
boolean mHidden;
/**
* Checks for focus.
*
* @return true, if successful
*/
public boolean hasFocus() {
return mIsFocused;
}
/**
* Sets the focus.
*
* @param f the new focus
*/
public void setFocus(boolean f) {
mIsFocused = f;
}
/**
* Sets the hidden.
*
* @param hidden the new hidden
*/
public void setHidden(boolean hidden) {
mHidden = hidden;
}
/**
* Draw.
*
* @param canvas the canvas
*/
public void draw(Canvas canvas) {
if (mHidden) {
return;
}
canvas.save();
Path path = new Path();
if (!hasFocus()) {
mOutlinePaint.setColor(0xFF000000);
canvas.drawRect(mDrawRect, mOutlinePaint);
} else {
Rect viewDrawingRect = new Rect();
mContext.getDrawingRect(viewDrawingRect);
if (mCircle) {
float width = mDrawRect.width();
float height = mDrawRect.height();
path.addCircle(mDrawRect.left + (width / 2), mDrawRect.top + (height / 2), width / 2, Path.Direction.CW);
mOutlinePaint.setColor(0xFFEF04D6);
} else {
path.addRect(new RectF(mDrawRect), Path.Direction.CW);
mOutlinePaint.setColor(0xFFFF8A00);
}
Region region = new Region();
region.set(viewDrawingRect);
region.op(mDrawRect, Region.Op.DIFFERENCE);
RegionIterator iter = new RegionIterator(region);
Rect r = new Rect();
while (iter.next(r)) {
canvas.drawRect(r, hasFocus() ? mFocusPaint : mNoFocusPaint);
}
//android 4.0 Support bad.java.lang.UnsupportedOperationException
//canvas.clipPath(path, Region.Op.DIFFERENCE);
//canvas.drawRect(viewDrawingRect, hasFocus() ? mFocusPaint : mNoFocusPaint);
canvas.restore();
canvas.drawPath(path, mOutlinePaint);
if (mMode == ModifyMode.Grow) {
if (mCircle) {
int width = mResizeDrawableDiagonal.getIntrinsicWidth();
int height = mResizeDrawableDiagonal.getIntrinsicHeight();
int d = (int) Math.round(Math.cos(/* 45deg */Math.PI / 4D) * (mDrawRect.width() / 2D));
int x = mDrawRect.left + (mDrawRect.width() / 2) + d - width / 2;
int y = mDrawRect.top + (mDrawRect.height() / 2) - d - height / 2;
mResizeDrawableDiagonal.setBounds(x, y, x + mResizeDrawableDiagonal.getIntrinsicWidth(), y
+ mResizeDrawableDiagonal.getIntrinsicHeight());
mResizeDrawableDiagonal.draw(canvas);
} else {
// int left = mDrawRect.left + 1;
// int right = mDrawRect.right + 1;
// int top = mDrawRect.top + 4;
// int bottom = mDrawRect.bottom + 3;
//
// int widthWidth = mResizeDrawableWidth.getIntrinsicWidth() / 2;
// int widthHeight = mResizeDrawableWidth.getIntrinsicHeight() / 2;
// int heightHeight = mResizeDrawableHeight.getIntrinsicHeight() / 2;
// int heightWidth = mResizeDrawableHeight.getIntrinsicWidth() / 2;
//
// int xMiddle = mDrawRect.left + ((mDrawRect.right - mDrawRect.left) / 2);
// int yMiddle = mDrawRect.top + ((mDrawRect.bottom - mDrawRect.top) / 2);
//
// mResizeDrawableWidth.setBounds(left - widthWidth, yMiddle - widthHeight, left + widthWidth, yMiddle
// + widthHeight);
// mResizeDrawableWidth.draw(canvas);
//
// mResizeDrawableWidth.setBounds(right - widthWidth, yMiddle - widthHeight, right + widthWidth, yMiddle
// + widthHeight);
// mResizeDrawableWidth.draw(canvas);
//
// mResizeDrawableHeight.setBounds(xMiddle - heightWidth, top - heightHeight, xMiddle + heightWidth, top
// + heightHeight);
// mResizeDrawableHeight.draw(canvas);
//
// mResizeDrawableHeight.setBounds(xMiddle - heightWidth, bottom - heightHeight, xMiddle + heightWidth, bottom
// + heightHeight);
// mResizeDrawableHeight.draw(canvas);
}
}
if (mCircle) {
}else{
int left = mDrawRect.left + 1;
int right = mDrawRect.right + 1;
int top = mDrawRect.top + 4;
int bottom = mDrawRect.bottom + 3;
int widthWidth = mResizeDrawableDiagonal.getIntrinsicWidth() / 2;
int widthHeight = mResizeDrawableDiagonal.getIntrinsicHeight() / 2;
mResizeDrawableDiagonal2.setBounds(left - widthWidth, top - widthHeight, left + widthWidth, top+ widthHeight);
mResizeDrawableDiagonal2.draw(canvas);
mResizeDrawableDiagonal.setBounds(right - widthWidth, top - widthHeight, right + widthWidth, top+ widthHeight);
mResizeDrawableDiagonal.draw(canvas);
mResizeDrawableDiagonal.setBounds(left - widthWidth, bottom - widthHeight, left + widthWidth, bottom+ widthHeight);
mResizeDrawableDiagonal.draw(canvas);
mResizeDrawableDiagonal2.setBounds(right - widthWidth, bottom - widthHeight, right + widthWidth, bottom+ widthHeight);
mResizeDrawableDiagonal2.draw(canvas);
}
}
}
/**
* Sets the mode.
*
* @param mode the new mode
*/
public void setMode(ModifyMode mode) {
if (mode != mMode) {
mMode = mode;
mContext.invalidate();
}
}
// Determines which edges are hit by touching at (x, y).
/**
* Gets the hit.
*
* @param x the x
* @param y the y
* @return the hit
*/
public int getHit(float x, float y) {
Rect r = computeLayout();
final float hysteresis = 20F;
int retval = GROW_NONE;
if (mCircle) {
float distX = x - r.centerX();
float distY = y - r.centerY();
int distanceFromCenter = (int) Math.sqrt(distX * distX + distY * distY);
int radius = mDrawRect.width() / 2;
int delta = distanceFromCenter - radius;
if (Math.abs(delta) <= hysteresis) {
if (Math.abs(distY) > Math.abs(distX)) {
if (distY < 0) {
retval = GROW_TOP_EDGE;
} else {
retval = GROW_BOTTOM_EDGE;
}
} else {
if (distX < 0) {
retval = GROW_LEFT_EDGE;
} else {
retval = GROW_RIGHT_EDGE;
}
}
} else
if (distanceFromCenter < radius) {
retval = MOVE;
} else {
retval = GROW_NONE;
}
} else {
// verticalCheck makes sure the position is between the top and
// the bottom edge (with some tolerance). Similar for horizCheck.
boolean verticalCheck = (y >= r.top - hysteresis) && (y < r.bottom + hysteresis);
boolean horizCheck = (x >= r.left - hysteresis) && (x < r.right + hysteresis);
// Check whether the position is near some edge(s).
if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
retval |= GROW_LEFT_EDGE;
}
if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
retval |= GROW_RIGHT_EDGE;
}
if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
retval |= GROW_TOP_EDGE;
}
if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
retval |= GROW_BOTTOM_EDGE;
}
// Not near any edge but inside the rectangle: move.
if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
retval = MOVE;
}
}
return retval;
}
// Handles motion (dx, dy) in screen space.
// The "edge" parameter specifies which edges the user is dragging.
/**
* Handle motion.
*
* @param edge the edge
* @param dx the dx
* @param dy the dy
*/
public void handleMotion(int edge, float dx, float dy) {
Rect r = computeLayout();
if (edge == GROW_NONE) {
return;
} else if (edge == MOVE) {
// Convert to image space before sending to moveBy().
moveBy(dx * (mCropRect.width() / r.width()), dy * (mCropRect.height() / r.height()));
} else {
if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
dx = 0;
}
if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
dy = 0;
}
// Convert to image space before sending to growBy().
float xDelta = dx * (mCropRect.width() / r.width());
float yDelta = dy * (mCropRect.height() / r.height());
growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta, (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
}
}
// Grows the cropping rectange by (dx, dy) in image space.
/**
* Move by.
*
* @param dx the dx
* @param dy the dy
*/
void moveBy(float dx, float dy) {
Rect invalRect = new Rect(mDrawRect);
mCropRect.offset(dx, dy);
// Put the cropping rectangle inside image rectangle.
mCropRect.offset(Math.max(0, mImageRect.left - mCropRect.left), Math.max(0, mImageRect.top - mCropRect.top));
mCropRect.offset(Math.min(0, mImageRect.right - mCropRect.right), Math.min(0, mImageRect.bottom - mCropRect.bottom));
mDrawRect = computeLayout();
invalRect.union(mDrawRect);
invalRect.inset(-10, -10);
// mContext.invalidate(invalRect);
mContext.invalidate();
}
// Grows the cropping rectange by (dx, dy) in image space.
/**
* Grow by.
*
* @param dx the dx
* @param dy the dy
*/
void growBy(float dx, float dy) {
if (mMaintainAspectRatio) {
if (dx != 0) {
dy = dx / mInitialAspectRatio;
} else if (dy != 0) {
dx = dy * mInitialAspectRatio;
}
}
// Don't let the cropping rectangle grow too fast.
// Grow at most half of the difference between the image rectangle and
// the cropping rectangle.
RectF r = new RectF(mCropRect);
if (dx > 0F && r.width() + 2 * dx > mImageRect.width()) {
float adjustment = (mImageRect.width() - r.width()) / 2F;
dx = adjustment;
if (mMaintainAspectRatio) {
dy = dx / mInitialAspectRatio;
}
}
if (dy > 0F && r.height() + 2 * dy > mImageRect.height()) {
float adjustment = (mImageRect.height() - r.height()) / 2F;
dy = adjustment;
if (mMaintainAspectRatio) {
dx = dy * mInitialAspectRatio;
}
}
r.inset(-dx, -dy);
// Don't let the cropping rectangle shrink too fast.
final float widthCap = 25F;
if (r.width() < widthCap) {
return;
// r.inset(-(widthCap - r.width()) / 2F, 0F);
}
float heightCap = mMaintainAspectRatio ? (widthCap / mInitialAspectRatio) : widthCap;
if (r.height() < heightCap) {
return;
// r.inset(0F, -(heightCap - r.height()) / 2F);
}
// Put the cropping rectangle inside the image rectangle.
if (r.left < mImageRect.left) {
r.offset(mImageRect.left - r.left, 0F);
} else if (r.right > mImageRect.right) {
r.offset(-(r.right - mImageRect.right), 0);
}
if (r.top < mImageRect.top) {
r.offset(0F, mImageRect.top - r.top);
} else if (r.bottom > mImageRect.bottom) {
r.offset(0F, -(r.bottom - mImageRect.bottom));
}
mCropRect.set(r);
mDrawRect = computeLayout();
mContext.invalidate();
}
// Returns the cropping rectangle in image space.
/**
* Gets the crop rect.
*
* @return the crop rect
*/
public Rect getCropRect() {
return new Rect((int) mCropRect.left, (int) mCropRect.top, (int) mCropRect.right, (int) mCropRect.bottom);
}
// Maps the cropping rectangle from image space to screen space.
/**
* Compute layout.
*
* @return the rect
*/
private Rect computeLayout() {
RectF r = new RectF(mCropRect.left, mCropRect.top, mCropRect.right, mCropRect.bottom);
mMatrix.mapRect(r);
return new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), Math.round(r.bottom));
}
/**
* Invalidate.
*/
public void invalidate() {
mDrawRect = computeLayout();
}
/**
* Setup.
*
* @param m the m
* @param imageRect the image rect
* @param cropRect the crop rect
* @param circle the circle
* @param maintainAspectRatio the maintain aspect ratio
*/
public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean circle, boolean maintainAspectRatio) {
if (circle) {
maintainAspectRatio = true;
}
mMatrix = new Matrix(m);
mCropRect = cropRect;
mImageRect = new RectF(imageRect);
mMaintainAspectRatio = maintainAspectRatio;
mCircle = circle;
mInitialAspectRatio = mCropRect.width() / mCropRect.height();
mDrawRect = computeLayout();
mFocusPaint.setARGB(125, 50, 50, 50);
mNoFocusPaint.setARGB(125, 50, 50, 50);
mOutlinePaint.setStrokeWidth(3F);
mOutlinePaint.setStyle(Paint.Style.STROKE);
mOutlinePaint.setAntiAlias(true);
mMode = ModifyMode.None;
init();
}
/**
* The Enum ModifyMode.
*/
public enum ModifyMode {
/** The None. */
None,
/** The Move. */
Move,
/** The Grow. */
Grow
}
/** The m mode. */
private ModifyMode mMode = ModifyMode.None;
/** The m draw rect. */
public Rect mDrawRect; // in screen space
/** The m image rect. */
public RectF mImageRect; // in image space
/** The m crop rect. */
public RectF mCropRect; // in image space
/** The m matrix. */
public Matrix mMatrix;
/** The m maintain aspect ratio. */
private boolean mMaintainAspectRatio = false;
/** The m initial aspect ratio. */
private float mInitialAspectRatio;
/** The m circle. */
private boolean mCircle = false;
// private Drawable mResizeDrawableWidth;
// private Drawable mResizeDrawableHeight;
/** The m resize drawable diagonal. */
private Drawable mResizeDrawableDiagonal;
/** The m resize drawable diagonal2. */
private Drawable mResizeDrawableDiagonal2;
/** The m focus paint. */
private final Paint mFocusPaint = new Paint();
/** The m no focus paint. */
private final Paint mNoFocusPaint = new Paint();
/** The m outline paint. */
private final Paint mOutlinePaint = new Paint();
}