/* * 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.gallery3d.filtershow.crop; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.graphics.drawable.NinePatchDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import com.android.gallery3d.R; import com.android.gallery3d.filtershow.crop.CropDrawingUtils; public class CropView extends View { private static final String LOGTAG = "CropView"; private RectF mImageBounds = new RectF(); private RectF mScreenBounds = new RectF(); private RectF mScreenImageBounds = new RectF(); private RectF mScreenCropBounds = new RectF(); private Rect mShadowBounds = new Rect(); private Bitmap mBitmap; private Paint mPaint = new Paint(); private NinePatchDrawable mShadow; private CropObject mCropObj = null; private final Drawable mCropIndicator; private final int mIndicatorSize; private int mRotation = 0; private boolean mMovingBlock = false; private Matrix mDisplayMatrix = null; private Matrix mDisplayMatrixInverse = null; private boolean mDirty = false; private float mPrevX = 0; private float mPrevY = 0; private int mShadowMargin = 15; private int mMargin = 32; private int mOverlayShadowColor = 0xCF000000; private int mMinSideSize = 90; private int mTouchTolerance = 40; private enum Mode { NONE, MOVE } private Mode mState = Mode.NONE; public CropView(Context context, AttributeSet attrs) { super(context, attrs); Resources rsc = context.getResources(); mShadow = (NinePatchDrawable) rsc.getDrawable(R.drawable.geometry_shadow); mCropIndicator = rsc.getDrawable(R.drawable.camera_crop); mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size); mShadowMargin = (int) rsc.getDimension(R.dimen.shadow_margin); mMargin = (int) rsc.getDimension(R.dimen.preview_margin); mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side); mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance); mOverlayShadowColor = (int) rsc.getColor(R.color.crop_shadow_color); } public void initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation) { mBitmap = image; if (mCropObj != null) { RectF crop = mCropObj.getInnerBounds(); RectF containing = mCropObj.getOuterBounds(); if (crop != newCropBounds || containing != newPhotoBounds || mRotation != rotation) { mRotation = rotation; mCropObj.resetBoundsTo(newCropBounds, newPhotoBounds); clearDisplay(); } } else { mRotation = rotation; mCropObj = new CropObject(newPhotoBounds, newCropBounds, 0); clearDisplay(); } } public RectF getCrop() { return mCropObj.getInnerBounds(); } public RectF getPhoto() { return mCropObj.getOuterBounds(); } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); if (mDisplayMatrix == null || mDisplayMatrixInverse == null) { return true; } float[] touchPoint = { x, y }; mDisplayMatrixInverse.mapPoints(touchPoint); x = touchPoint[0]; y = touchPoint[1]; switch (event.getActionMasked()) { case (MotionEvent.ACTION_DOWN): if (mState == Mode.NONE) { if (!mCropObj.selectEdge(x, y)) { mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK); } mPrevX = x; mPrevY = y; mState = Mode.MOVE; } break; case (MotionEvent.ACTION_UP): if (mState == Mode.MOVE) { mCropObj.selectEdge(CropObject.MOVE_NONE); mMovingBlock = false; mPrevX = x; mPrevY = y; mState = Mode.NONE; } break; case (MotionEvent.ACTION_MOVE): if (mState == Mode.MOVE) { float dx = x - mPrevX; float dy = y - mPrevY; mCropObj.moveCurrentSelection(dx, dy); mPrevX = x; mPrevY = y; } break; default: break; } invalidate(); return true; } private void reset() { Log.w(LOGTAG, "crop reset called"); mState = Mode.NONE; mCropObj = null; mRotation = 0; mMovingBlock = false; clearDisplay(); } private void clearDisplay() { mDisplayMatrix = null; mDisplayMatrixInverse = null; invalidate(); } protected void configChanged() { mDirty = true; } public void applyFreeAspect() { mCropObj.unsetAspectRatio(); invalidate(); } public void applyOriginalAspect() { RectF outer = mCropObj.getOuterBounds(); float w = outer.width(); float h = outer.height(); if (w > 0 && h > 0) { applyAspect(w, h); mCropObj.resetBoundsTo(outer, outer); } else { Log.w(LOGTAG, "failed to set aspect ratio original"); } } public void applySquareAspect() { applyAspect(1, 1); } public void applyAspect(float x, float y) { if (x <= 0 || y <= 0) { throw new IllegalArgumentException("Bad arguments to applyAspect"); } if (!mCropObj.setInnerAspectRatio(x, y)) { Log.w(LOGTAG, "failed to set aspect ratio"); } invalidate(); } @Override public void onDraw(Canvas canvas) { if (mBitmap == null) { return; } if (mDirty) { mDirty = false; clearDisplay(); } mImageBounds = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); mScreenBounds = new RectF(0, 0, canvas.getWidth(), canvas.getHeight()); mScreenBounds.inset(mMargin, mMargin); // If crop object doesn't exist, create it and update it from master // state if (mCropObj == null) { reset(); mCropObj = new CropObject(mImageBounds, mImageBounds, 0); } // If display matrix doesn't exist, create it and its dependencies if (mDisplayMatrix == null || mDisplayMatrixInverse == null) { mDisplayMatrix = new Matrix(); mDisplayMatrix.reset(); if (!CropDrawingUtils.setImageToScreenMatrix(mDisplayMatrix, mImageBounds, mScreenBounds, mRotation)) { Log.w(LOGTAG, "failed to get screen matrix"); mDisplayMatrix = null; return; } mDisplayMatrixInverse = new Matrix(); mDisplayMatrixInverse.reset(); if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) { Log.w(LOGTAG, "could not invert display matrix"); mDisplayMatrixInverse = null; return; } // Scale min side and tolerance by display matrix scale factor mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize)); mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance)); } mScreenImageBounds.set(mImageBounds); // Draw background shadow if (mDisplayMatrix.mapRect(mScreenImageBounds)) { int margin = (int) mDisplayMatrix.mapRadius(mShadowMargin); mScreenImageBounds.roundOut(mShadowBounds); mShadowBounds.set(mShadowBounds.left - margin, mShadowBounds.top - margin, mShadowBounds.right + margin, mShadowBounds.bottom + margin); mShadow.setBounds(mShadowBounds); mShadow.draw(canvas); } // Draw actual bitmap canvas.drawBitmap(mBitmap, mDisplayMatrix, mPaint); mCropObj.getInnerBounds(mScreenCropBounds); if (mDisplayMatrix.mapRect(mScreenCropBounds)) { // Draw overlay shadows Paint p = new Paint(); p.setColor(mOverlayShadowColor); p.setStyle(Paint.Style.FILL); CropDrawingUtils.drawShadows(canvas, p, mScreenCropBounds, mScreenImageBounds); // Draw crop rect and markers CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds); CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds); CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, mScreenCropBounds, mCropObj.isFixedAspect(), mCropObj.getSelectState()); } } }