/* * Copyright (C) 2007 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; 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.drawable.Drawable; import android.util.Config; import android.util.Log; import android.view.KeyEvent; import android.view.View; public class HighlightView { private static final String TAG = "CropImage"; View mContext; Path mPath; Rect mViewDrawingRect = new Rect(); int mMotionMode; public static final int GROW_NONE = (1 << 0); public static final int GROW_LEFT_EDGE = (1 << 1); public static final int GROW_RIGHT_EDGE = (1 << 2); public static final int GROW_TOP_EDGE = (1 << 3); public static final int GROW_BOTTOM_EDGE = (1 << 4); public static final int MOVE = (1 << 5); public HighlightView(View ctx) { super(); mContext = ctx; mPath = new Path(); } private void initHighlightView() { android.content.res.Resources resources = mContext.getResources(); mResizeDrawableWidth = resources.getDrawable(R.drawable.camera_crop_width); mResizeDrawableHeight = resources.getDrawable(R.drawable.camera_crop_height); mResizeDrawableDiagonal = resources.getDrawable(R.drawable.indicator_autocrop); } boolean mIsFocused; boolean mHidden; public boolean hasFocus() { return mIsFocused; } public void setFocus(boolean f) { mIsFocused = f; } public void setHidden(boolean hidden) { mHidden = hidden; } protected void draw(Canvas canvas) { if (mHidden) return; canvas.save(); mPath.reset(); if (!hasFocus()) { mOutlinePaint.setColor(0xFF000000); canvas.drawRect(mDrawRect, mOutlinePaint); } else { mContext.getDrawingRect(mViewDrawingRect); if (mCircle) { float width = mDrawRect.width() - (getPaddingLeft() + getPaddingRight()); float height = mDrawRect.height() - (getPaddingTop() + getPaddingBottom()); mPath.addCircle( mDrawRect.left + getPaddingLeft() + (width / 2), mDrawRect.top + getPaddingTop() + (height / 2), width / 2, Path.Direction.CW); mOutlinePaint.setColor(0xFFEF04D6); } else { mPath.addRect(new RectF(mDrawRect), Path.Direction.CW); mOutlinePaint.setColor(0xFFFF8A00); } canvas.clipPath(mPath, Region.Op.DIFFERENCE); canvas.drawRect(mViewDrawingRect, hasFocus() ? mFocusPaint : mNoFocusPaint); canvas.restore(); canvas.drawPath(mPath, 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); } } } } float getPaddingTop() { return 0F; } float getPaddingBottom() { return 0F; } float getPaddingLeft() { return 0F; } float getPaddingRight() { return 0F; } public ModifyMode getMode() { return mMode; } public void setMode(ModifyMode mode) { if (mode != mMode) { mMode = mode; mContext.invalidate(); } } 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 = (int) (mDrawRect.width() - getPaddingLeft()) / 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; } // Log.v(TAG, "radius: " + radius + "; touchRadius: " + distanceFromCenter + "; distX: " + distX + "; distY: " + distY + "; retval: " + retval); } else { 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; } void handleMotion(int edge, float dx, float dy) { Rect r = computeLayout(); if (edge == GROW_NONE) { return; } else if (edge == MOVE) { 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; 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); } // Log.v(TAG, "ratio is now " + this.mCropRect.width() / this.mCropRect.height()); } void moveBy(float dx, float dy) { Rect invalRect = new Rect(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)); mDrawRect = computeLayout(); invalRect.union(mDrawRect); invalRect.inset(-10, -10); mContext.invalidate(invalRect); } private void shift(RectF r, float dx, float dy) { r.left += dx; r.right += dx; r.top += dy; r.bottom += dy; } void growBy(float dx, float dy) { // Log.v(TAG, "growBy: " + dx + " " + dy + "; rect w/h is " + mCropRect.width() + " / " + mCropRect.height()); if (mMaintainAspectRatio) { if (dx != 0) { dy = dx / mInitialAspectRatio; } else if (dy != 0) { dx = dy * mInitialAspectRatio; } } 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); float widthCap = 25F; if (r.width() < 25) { r.inset(-(25F-r.width())/2F, 0F); } float heightCap = mMaintainAspectRatio ? (widthCap / mInitialAspectRatio) : widthCap; if (r.height() < heightCap) { r.inset(0F, -(heightCap-r.height())/2F); } if (r.left < mImageRect.left) { shift(r, mImageRect.left - r.left, 0F); } else if (r.right > mImageRect.right) { shift(r, -(r.right - mImageRect.right), 0); } if (r.top < mImageRect.top) { shift(r, 0F, mImageRect.top - r.top); } else if (r.bottom > mImageRect.bottom) { shift(r, 0F, -(r.bottom - mImageRect.bottom)); } /* RectF rCandidate = new RectF(r); r.intersect(mImageRect); if (mMaintainAspectRatio) { if (r.left != rCandidate.left) { Log.v(TAG, "bail 1"); return; } if (r.right != rCandidate.right) { Log.v(TAG, "bail 2"); return; } if (r.top != rCandidate.top) { Log.v(TAG, "bail 3"); return; } if (r.bottom != rCandidate.bottom) { Log.v(TAG, "bail 4"); return; } } */ mCropRect.set(r); mDrawRect = computeLayout(); mContext.invalidate(); } public Rect getCropRect() { return new Rect((int)mCropRect.left, (int)mCropRect.top, (int)mCropRect.right, (int)mCropRect.bottom); } 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)); } public void invalidate() { mDrawRect = computeLayout(); } public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean circle, boolean maintainAspectRatio) { if (Config.LOGV) Log.v(TAG, "setup... " + imageRect + "; " + cropRect + "; maintain " + maintainAspectRatio + "; circle " + circle); 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; initHighlightView(); } public void modify(int keyCode, long repeatCount) { float factor = Math.max(.01F, Math.min(.1F, repeatCount * .01F)); float widthUnits = factor * (float)mContext.getWidth(); float heightUnits = widthUnits; switch (keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: if (mMode == ModifyMode.Move) moveBy(-widthUnits, 0); else if (mMode == ModifyMode.Grow) growBy(-widthUnits, 0); break; case KeyEvent.KEYCODE_DPAD_RIGHT: if (mMode == ModifyMode.Move) moveBy(widthUnits, 0); else if (mMode == ModifyMode.Grow) growBy(widthUnits, 0); break; case KeyEvent.KEYCODE_DPAD_UP: if (mMode == ModifyMode.Move) moveBy(0, -heightUnits); else if (mMode == ModifyMode.Grow) growBy(0, -heightUnits); break; case KeyEvent.KEYCODE_DPAD_DOWN: if (mMode == ModifyMode.Move) moveBy(0, heightUnits); else if (mMode == ModifyMode.Grow) growBy(0, heightUnits); break; } } enum ModifyMode { None, Move,Grow }; ModifyMode mMode = ModifyMode.None; Rect mDrawRect; RectF mImageRect; RectF mCropRect; Matrix mMatrix; boolean mMaintainAspectRatio = false; float mInitialAspectRatio; boolean mCircle = false; Drawable mResizeDrawableWidth, mResizeDrawableHeight, mResizeDrawableDiagonal; Paint mFocusPaint = new Paint(); Paint mNoFocusPaint = new Paint(); Paint mOutlinePaint = new Paint(); }