2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.cooliris.media;
19 import android.graphics.Canvas;
20 import android.graphics.Matrix;
21 import android.graphics.Paint;
22 import android.graphics.Path;
23 import android.graphics.Rect;
24 import android.graphics.RectF;
25 import android.graphics.Region;
26 import android.graphics.drawable.Drawable;
27 import android.view.View;
29 import com.cooliris.app.Res;
31 // This class is used by CropImage to display a highlighted cropping rectangle
32 // overlayed with the image. There are two coordinate spaces in use. One is
33 // image, another is screen. computeLayout() uses mMatrix to map from image
34 // space to screen space.
37 @SuppressWarnings("unused")
38 private static final String TAG = "HighlightView";
39 View mContext; // The View displaying the image.
41 public static final int GROW_NONE = (1 << 0);
42 public static final int GROW_LEFT_EDGE = (1 << 1);
43 public static final int GROW_RIGHT_EDGE = (1 << 2);
44 public static final int GROW_TOP_EDGE = (1 << 3);
45 public static final int GROW_BOTTOM_EDGE = (1 << 4);
46 public static final int MOVE = (1 << 5);
48 public HighlightView(View ctx) {
53 android.content.res.Resources resources = mContext.getResources();
54 mResizeDrawableWidth = resources.getDrawable(Res.drawable.camera_crop_width);
55 mResizeDrawableHeight = resources.getDrawable(Res.drawable.camera_crop_height);
56 mResizeDrawableDiagonal = resources.getDrawable(Res.drawable.indicator_autocrop);
62 public boolean hasFocus() {
66 public void setFocus(boolean f) {
70 public void setHidden(boolean hidden) {
74 protected void draw(Canvas canvas) {
79 Path path = new Path();
81 mOutlinePaint.setColor(0xFF000000);
82 canvas.drawRect(mDrawRect, mOutlinePaint);
84 Rect viewDrawingRect = new Rect();
85 mContext.getDrawingRect(viewDrawingRect);
87 float width = mDrawRect.width();
88 float height = mDrawRect.height();
89 path.addCircle(mDrawRect.left + (width / 2), mDrawRect.top + (height / 2), width / 2, Path.Direction.CW);
90 mOutlinePaint.setColor(0xFFEF04D6);
92 path.addRect(new RectF(mDrawRect), Path.Direction.CW);
93 mOutlinePaint.setColor(0xFFFF8A00);
95 canvas.clipPath(path, Region.Op.DIFFERENCE);
96 canvas.drawRect(viewDrawingRect, hasFocus() ? mFocusPaint : mNoFocusPaint);
99 canvas.drawPath(path, mOutlinePaint);
101 if (mMode == ModifyMode.Grow) {
103 int width = mResizeDrawableDiagonal.getIntrinsicWidth();
104 int height = mResizeDrawableDiagonal.getIntrinsicHeight();
106 int d = (int) Math.round(Math.cos(/* 45deg */Math.PI / 4D) * (mDrawRect.width() / 2D));
107 int x = mDrawRect.left + (mDrawRect.width() / 2) + d - width / 2;
108 int y = mDrawRect.top + (mDrawRect.height() / 2) - d - height / 2;
109 mResizeDrawableDiagonal.setBounds(x, y, x + mResizeDrawableDiagonal.getIntrinsicWidth(), y
110 + mResizeDrawableDiagonal.getIntrinsicHeight());
111 mResizeDrawableDiagonal.draw(canvas);
113 int left = mDrawRect.left + 1;
114 int right = mDrawRect.right + 1;
115 int top = mDrawRect.top + 4;
116 int bottom = mDrawRect.bottom + 3;
118 int widthWidth = mResizeDrawableWidth.getIntrinsicWidth() / 2;
119 int widthHeight = mResizeDrawableWidth.getIntrinsicHeight() / 2;
120 int heightHeight = mResizeDrawableHeight.getIntrinsicHeight() / 2;
121 int heightWidth = mResizeDrawableHeight.getIntrinsicWidth() / 2;
123 int xMiddle = mDrawRect.left + ((mDrawRect.right - mDrawRect.left) / 2);
124 int yMiddle = mDrawRect.top + ((mDrawRect.bottom - mDrawRect.top) / 2);
126 mResizeDrawableWidth.setBounds(left - widthWidth, yMiddle - widthHeight, left + widthWidth, yMiddle
128 mResizeDrawableWidth.draw(canvas);
130 mResizeDrawableWidth.setBounds(right - widthWidth, yMiddle - widthHeight, right + widthWidth, yMiddle
132 mResizeDrawableWidth.draw(canvas);
134 mResizeDrawableHeight.setBounds(xMiddle - heightWidth, top - heightHeight, xMiddle + heightWidth, top
136 mResizeDrawableHeight.draw(canvas);
138 mResizeDrawableHeight.setBounds(xMiddle - heightWidth, bottom - heightHeight, xMiddle + heightWidth, bottom
140 mResizeDrawableHeight.draw(canvas);
146 public void setMode(ModifyMode mode) {
149 mContext.invalidate();
153 // Determines which edges are hit by touching at (x, y).
154 public int getHit(float x, float y) {
155 Rect r = computeLayout();
156 final float hysteresis = 20F;
157 int retval = GROW_NONE;
160 float distX = x - r.centerX();
161 float distY = y - r.centerY();
162 int distanceFromCenter = (int) Math.sqrt(distX * distX + distY * distY);
163 int radius = mDrawRect.width() / 2;
164 int delta = distanceFromCenter - radius;
165 if (Math.abs(delta) <= hysteresis) {
166 if (Math.abs(distY) > Math.abs(distX)) {
168 retval = GROW_TOP_EDGE;
170 retval = GROW_BOTTOM_EDGE;
174 retval = GROW_LEFT_EDGE;
176 retval = GROW_RIGHT_EDGE;
179 } else if (distanceFromCenter < radius) {
185 // verticalCheck makes sure the position is between the top and
186 // the bottom edge (with some tolerance). Similar for horizCheck.
187 boolean verticalCheck = (y >= r.top - hysteresis) && (y < r.bottom + hysteresis);
188 boolean horizCheck = (x >= r.left - hysteresis) && (x < r.right + hysteresis);
190 // Check whether the position is near some edge(s).
191 if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
192 retval |= GROW_LEFT_EDGE;
194 if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
195 retval |= GROW_RIGHT_EDGE;
197 if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
198 retval |= GROW_TOP_EDGE;
200 if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
201 retval |= GROW_BOTTOM_EDGE;
204 // Not near any edge but inside the rectangle: move.
205 if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
212 // Handles motion (dx, dy) in screen space.
213 // The "edge" parameter specifies which edges the user is dragging.
214 void handleMotion(int edge, float dx, float dy) {
215 Rect r = computeLayout();
216 if (edge == GROW_NONE) {
218 } else if (edge == MOVE) {
219 // Convert to image space before sending to moveBy().
220 moveBy(dx * (mCropRect.width() / r.width()), dy * (mCropRect.height() / r.height()));
222 if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
226 if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
230 // Convert to image space before sending to growBy().
231 float xDelta = dx * (mCropRect.width() / r.width());
232 float yDelta = dy * (mCropRect.height() / r.height());
233 growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta, (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
237 // Grows the cropping rectange by (dx, dy) in image space.
238 void moveBy(float dx, float dy) {
239 Rect invalRect = new Rect(mDrawRect);
241 mCropRect.offset(dx, dy);
243 // Put the cropping rectangle inside image rectangle.
244 mCropRect.offset(Math.max(0, mImageRect.left - mCropRect.left), Math.max(0, mImageRect.top - mCropRect.top));
246 mCropRect.offset(Math.min(0, mImageRect.right - mCropRect.right), Math.min(0, mImageRect.bottom - mCropRect.bottom));
248 mDrawRect = computeLayout();
249 invalRect.union(mDrawRect);
250 invalRect.inset(-10, -10);
251 mContext.invalidate(invalRect);
254 // Grows the cropping rectange by (dx, dy) in image space.
255 void growBy(float dx, float dy) {
256 if (mMaintainAspectRatio) {
258 dy = dx / mInitialAspectRatio;
259 } else if (dy != 0) {
260 dx = dy * mInitialAspectRatio;
264 // Don't let the cropping rectangle grow too fast.
265 // Grow at most half of the difference between the image rectangle and
266 // the cropping rectangle.
267 RectF r = new RectF(mCropRect);
268 if (dx > 0F && r.width() + 2 * dx > mImageRect.width()) {
269 float adjustment = (mImageRect.width() - r.width()) / 2F;
271 if (mMaintainAspectRatio) {
272 dy = dx / mInitialAspectRatio;
275 if (dy > 0F && r.height() + 2 * dy > mImageRect.height()) {
276 float adjustment = (mImageRect.height() - r.height()) / 2F;
278 if (mMaintainAspectRatio) {
279 dx = dy * mInitialAspectRatio;
285 // Don't let the cropping rectangle shrink too fast.
286 final float widthCap = 25F;
287 if (r.width() < widthCap) {
288 r.inset(-(widthCap - r.width()) / 2F, 0F);
290 float heightCap = mMaintainAspectRatio ? (widthCap / mInitialAspectRatio) : widthCap;
291 if (r.height() < heightCap) {
292 r.inset(0F, -(heightCap - r.height()) / 2F);
295 // Put the cropping rectangle inside the image rectangle.
296 if (r.left < mImageRect.left) {
297 r.offset(mImageRect.left - r.left, 0F);
298 } else if (r.right > mImageRect.right) {
299 r.offset(-(r.right - mImageRect.right), 0);
301 if (r.top < mImageRect.top) {
302 r.offset(0F, mImageRect.top - r.top);
303 } else if (r.bottom > mImageRect.bottom) {
304 r.offset(0F, -(r.bottom - mImageRect.bottom));
308 mDrawRect = computeLayout();
309 mContext.invalidate();
312 // Returns the cropping rectangle in image space.
313 public Rect getCropRect() {
314 return new Rect((int) mCropRect.left, (int) mCropRect.top, (int) mCropRect.right, (int) mCropRect.bottom);
317 // Maps the cropping rectangle from image space to screen space.
318 private Rect computeLayout() {
319 RectF r = new RectF(mCropRect.left, mCropRect.top, mCropRect.right, mCropRect.bottom);
321 return new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), Math.round(r.bottom));
324 public void invalidate() {
325 mDrawRect = computeLayout();
328 public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean circle, boolean maintainAspectRatio) {
330 maintainAspectRatio = true;
332 mMatrix = new Matrix(m);
334 mCropRect = cropRect;
335 mImageRect = new RectF(imageRect);
336 mMaintainAspectRatio = maintainAspectRatio;
339 mInitialAspectRatio = mCropRect.width() / mCropRect.height();
340 mDrawRect = computeLayout();
342 mFocusPaint.setARGB(125, 50, 50, 50);
343 mNoFocusPaint.setARGB(125, 50, 50, 50);
344 mOutlinePaint.setStrokeWidth(3F);
345 mOutlinePaint.setStyle(Paint.Style.STROKE);
346 mOutlinePaint.setAntiAlias(true);
348 mMode = ModifyMode.None;
356 private ModifyMode mMode = ModifyMode.None;
358 Rect mDrawRect; // in screen space
359 private RectF mImageRect; // in image space
360 RectF mCropRect; // in image space
363 private boolean mMaintainAspectRatio = false;
364 private float mInitialAspectRatio;
365 private boolean mCircle = false;
367 private Drawable mResizeDrawableWidth;
368 private Drawable mResizeDrawableHeight;
369 private Drawable mResizeDrawableDiagonal;
371 private final Paint mFocusPaint = new Paint();
372 private final Paint mNoFocusPaint = new Paint();
373 private final Paint mOutlinePaint = new Paint();