package com.aviary.android.feather.widget; import it.sephiroth.android.library.imagezoom.easing.Easing; import it.sephiroth.android.library.imagezoom.easing.Quad; import android.graphics.BlurMaskFilter; import android.graphics.BlurMaskFilter.Blur; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Handler; import android.view.View; import com.aviary.android.feather.R; import com.aviary.android.feather.library.utils.ReflectionUtils; import com.aviary.android.feather.library.utils.ReflectionUtils.ReflectionException; // TODO: Auto-generated Javadoc /** * The Class HighlightView. */ public class HighlightView { /** The Constant LOG_TAG. */ @SuppressWarnings("unused") private static final String LOG_TAG = "hv"; /** The Constant GROW_NONE. */ static final int GROW_NONE = 1 << 0; /** The Constant GROW_LEFT_EDGE. */ static final int GROW_LEFT_EDGE = 1 << 1; /** The Constant GROW_RIGHT_EDGE. */ static final int GROW_RIGHT_EDGE = 1 << 2; /** The Constant GROW_TOP_EDGE. */ static final int GROW_TOP_EDGE = 1 << 3; /** The Constant GROW_BOTTOM_EDGE. */ static final int GROW_BOTTOM_EDGE = 1 << 4; /** The Constant MOVE. */ static final int MOVE = 1 << 5; /** The m hidden. */ private boolean mHidden; /** The m context. */ private View mContext; private static Handler mHandler = new Handler(); /** * The Enum Mode. */ enum Mode { /** The None. */ None, /** The Move. */ Move, /** The Grow. */ Grow } /** The m min size. */ private int mMinSize = 20; /** The m mode. */ private Mode mMode; /** The draw rect. */ private Rect mDrawRect = new Rect(); /** The image rect. */ private RectF mImageRect; /** The crop rect. */ private RectF mCropRect; private Matrix mMatrix; /** The m maintain aspect ratio. */ private boolean mMaintainAspectRatio = false; /** The m initial aspect ratio. */ private double mInitialAspectRatio; /** The handle knob drawable. */ private Drawable mResizeDrawable; private final Paint mOutlinePaint = new Paint(); private final Paint mOutlinePaint2 = new Paint(); private final Paint mOutlineFill = new Paint(); private Paint mLinesPaintShadow = new Paint(); /** The highlight_color. */ private int highlight_color; /** The highlight_down_color. */ private int highlight_down_color; /** The highlight_outside_color. */ private int highlight_outside_color; /** The highlight_outside_color_down. */ private int highlight_outside_color_down; /** The internal_stroke_width. */ private int stroke_width, internal_stroke_width; /** internal grid colors */ private int highlight_internal_color, highlight_internal_color_down; /** The d height. */ private int dWidth, dHeight; final int grid_rows = 3; final int grid_cols = 3; /** * Instantiates a new highlight view. * * @param ctx * the ctx */ public HighlightView( View ctx ) { mContext = ctx; highlight_color = mContext.getResources().getColor( R.color.feather_crop_highlight ); highlight_down_color = mContext.getResources().getColor( R.color.feather_crop_highlight_down ); highlight_outside_color = mContext.getResources().getColor( R.color.feather_crop_highlight_outside ); highlight_outside_color_down = mContext.getResources().getColor( R.color.feather_crop_highlight_outside_down ); stroke_width = mContext.getResources().getInteger( R.integer.feather_crop_highlight_stroke_width ); internal_stroke_width = mContext.getResources().getInteger( R.integer.feather_crop_highlight_internal_stroke_width ); highlight_internal_color = mContext.getResources().getColor( R.color.feather_crop_highlight_internal ); highlight_internal_color_down = mContext.getResources().getColor( R.color.feather_crop_highlight_internal_down ); } /** * Inits the. */ private void init() { android.content.res.Resources resources = mContext.getResources(); mResizeDrawable = resources.getDrawable( R.drawable.feather_highlight_crop_handle ); double w = mResizeDrawable.getIntrinsicWidth(); double h = mResizeDrawable.getIntrinsicHeight(); dWidth = (int) Math.ceil( w / 2.0 ); dHeight = (int) Math.ceil( h / 2.0 ); } /** * Dispose. */ public void dispose() { mContext = null; } /** * Sets the min size. * * @param value * the new min size */ public void setMinSize( int value ) { mMinSize = value; } /** * Sets the hidden. * * @param hidden * the new hidden */ public void setHidden( boolean hidden ) { mHidden = hidden; } /** The m view drawing rect. */ private Rect mViewDrawingRect = new Rect(); /** The m path. */ private Path mPath = new Path(); /** The m lines path. */ private Path mLinesPath = new Path(); /** The m inverse path. */ private Path mInversePath = new Path(); private RectF tmpRect2 = new RectF(); private Rect tmpRect4 = new Rect(); private RectF tmpDrawRect2F = new RectF(); private RectF tmpDrawRectF = new RectF(); private RectF tmpDisplayRectF = new RectF(); private Rect tmpRectMotion = new Rect(); private RectF tmpRectMotionF = new RectF(); private RectF tempLayoutRectF = new RectF(); /** * Draw. * * @param canvas * the canvas */ protected void draw( Canvas canvas ) { if ( mHidden ) return; // canvas.save(); mPath.reset(); mInversePath.reset(); mLinesPath.reset(); mContext.getDrawingRect( mViewDrawingRect ); tmpDrawRectF.set( mDrawRect ); tmpDrawRect2F.set( mViewDrawingRect ); mInversePath.addRect( tmpDrawRect2F, Path.Direction.CW ); mInversePath.addRect( tmpDrawRectF, Path.Direction.CCW ); tmpDrawRectF.set( mDrawRect ); mPath.addRect( tmpDrawRectF, Path.Direction.CW ); tmpDrawRect2F.set( mDrawRect ); mLinesPath.addRect( tmpDrawRect2F, Path.Direction.CW ); float colStep = (float) mDrawRect.height() / grid_cols; float rowStep = (float) mDrawRect.width() / grid_rows; for ( int i = 1; i < grid_cols; i++ ) { mLinesPath.moveTo( (int) mDrawRect.left, (int) ( mDrawRect.top + colStep * i ) ); mLinesPath.lineTo( (int) mDrawRect.right, (int) ( mDrawRect.top + colStep * i ) ); } for ( int i = 1; i < grid_rows; i++ ) { mLinesPath.moveTo( (int) ( mDrawRect.left + rowStep * i ), (int) mDrawRect.top ); mLinesPath.lineTo( (int) ( mDrawRect.left + rowStep * i ), (int) mDrawRect.bottom ); } // canvas.restore(); canvas.drawPath( mInversePath, mOutlineFill ); // canvas.drawPath( mLinesPath, mLinesPaintShadow ); canvas.drawPath( mLinesPath, mOutlinePaint2 ); canvas.drawPath( mPath, mOutlinePaint ); if ( true /* || mMode == Mode.Grow */) { int left = mDrawRect.left + 1; int right = mDrawRect.right + 1; int top = mDrawRect.top + 4; int bottom = mDrawRect.bottom + 3; if ( mResizeDrawable != null ) { mResizeDrawable.setBounds( left - dWidth, top - dHeight, left + dWidth, top + dHeight ); mResizeDrawable.draw( canvas ); mResizeDrawable.setBounds( right - dWidth, top - dHeight, right + dWidth, top + dHeight ); mResizeDrawable.draw( canvas ); mResizeDrawable.setBounds( left - dWidth, bottom - dHeight, left + dWidth, bottom + dHeight ); mResizeDrawable.draw( canvas ); mResizeDrawable.setBounds( right - dWidth, bottom - dHeight, right + dWidth, bottom + dHeight ); mResizeDrawable.draw( canvas ); } } } /** * Sets the mode. * * @param mode * the new mode */ public void setMode( Mode mode ) { if ( mode != mMode ) { mMode = mode; mOutlinePaint.setColor( mMode == Mode.None ? highlight_color : highlight_down_color ); mOutlinePaint2.setColor( mMode == Mode.None ? highlight_internal_color : highlight_internal_color_down ); mLinesPaintShadow.setAlpha( mMode == Mode.None ? 102 : 0 ); mOutlineFill.setColor( mMode == Mode.None ? highlight_outside_color : highlight_outside_color_down ); mContext.invalidate(); } } /** The hysteresis. */ final float hysteresis = 30F; /** * Gets the hit. * * @param x * the x * @param y * the y * @return the hit */ public int getHit( float x, float y ) { Rect r = new Rect(); computeLayout( false, r ); int retval = GROW_NONE; boolean verticalCheck = ( y >= r.top - hysteresis ) && ( y < r.bottom + hysteresis ); boolean horizCheck = ( x >= r.left - hysteresis ) && ( x < r.right + hysteresis ); 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; if ( retval == GROW_NONE && r.contains( (int) x, (int) y ) ) retval = MOVE; return retval; } boolean isLeftEdge( int edge ) { return ( GROW_LEFT_EDGE & edge ) == GROW_LEFT_EDGE; } boolean isRightEdge( int edge ) { return ( GROW_RIGHT_EDGE & edge ) == GROW_RIGHT_EDGE; } boolean isTopEdge( int edge ) { return ( GROW_TOP_EDGE & edge ) == GROW_TOP_EDGE; } boolean isBottomEdge( int edge ) { return ( GROW_BOTTOM_EDGE & edge ) == GROW_BOTTOM_EDGE; } /** * Handle motion. * * @param edge * the edge * @param dx * the dx * @param dy * the dy */ void handleMotion( int edge, float dx, float dy ) { if ( mRunning ) return; computeLayout( false, tmpRect4 ); if ( edge == GROW_NONE ) { return; } else if ( edge == MOVE ) { moveBy( dx * ( mCropRect.width() / tmpRect4.width() ), dy * ( mCropRect.height() / tmpRect4.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(). double xDelta = Math.round( dx * ( mCropRect.width() / tmpRect4.width() ) ); double yDelta = Math.round( dy * ( mCropRect.height() / tmpRect4.height() ) ); if ( mMaintainAspectRatio ) { growWithConstantAspectSize( edge, xDelta, yDelta ); } else { growWithoutConstantAspectSize( edge, xDelta, yDelta ); } } } double calculateDy( double dx, double dy ) { double ndy = dy; if ( dx != 0 ) { ndy = ( dx / mInitialAspectRatio ); if ( dy != 0 ) { if ( dy > 0 ) { ndy = Math.abs( ndy ); } else { ndy = Math.abs( ndy ) * -1; } } dy = ndy; } return ndy; } double calculateDx( double dy, double dx ) { double ndx = dx; if ( dy != 0 ) { ndx = ( dy * mInitialAspectRatio ); if ( dx != 0 ) { if ( dx > 0 ) { ndx = Math.abs( ndx ); } else { ndx = Math.abs( ndx ) * -1; } } dx = ndx; } return ndx; } /** * Grow only one handle * * @param dx * @param dy */ void growWithConstantAspectSize( int edge, double dx, double dy ) { final boolean left = isLeftEdge( edge ); final boolean right = isRightEdge( edge ); final boolean top = isTopEdge( edge ); final boolean bottom = isBottomEdge( edge ); final boolean horizontal = left || right; final boolean vertical = top || bottom; final boolean singleSide = !( horizontal && vertical ); // check minimum size and outset the rectangle as needed final double widthCap = (double) mMinSize / getScale(); double ndx, ndy; tmpRectMotionF.set( mCropRect ); if ( singleSide ) { if ( horizontal ) { // horizontal only ndx = dx; ndy = calculateDy( ndx, 0 ); if ( left ) { tmpRectMotionF.left += ndx; tmpRectMotionF.inset( 0, (float) ( ndy / 2 ) ); } else { tmpRectMotionF.right += ndx; tmpRectMotionF.inset( 0, (float) ( -ndy / 2 ) ); } } else { // vertical only ndy = dy; ndx = calculateDx( ndy, 0 ); if ( top ) { tmpRectMotionF.top += ndy; tmpRectMotionF.inset( (float) ( ndx / 2 ), 0 ); } else if ( bottom ) { tmpRectMotionF.bottom += ndy; tmpRectMotionF.inset( (float) ( -ndx / 2 ), 0 ); } } } else { // both horizontal & vertical ndx = dx; ndy = calculateDy( dx, 0 ); if ( left && top ) { tmpRectMotionF.left += ndx; tmpRectMotionF.top += ndy; } else if ( left && bottom ) { tmpRectMotionF.left += ndx; tmpRectMotionF.bottom -= ndy; } else if ( right && top ) { tmpRectMotionF.right += ndx; tmpRectMotionF.top -= ndy; } else if ( right && bottom ) { tmpRectMotionF.right += ndx; tmpRectMotionF.bottom += ndy; } } if ( tmpRectMotionF.width() >= widthCap && tmpRectMotionF.height() >= widthCap && mImageRect.contains( tmpRectMotionF ) ) { mCropRect.set( tmpRectMotionF ); } computeLayout( true, mDrawRect ); mContext.invalidate(); } /** * Grow only one handle * * @param dx * @param dy */ void growWithoutConstantAspectSize( int edge, double dx, double dy ) { final boolean left = isLeftEdge( edge ); final boolean right = isRightEdge( edge ); final boolean top = isTopEdge( edge ); final boolean bottom = isBottomEdge( edge ); final boolean horizontal = left || right; final boolean vertical = top || bottom; // check minimum size and outset the rectangle as needed final double widthCap = (double) mMinSize / getScale(); tmpRectMotionF.set( mCropRect ); double ndy = dy; double ndx = dx; if ( horizontal ) { if ( left ) { tmpRectMotionF.left += ndx; if ( !vertical ) tmpRectMotionF.inset( 0, (float) ( ndy / 2 ) ); } else if ( right ) { tmpRectMotionF.right += ndx; if ( !vertical ) tmpRectMotionF.inset( 0, (float) ( -ndy / 2 ) ); } } if ( vertical ) { if ( top ) { tmpRectMotionF.top += ndy; if ( !horizontal ) tmpRectMotionF.inset( (float) ( ndx / 2 ), 0 ); } else if ( bottom ) { tmpRectMotionF.bottom += ndy; if ( !horizontal ) tmpRectMotionF.inset( (float) ( -ndx / 2 ), 0 ); } } if ( tmpRectMotionF.width() >= widthCap && tmpRectMotionF.height() >= widthCap && mImageRect.contains( tmpRectMotionF ) ) { mCropRect.set( tmpRectMotionF ); } computeLayout( true, mDrawRect ); mContext.invalidate(); } /** * Move by. * * @param dx * the dx * @param dy * the dy */ void moveBy( float dx, float dy ) { tmpRectMotion.set( mDrawRect ); mCropRect.offset( dx, dy ); 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 ) ); computeLayout( false, mDrawRect ); tmpRectMotion.union( mDrawRect ); tmpRectMotion.inset( -dWidth * 2, -dHeight * 2 ); mContext.invalidate( tmpRectMotion ); } /** * Gets the scale. * * @return the scale */ protected float getScale() { float values[] = new float[9]; mMatrix.getValues( values ); return values[Matrix.MSCALE_X]; } /** * @deprecated Old grow behavior */ void growBy( double dx, double dy ) { if ( mMaintainAspectRatio ) { if ( dx != 0 ) { dy = dx / mInitialAspectRatio; } else if ( dy != 0 ) { dx = dy * mInitialAspectRatio; } } tmpRectMotionF.set( mCropRect ); if ( dx > 0F && tmpRectMotionF.width() + 2 * dx > mImageRect.width() ) { float adjustment = ( mImageRect.width() - tmpRectMotionF.width() ) / 2F; dx = adjustment; if ( mMaintainAspectRatio ) { dy = dx / mInitialAspectRatio; } } if ( dy > 0F && tmpRectMotionF.height() + 2 * dy > mImageRect.height() ) { float adjustment = ( mImageRect.height() - tmpRectMotionF.height() ) / 2F; dy = adjustment; if ( mMaintainAspectRatio ) { dx = dy * mInitialAspectRatio; } } tmpRectMotionF.inset( (float) -dx, (float) -dy ); // check minimum size and outset the rectangle as needed final double widthCap = (double) mMinSize / getScale(); if ( tmpRectMotionF.width() < widthCap ) { tmpRectMotionF.inset( (float) ( -( widthCap - tmpRectMotionF.width() ) / 2F ), 0F ); } double heightCap = mMaintainAspectRatio ? ( widthCap / mInitialAspectRatio ) : widthCap; if ( tmpRectMotionF.height() < heightCap ) { tmpRectMotionF.inset( 0F, (float) ( -( heightCap - tmpRectMotionF.height() ) / 2F ) ); } mCropRect.set( tmpRectMotionF ); computeLayout( true, mDrawRect ); mContext.invalidate(); } /** * Adjust crop rect based on the current image. * * @param r * - The {@link Rect} to be adjusted */ private void adjustCropRect( RectF r ) { // Log.i( LOG_TAG, "adjustCropRect: " + r + ", mImageRect: " + mImageRect ); 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 ) ); } double diffx = -1, diffy = -1; if ( r.width() > mImageRect.width() ) { if ( r.left < mImageRect.left ) { diffx = mImageRect.left - r.left; r.left += diffx; } else if ( r.right > mImageRect.right ) { diffx = ( r.right - mImageRect.right ); r.right += -diffx; } } else if ( r.height() > mImageRect.height() ) { if ( r.top < mImageRect.top ) { // top diffy = mImageRect.top - r.top; r.top += diffy; } else if ( r.bottom > mImageRect.bottom ) { // bottom diffy = ( r.bottom - mImageRect.bottom ); r.bottom += -diffy; } } if ( mMaintainAspectRatio ) { // Log.d( LOG_TAG, "diffx: " + diffx + ", diffy: " + diffy ); if ( diffy != -1 ) { diffx = diffy * mInitialAspectRatio; r.inset( (float) ( diffx / 2.0 ), 0 ); } else if ( diffx != -1 ) { diffy = diffx / mInitialAspectRatio; r.inset( 0, (float) ( diffy / 2.0 ) ); } } r.sort(); } /** * Adjust real crop rect. * * @param matrix * the matrix * @param rect * the rect * @param outsideRect * the outside rect * @return the rect f */ private RectF adjustRealCropRect( Matrix matrix, RectF rect, RectF outsideRect ) { // Log.i( LOG_TAG, "adjustRealCropRect" ); boolean adjusted = false; tempLayoutRectF.set( rect ); matrix.mapRect( tempLayoutRectF ); float[] mvalues = new float[9]; matrix.getValues( mvalues ); final float scale = mvalues[Matrix.MSCALE_X]; if ( tempLayoutRectF.left < outsideRect.left ) { adjusted = true; rect.offset( ( outsideRect.left - tempLayoutRectF.left ) / scale, 0 ); } else if ( tempLayoutRectF.right > outsideRect.right ) { adjusted = true; rect.offset( -( tempLayoutRectF.right - outsideRect.right ) / scale, 0 ); } if ( tempLayoutRectF.top < outsideRect.top ) { adjusted = true; rect.offset( 0, ( outsideRect.top - tempLayoutRectF.top ) / scale ); } else if ( tempLayoutRectF.bottom > outsideRect.bottom ) { adjusted = true; rect.offset( 0, -( tempLayoutRectF.bottom - outsideRect.bottom ) / scale ); } tempLayoutRectF.set( rect ); matrix.mapRect( tempLayoutRectF ); if ( tempLayoutRectF.width() > outsideRect.width() ) { adjusted = true; if ( tempLayoutRectF.left < outsideRect.left ) rect.left += ( outsideRect.left - tempLayoutRectF.left ) / scale; if ( tempLayoutRectF.right > outsideRect.right ) rect.right += -( tempLayoutRectF.right - outsideRect.right ) / scale; } if ( tempLayoutRectF.height() > outsideRect.height() ) { adjusted = true; if ( tempLayoutRectF.top < outsideRect.top ) rect.top += ( outsideRect.top - tempLayoutRectF.top ) / scale; if ( tempLayoutRectF.bottom > outsideRect.bottom ) rect.bottom += -( tempLayoutRectF.bottom - outsideRect.bottom ) / scale; } if ( mMaintainAspectRatio && adjusted ) { if ( mInitialAspectRatio >= 1 ) { // width > height final double dy = rect.width() / mInitialAspectRatio; rect.bottom = (float) ( rect.top + dy ); } else { // height >= width final double dx = rect.height() * mInitialAspectRatio; rect.right = (float) ( rect.left + dx ); } } rect.sort(); return rect; } /** * Compute and adjust the current crop layout * * @param adjust * - If true tries to adjust the crop rect * @param outRect * - The result will be stored in this {@link Rect} */ public void computeLayout( boolean adjust, Rect outRect ) { if ( adjust ) { adjustCropRect( mCropRect ); tmpRect2.set( 0, 0, mContext.getWidth(), mContext.getHeight() ); mCropRect = adjustRealCropRect( mMatrix, mCropRect, tmpRect2 ); } getDisplayRect( mMatrix, mCropRect, outRect ); } public void getDisplayRect( Matrix m, RectF supportRect, Rect outRect ) { tmpDisplayRectF.set( supportRect.left, supportRect.top, supportRect.right, supportRect.bottom ); m.mapRect( tmpDisplayRectF ); outRect.set( Math.round( tmpDisplayRectF.left ), Math.round( tmpDisplayRectF.top ), Math.round( tmpDisplayRectF.right ), Math.round( tmpDisplayRectF.bottom ) ); } /** * Invalidate. */ public void invalidate() { if ( !mRunning ) { computeLayout( true, mDrawRect ); } } /** true while the view is animating */ protected volatile boolean mRunning = false; /** animation duration in ms */ protected int animationDurationMs = 300; /** {@link Easing} used to animate the view */ protected Easing mEasing = new Quad(); /** * Return true if the view is currently running * * @return */ public boolean isRunning() { return mRunning; } public void animateTo( Matrix m, Rect imageRect, RectF cropRect, final boolean maintainAspectRatio ) { if ( !mRunning ) { mRunning = true; setMode( Mode.None ); mMatrix = new Matrix( m ); mCropRect = cropRect; mImageRect = new RectF( imageRect ); mMaintainAspectRatio = false; double ratio = (double) mCropRect.width() / (double) mCropRect.height(); mInitialAspectRatio = Math.round( ratio * 1000.0 ) / 1000.0; // Log.i( LOG_TAG, "aspect ratio: " + mInitialAspectRatio ); final Rect oldRect = mDrawRect; final Rect newRect = new Rect(); computeLayout( false, newRect ); final float[] topLeft = { oldRect.left, oldRect.top }; final float[] bottomRight = { oldRect.right, oldRect.bottom }; final double pt1 = newRect.left - oldRect.left; final double pt2 = newRect.right - oldRect.right; final double pt3 = newRect.top - oldRect.top; final double pt4 = newRect.bottom - oldRect.bottom; final long startTime = System.currentTimeMillis(); mHandler.post( new Runnable() { @Override public void run() { if ( mContext == null ) return; long now = System.currentTimeMillis(); double currentMs = Math.min( animationDurationMs, now - startTime ); double value1 = mEasing.easeOut( currentMs, 0, pt1, animationDurationMs ); double value2 = mEasing.easeOut( currentMs, 0, pt2, animationDurationMs ); double value3 = mEasing.easeOut( currentMs, 0, pt3, animationDurationMs ); double value4 = mEasing.easeOut( currentMs, 0, pt4, animationDurationMs ); mDrawRect.left = (int) ( topLeft[0] + value1 ); mDrawRect.right = (int) ( bottomRight[0] + value2 ); mDrawRect.top = (int) ( topLeft[1] + value3 ); mDrawRect.bottom = (int) ( bottomRight[1] + value4 ); mContext.invalidate(); if ( currentMs < animationDurationMs ) { mHandler.post( this ); } else { mMaintainAspectRatio = maintainAspectRatio; mRunning = false; invalidate(); mContext.invalidate(); } } } ); } } /** * Setup. * * @param m * the m * @param imageRect * the image rect * @param cropRect * the crop rect * @param maintainAspectRatio * the maintain aspect ratio */ public void setup( Matrix m, Rect imageRect, RectF cropRect, boolean maintainAspectRatio ) { mMatrix = new Matrix( m ); mCropRect = cropRect; mImageRect = new RectF( imageRect ); mMaintainAspectRatio = maintainAspectRatio; double ratio = (double) mCropRect.width() / (double) mCropRect.height(); mInitialAspectRatio = Math.round( ratio * 1000.0 ) / 1000.0; // Log.i( LOG_TAG, "aspect ratio: " + mInitialAspectRatio ); computeLayout( true, mDrawRect ); mOutlinePaint.setStrokeWidth( stroke_width ); mOutlinePaint.setStyle( Paint.Style.STROKE ); mOutlinePaint.setAntiAlias( false ); try { ReflectionUtils.invokeMethod( mOutlinePaint, "setHinting", new Class<?>[] { int.class }, 0 ); } catch ( ReflectionException e ) {} mOutlinePaint2.setStrokeWidth( internal_stroke_width ); mOutlinePaint2.setStyle( Paint.Style.STROKE ); mOutlinePaint2.setAntiAlias( false ); mOutlinePaint2.setColor( highlight_internal_color ); try { ReflectionUtils.invokeMethod( mOutlinePaint2, "setHinting", new Class<?>[] { int.class }, 0 ); } catch ( ReflectionException e ) {} mOutlineFill.setColor( highlight_outside_color ); mOutlineFill.setStyle( Paint.Style.FILL ); mOutlineFill.setAntiAlias( false ); try { ReflectionUtils.invokeMethod( mOutlineFill, "setHinting", new Class<?>[] { int.class }, 0 ); } catch ( ReflectionException e ) {} mLinesPaintShadow.setStrokeWidth( internal_stroke_width ); mLinesPaintShadow.setAntiAlias( true ); mLinesPaintShadow.setColor( Color.BLACK ); mLinesPaintShadow.setStyle( Paint.Style.STROKE ); mLinesPaintShadow.setMaskFilter( new BlurMaskFilter( 2, Blur.NORMAL ) ); setMode( Mode.None ); init(); } /** * Sets the aspect ratio. * * @param value * the new aspect ratio */ public void setAspectRatio( float value ) { mInitialAspectRatio = value; } /** * Sets the maintain aspect ratio. * * @param value * the new maintain aspect ratio */ public void setMaintainAspectRatio( boolean value ) { mMaintainAspectRatio = value; } /** * Update. * * @param imageMatrix * the image matrix * @param imageRect * the image rect */ public void update( Matrix imageMatrix, Rect imageRect ) { mMatrix = new Matrix( imageMatrix ); mImageRect = new RectF( imageRect ); computeLayout( true, mDrawRect ); mContext.invalidate(); } /** * Gets the matrix. * * @return the matrix */ public Matrix getMatrix() { return mMatrix; } /** * Gets the draw rect. * * @return the draw rect */ public Rect getDrawRect() { return mDrawRect; } /** * Gets the crop rect f. * * @return the crop rect f */ public RectF getCropRectF() { return mCropRect; } /** * Returns the cropping rectangle in image space. * * @return the crop rect */ public Rect getCropRect() { return new Rect( (int) mCropRect.left, (int) mCropRect.top, (int) mCropRect.right, (int) mCropRect.bottom ); } }