blob: 78d5d373d4e1154a829b0a264ce759cb71a71638 [file] [log] [blame]
Owen Lina2fba682011-08-17 22:07:43 +08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package com.android.gallery3d.ui;
18
Owen Lina2fba682011-08-17 22:07:43 +080019import android.graphics.Bitmap;
20import android.graphics.Bitmap.Config;
21import android.graphics.Canvas;
22import android.graphics.Color;
23import android.graphics.Paint;
24import android.graphics.PointF;
25import android.graphics.RectF;
26import android.media.FaceDetector;
27import android.os.Handler;
28import android.os.Message;
Chih-Chung Chang4e051902012-02-11 07:19:47 +080029import android.util.FloatMath;
Owen Lina2fba682011-08-17 22:07:43 +080030import android.view.MotionEvent;
31import android.view.animation.DecelerateInterpolator;
32import android.widget.Toast;
33
Owen Lin73a04ff2012-03-14 17:27:24 +080034import com.android.gallery3d.R;
35import com.android.gallery3d.anim.Animation;
36import com.android.gallery3d.app.GalleryActivity;
37import com.android.gallery3d.common.Utils;
38
Owen Lina2fba682011-08-17 22:07:43 +080039import java.util.ArrayList;
Owen Lin73a04ff2012-03-14 17:27:24 +080040
Owen Lina2fba682011-08-17 22:07:43 +080041import javax.microedition.khronos.opengles.GL11;
42
43/**
44 * The activity can crop specific region of interest from an image.
45 */
46public class CropView extends GLView {
47 private static final String TAG = "CropView";
48
49 private static final int FACE_PIXEL_COUNT = 120000; // around 400x300
50
51 private static final int COLOR_OUTLINE = 0xFF008AFF;
52 private static final int COLOR_FACE_OUTLINE = 0xFF000000;
53
54 private static final float OUTLINE_WIDTH = 3f;
55
56 private static final int SIZE_UNKNOWN = -1;
57 private static final int TOUCH_TOLERANCE = 30;
58
59 private static final float MIN_SELECTION_LENGTH = 16f;
60 public static final float UNSPECIFIED = -1f;
61
62 private static final int MAX_FACE_COUNT = 3;
63 private static final float FACE_EYE_RATIO = 2f;
64
65 private static final int ANIMATION_DURATION = 1250;
66
67 private static final int MOVE_LEFT = 1;
68 private static final int MOVE_TOP = 2;
69 private static final int MOVE_RIGHT = 4;
70 private static final int MOVE_BOTTOM = 8;
71 private static final int MOVE_BLOCK = 16;
72
73 private static final float MAX_SELECTION_RATIO = 0.8f;
74 private static final float MIN_SELECTION_RATIO = 0.4f;
75 private static final float SELECTION_RATIO = 0.60f;
76 private static final int ANIMATION_TRIGGER = 64;
77
78 private static final int MSG_UPDATE_FACES = 1;
79
80 private float mAspectRatio = UNSPECIFIED;
81 private float mSpotlightRatioX = 0;
82 private float mSpotlightRatioY = 0;
83
84 private Handler mMainHandler;
85
86 private FaceHighlightView mFaceDetectionView;
87 private HighlightRectangle mHighlightRectangle;
88 private TileImageView mImageView;
89 private AnimationController mAnimation = new AnimationController();
90
91 private int mImageWidth = SIZE_UNKNOWN;
92 private int mImageHeight = SIZE_UNKNOWN;
93
94 private GalleryActivity mActivity;
95
96 private GLPaint mPaint = new GLPaint();
97 private GLPaint mFacePaint = new GLPaint();
98
99 private int mImageRotation;
100
101 public CropView(GalleryActivity activity) {
102 mActivity = activity;
103 mImageView = new TileImageView(activity);
104 mFaceDetectionView = new FaceHighlightView();
105 mHighlightRectangle = new HighlightRectangle();
106
107 addComponent(mImageView);
108 addComponent(mFaceDetectionView);
109 addComponent(mHighlightRectangle);
110
111 mHighlightRectangle.setVisibility(GLView.INVISIBLE);
112
113 mPaint.setColor(COLOR_OUTLINE);
114 mPaint.setLineWidth(OUTLINE_WIDTH);
115
116 mFacePaint.setColor(COLOR_FACE_OUTLINE);
117 mFacePaint.setLineWidth(OUTLINE_WIDTH);
118
119 mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
120 @Override
121 public void handleMessage(Message message) {
122 Utils.assertTrue(message.what == MSG_UPDATE_FACES);
123 ((DetectFaceTask) message.obj).updateFaces();
124 }
125 };
126 }
127
128 public void setAspectRatio(float ratio) {
129 mAspectRatio = ratio;
130 }
131
132 public void setSpotlightRatio(float ratioX, float ratioY) {
133 mSpotlightRatioX = ratioX;
134 mSpotlightRatioY = ratioY;
135 }
136
137 @Override
138 public void onLayout(boolean changed, int l, int t, int r, int b) {
139 int width = r - l;
140 int height = b - t;
141
142 mFaceDetectionView.layout(0, 0, width, height);
143 mHighlightRectangle.layout(0, 0, width, height);
144 mImageView.layout(0, 0, width, height);
145 if (mImageHeight != SIZE_UNKNOWN) {
146 mAnimation.initialize();
147 if (mHighlightRectangle.getVisibility() == GLView.VISIBLE) {
148 mAnimation.parkNow(
149 mHighlightRectangle.mHighlightRect);
150 }
151 }
152 }
153
154 private boolean setImageViewPosition(int centerX, int centerY, float scale) {
155 int inverseX = mImageWidth - centerX;
156 int inverseY = mImageHeight - centerY;
157 TileImageView t = mImageView;
158 int rotation = mImageRotation;
159 switch (rotation) {
160 case 0: return t.setPosition(centerX, centerY, scale, 0);
161 case 90: return t.setPosition(centerY, inverseX, scale, 90);
162 case 180: return t.setPosition(inverseX, inverseY, scale, 180);
163 case 270: return t.setPosition(inverseY, centerX, scale, 270);
164 default: throw new IllegalArgumentException(String.valueOf(rotation));
165 }
166 }
167
168 @Override
169 public void render(GLCanvas canvas) {
170 AnimationController a = mAnimation;
Chih-Chung Changb3d01962012-02-17 10:02:27 +0800171 if (a.calculate(AnimationTime.get())) invalidate();
Owen Lina2fba682011-08-17 22:07:43 +0800172 setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale());
173 super.render(canvas);
174 }
175
176 @Override
177 public void renderBackground(GLCanvas canvas) {
178 canvas.clearBuffer();
179 }
180
181 public RectF getCropRectangle() {
182 if (mHighlightRectangle.getVisibility() == GLView.INVISIBLE) return null;
183 RectF rect = mHighlightRectangle.mHighlightRect;
184 RectF result = new RectF(rect.left * mImageWidth, rect.top * mImageHeight,
185 rect.right * mImageWidth, rect.bottom * mImageHeight);
186 return result;
187 }
188
189 public int getImageWidth() {
190 return mImageWidth;
191 }
192
193 public int getImageHeight() {
194 return mImageHeight;
195 }
196
197 private class FaceHighlightView extends GLView {
198 private static final int INDEX_NONE = -1;
199 private ArrayList<RectF> mFaces = new ArrayList<RectF>();
200 private RectF mRect = new RectF();
201 private int mPressedFaceIndex = INDEX_NONE;
202
203 public void addFace(RectF faceRect) {
204 mFaces.add(faceRect);
205 invalidate();
206 }
207
208 private void renderFace(GLCanvas canvas, RectF face, boolean pressed) {
209 GL11 gl = canvas.getGLInstance();
210 if (pressed) {
211 gl.glEnable(GL11.GL_STENCIL_TEST);
212 gl.glClear(GL11.GL_STENCIL_BUFFER_BIT);
213 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
214 gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
215 }
216
217 RectF r = mAnimation.mapRect(face, mRect);
218 canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT);
219 canvas.drawRect(r.left, r.top, r.width(), r.height(), mFacePaint);
220
221 if (pressed) {
222 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
223 }
224 }
225
226 @Override
227 protected void renderBackground(GLCanvas canvas) {
228 ArrayList<RectF> faces = mFaces;
229 for (int i = 0, n = faces.size(); i < n; ++i) {
230 renderFace(canvas, faces.get(i), i == mPressedFaceIndex);
231 }
232
233 GL11 gl = canvas.getGLInstance();
234 if (mPressedFaceIndex != INDEX_NONE) {
235 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
236 canvas.fillRect(0, 0, getWidth(), getHeight(), 0x66000000);
237 gl.glDisable(GL11.GL_STENCIL_TEST);
238 }
239 }
240
241 private void setPressedFace(int index) {
242 if (mPressedFaceIndex == index) return;
243 mPressedFaceIndex = index;
244 invalidate();
245 }
246
247 private int getFaceIndexByPosition(float x, float y) {
248 ArrayList<RectF> faces = mFaces;
249 for (int i = 0, n = faces.size(); i < n; ++i) {
250 RectF r = mAnimation.mapRect(faces.get(i), mRect);
251 if (r.contains(x, y)) return i;
252 }
253 return INDEX_NONE;
254 }
255
256 @Override
257 protected boolean onTouch(MotionEvent event) {
258 float x = event.getX();
259 float y = event.getY();
260 switch (event.getAction()) {
261 case MotionEvent.ACTION_DOWN:
262 case MotionEvent.ACTION_MOVE: {
263 setPressedFace(getFaceIndexByPosition(x, y));
264 break;
265 }
266 case MotionEvent.ACTION_CANCEL:
267 case MotionEvent.ACTION_UP: {
268 int index = mPressedFaceIndex;
269 setPressedFace(INDEX_NONE);
270 if (index != INDEX_NONE) {
271 mHighlightRectangle.setRectangle(mFaces.get(index));
272 mHighlightRectangle.setVisibility(GLView.VISIBLE);
273 setVisibility(GLView.INVISIBLE);
274 }
275 }
276 }
277 return true;
278 }
279 }
280
281 private class AnimationController extends Animation {
282 private int mCurrentX;
283 private int mCurrentY;
284 private float mCurrentScale;
285 private int mStartX;
286 private int mStartY;
287 private float mStartScale;
288 private int mTargetX;
289 private int mTargetY;
290 private float mTargetScale;
291
292 public AnimationController() {
293 setDuration(ANIMATION_DURATION);
294 setInterpolator(new DecelerateInterpolator(4));
295 }
296
297 public void initialize() {
298 mCurrentX = mImageWidth / 2;
299 mCurrentY = mImageHeight / 2;
300 mCurrentScale = Math.min(2, Math.min(
301 (float) getWidth() / mImageWidth,
302 (float) getHeight() / mImageHeight));
303 }
304
305 public void startParkingAnimation(RectF highlight) {
306 RectF r = mAnimation.mapRect(highlight, new RectF());
307 int width = getWidth();
308 int height = getHeight();
309
310 float wr = r.width() / width;
311 float hr = r.height() / height;
312 final int d = ANIMATION_TRIGGER;
313 if (wr >= MIN_SELECTION_RATIO && wr < MAX_SELECTION_RATIO
314 && hr >= MIN_SELECTION_RATIO && hr < MAX_SELECTION_RATIO
315 && r.left >= d && r.right < width - d
316 && r.top >= d && r.bottom < height - d) return;
317
318 mStartX = mCurrentX;
319 mStartY = mCurrentY;
320 mStartScale = mCurrentScale;
321 calculateTarget(highlight);
322 start();
323 }
324
325 public void parkNow(RectF highlight) {
326 calculateTarget(highlight);
327 forceStop();
328 mStartX = mCurrentX = mTargetX;
329 mStartY = mCurrentY = mTargetY;
330 mStartScale = mCurrentScale = mTargetScale;
331 }
332
333 public void inverseMapPoint(PointF point) {
334 float s = mCurrentScale;
335 point.x = Utils.clamp(((point.x - getWidth() * 0.5f) / s
336 + mCurrentX) / mImageWidth, 0, 1);
337 point.y = Utils.clamp(((point.y - getHeight() * 0.5f) / s
338 + mCurrentY) / mImageHeight, 0, 1);
339 }
340
341 public RectF mapRect(RectF input, RectF output) {
342 float offsetX = getWidth() * 0.5f;
343 float offsetY = getHeight() * 0.5f;
344 int x = mCurrentX;
345 int y = mCurrentY;
346 float s = mCurrentScale;
347 output.set(
348 offsetX + (input.left * mImageWidth - x) * s,
349 offsetY + (input.top * mImageHeight - y) * s,
350 offsetX + (input.right * mImageWidth - x) * s,
351 offsetY + (input.bottom * mImageHeight - y) * s);
352 return output;
353 }
354
355 @Override
356 protected void onCalculate(float progress) {
357 mCurrentX = Math.round(mStartX + (mTargetX - mStartX) * progress);
358 mCurrentY = Math.round(mStartY + (mTargetY - mStartY) * progress);
359 mCurrentScale = mStartScale + (mTargetScale - mStartScale) * progress;
360
361 if (mCurrentX == mTargetX && mCurrentY == mTargetY
362 && mCurrentScale == mTargetScale) forceStop();
363 }
364
365 public int getCenterX() {
366 return mCurrentX;
367 }
368
369 public int getCenterY() {
370 return mCurrentY;
371 }
372
373 public float getScale() {
374 return mCurrentScale;
375 }
376
377 private void calculateTarget(RectF highlight) {
378 float width = getWidth();
379 float height = getHeight();
380
381 if (mImageWidth != SIZE_UNKNOWN) {
382 float minScale = Math.min(width / mImageWidth, height / mImageHeight);
383 float scale = Utils.clamp(SELECTION_RATIO * Math.min(
384 width / (highlight.width() * mImageWidth),
385 height / (highlight.height() * mImageHeight)), minScale, 2f);
386 int centerX = Math.round(
387 mImageWidth * (highlight.left + highlight.right) * 0.5f);
388 int centerY = Math.round(
389 mImageHeight * (highlight.top + highlight.bottom) * 0.5f);
390
391 if (Math.round(mImageWidth * scale) > width) {
392 int limitX = Math.round(width * 0.5f / scale);
393 centerX = Math.round(
394 (highlight.left + highlight.right) * mImageWidth / 2);
395 centerX = Utils.clamp(centerX, limitX, mImageWidth - limitX);
396 } else {
397 centerX = mImageWidth / 2;
398 }
399 if (Math.round(mImageHeight * scale) > height) {
400 int limitY = Math.round(height * 0.5f / scale);
401 centerY = Math.round(
402 (highlight.top + highlight.bottom) * mImageHeight / 2);
403 centerY = Utils.clamp(centerY, limitY, mImageHeight - limitY);
404 } else {
405 centerY = mImageHeight / 2;
406 }
407 mTargetX = centerX;
408 mTargetY = centerY;
409 mTargetScale = scale;
410 }
411 }
412
413 }
414
415 private class HighlightRectangle extends GLView {
416 private RectF mHighlightRect = new RectF(0.25f, 0.25f, 0.75f, 0.75f);
417 private RectF mTempRect = new RectF();
418 private PointF mTempPoint = new PointF();
419
Michael Jurkae3f4b8f2011-09-27 12:21:27 -0700420 private ResourceTexture mArrow;
Owen Lina2fba682011-08-17 22:07:43 +0800421
422 private int mMovingEdges = 0;
423 private float mReferenceX;
424 private float mReferenceY;
425
426 public HighlightRectangle() {
Michael Jurkae3f4b8f2011-09-27 12:21:27 -0700427 mArrow = new ResourceTexture(mActivity.getAndroidContext(),
428 R.drawable.camera_crop_holo);
Owen Lina2fba682011-08-17 22:07:43 +0800429 }
430
431 public void setInitRectangle() {
432 float targetRatio = mAspectRatio == UNSPECIFIED
433 ? 1f
434 : mAspectRatio * mImageHeight / mImageWidth;
435 float w = SELECTION_RATIO / 2f;
436 float h = SELECTION_RATIO / 2f;
437 if (targetRatio > 1) {
438 h = w / targetRatio;
439 } else {
440 w = h * targetRatio;
441 }
442 mHighlightRect.set(0.5f - w, 0.5f - h, 0.5f + w, 0.5f + h);
443 }
444
445 public void setRectangle(RectF faceRect) {
446 mHighlightRect.set(faceRect);
447 mAnimation.startParkingAnimation(faceRect);
448 invalidate();
449 }
450
451 private void moveEdges(MotionEvent event) {
452 float scale = mAnimation.getScale();
453 float dx = (event.getX() - mReferenceX) / scale / mImageWidth;
454 float dy = (event.getY() - mReferenceY) / scale / mImageHeight;
455 mReferenceX = event.getX();
456 mReferenceY = event.getY();
457 RectF r = mHighlightRect;
458
459 if ((mMovingEdges & MOVE_BLOCK) != 0) {
460 dx = Utils.clamp(dx, -r.left, 1 - r.right);
461 dy = Utils.clamp(dy, -r.top , 1 - r.bottom);
462 r.top += dy;
463 r.bottom += dy;
464 r.left += dx;
465 r.right += dx;
466 } else {
467 PointF point = mTempPoint;
468 point.set(mReferenceX, mReferenceY);
469 mAnimation.inverseMapPoint(point);
470 float left = r.left + MIN_SELECTION_LENGTH / mImageWidth;
471 float right = r.right - MIN_SELECTION_LENGTH / mImageWidth;
472 float top = r.top + MIN_SELECTION_LENGTH / mImageHeight;
473 float bottom = r.bottom - MIN_SELECTION_LENGTH / mImageHeight;
474 if ((mMovingEdges & MOVE_RIGHT) != 0) {
475 r.right = Utils.clamp(point.x, left, 1f);
476 }
477 if ((mMovingEdges & MOVE_LEFT) != 0) {
478 r.left = Utils.clamp(point.x, 0, right);
479 }
480 if ((mMovingEdges & MOVE_TOP) != 0) {
481 r.top = Utils.clamp(point.y, 0, bottom);
482 }
483 if ((mMovingEdges & MOVE_BOTTOM) != 0) {
484 r.bottom = Utils.clamp(point.y, top, 1f);
485 }
486 if (mAspectRatio != UNSPECIFIED) {
487 float targetRatio = mAspectRatio * mImageHeight / mImageWidth;
488 if (r.width() / r.height() > targetRatio) {
489 float height = r.width() / targetRatio;
490 if ((mMovingEdges & MOVE_BOTTOM) != 0) {
491 r.bottom = Utils.clamp(r.top + height, top, 1f);
492 } else {
493 r.top = Utils.clamp(r.bottom - height, 0, bottom);
494 }
495 } else {
496 float width = r.height() * targetRatio;
497 if ((mMovingEdges & MOVE_LEFT) != 0) {
498 r.left = Utils.clamp(r.right - width, 0, right);
499 } else {
500 r.right = Utils.clamp(r.left + width, left, 1f);
501 }
502 }
503 if (r.width() / r.height() > targetRatio) {
504 float width = r.height() * targetRatio;
505 if ((mMovingEdges & MOVE_LEFT) != 0) {
506 r.left = Utils.clamp(r.right - width, 0, right);
507 } else {
508 r.right = Utils.clamp(r.left + width, left, 1f);
509 }
510 } else {
511 float height = r.width() / targetRatio;
512 if ((mMovingEdges & MOVE_BOTTOM) != 0) {
513 r.bottom = Utils.clamp(r.top + height, top, 1f);
514 } else {
515 r.top = Utils.clamp(r.bottom - height, 0, bottom);
516 }
517 }
518 }
519 }
520 invalidate();
521 }
522
523 private void setMovingEdges(MotionEvent event) {
524 RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
525 float x = event.getX();
526 float y = event.getY();
527
528 if (x > r.left + TOUCH_TOLERANCE && x < r.right - TOUCH_TOLERANCE
529 && y > r.top + TOUCH_TOLERANCE && y < r.bottom - TOUCH_TOLERANCE) {
530 mMovingEdges = MOVE_BLOCK;
531 return;
532 }
533
534 boolean inVerticalRange = (r.top - TOUCH_TOLERANCE) <= y
535 && y <= (r.bottom + TOUCH_TOLERANCE);
536 boolean inHorizontalRange = (r.left - TOUCH_TOLERANCE) <= x
537 && x <= (r.right + TOUCH_TOLERANCE);
538
539 if (inVerticalRange) {
540 boolean left = Math.abs(x - r.left) <= TOUCH_TOLERANCE;
541 boolean right = Math.abs(x - r.right) <= TOUCH_TOLERANCE;
542 if (left && right) {
543 left = Math.abs(x - r.left) < Math.abs(x - r.right);
544 right = !left;
545 }
546 if (left) mMovingEdges |= MOVE_LEFT;
547 if (right) mMovingEdges |= MOVE_RIGHT;
548 if (mAspectRatio != UNSPECIFIED && inHorizontalRange) {
549 mMovingEdges |= (y >
550 (r.top + r.bottom) / 2) ? MOVE_BOTTOM : MOVE_TOP;
551 }
552 }
553 if (inHorizontalRange) {
554 boolean top = Math.abs(y - r.top) <= TOUCH_TOLERANCE;
555 boolean bottom = Math.abs(y - r.bottom) <= TOUCH_TOLERANCE;
556 if (top && bottom) {
557 top = Math.abs(y - r.top) < Math.abs(y - r.bottom);
558 bottom = !top;
559 }
560 if (top) mMovingEdges |= MOVE_TOP;
561 if (bottom) mMovingEdges |= MOVE_BOTTOM;
562 if (mAspectRatio != UNSPECIFIED && inVerticalRange) {
563 mMovingEdges |= (x >
564 (r.left + r.right) / 2) ? MOVE_RIGHT : MOVE_LEFT;
565 }
566 }
567 }
568
569 @Override
570 protected boolean onTouch(MotionEvent event) {
571 switch (event.getAction()) {
572 case MotionEvent.ACTION_DOWN: {
573 mReferenceX = event.getX();
574 mReferenceY = event.getY();
575 setMovingEdges(event);
576 invalidate();
577 return true;
578 }
579 case MotionEvent.ACTION_MOVE:
580 moveEdges(event);
581 break;
582 case MotionEvent.ACTION_CANCEL:
583 case MotionEvent.ACTION_UP: {
584 mMovingEdges = 0;
585 mAnimation.startParkingAnimation(mHighlightRect);
586 invalidate();
587 return true;
588 }
589 }
590 return true;
591 }
592
593 @Override
594 protected void renderBackground(GLCanvas canvas) {
595 RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
596 drawHighlightRectangle(canvas, r);
597
598 float centerY = (r.top + r.bottom) / 2;
599 float centerX = (r.left + r.right) / 2;
Michael Jurkae3f4b8f2011-09-27 12:21:27 -0700600 boolean notMoving = mMovingEdges == 0;
601 if ((mMovingEdges & MOVE_RIGHT) != 0 || notMoving) {
602 mArrow.draw(canvas,
603 Math.round(r.right - mArrow.getWidth() / 2),
604 Math.round(centerY - mArrow.getHeight() / 2));
Owen Lina2fba682011-08-17 22:07:43 +0800605 }
Michael Jurkae3f4b8f2011-09-27 12:21:27 -0700606 if ((mMovingEdges & MOVE_LEFT) != 0 || notMoving) {
607 mArrow.draw(canvas,
608 Math.round(r.left - mArrow.getWidth() / 2),
609 Math.round(centerY - mArrow.getHeight() / 2));
Owen Lina2fba682011-08-17 22:07:43 +0800610 }
Michael Jurkae3f4b8f2011-09-27 12:21:27 -0700611 if ((mMovingEdges & MOVE_TOP) != 0 || notMoving) {
612 mArrow.draw(canvas,
613 Math.round(centerX - mArrow.getWidth() / 2),
614 Math.round(r.top - mArrow.getHeight() / 2));
Owen Lina2fba682011-08-17 22:07:43 +0800615 }
Michael Jurkae3f4b8f2011-09-27 12:21:27 -0700616 if ((mMovingEdges & MOVE_BOTTOM) != 0 || notMoving) {
617 mArrow.draw(canvas,
618 Math.round(centerX - mArrow.getWidth() / 2),
619 Math.round(r.bottom - mArrow.getHeight() / 2));
Owen Lina2fba682011-08-17 22:07:43 +0800620 }
621 }
622
623 private void drawHighlightRectangle(GLCanvas canvas, RectF r) {
624 GL11 gl = canvas.getGLInstance();
625 gl.glLineWidth(3.0f);
626 gl.glEnable(GL11.GL_LINE_SMOOTH);
627
628 gl.glEnable(GL11.GL_STENCIL_TEST);
629 gl.glClear(GL11.GL_STENCIL_BUFFER_BIT);
630 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
631 gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
632
633 if (mSpotlightRatioX == 0 || mSpotlightRatioY == 0) {
634 canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT);
635 canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint);
636 } else {
637 float sx = r.width() * mSpotlightRatioX;
638 float sy = r.height() * mSpotlightRatioY;
639 float cx = r.centerX();
640 float cy = r.centerY();
641
642 canvas.fillRect(cx - sx / 2, cy - sy / 2, sx, sy, Color.TRANSPARENT);
643 canvas.drawRect(cx - sx / 2, cy - sy / 2, sx, sy, mPaint);
644 canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint);
645
646 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
647 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
648
649 canvas.drawRect(cx - sy / 2, cy - sx / 2, sy, sx, mPaint);
650 canvas.fillRect(cx - sy / 2, cy - sx / 2, sy, sx, Color.TRANSPARENT);
651 canvas.fillRect(r.left, r.top, r.width(), r.height(), 0x80000000);
652 }
653
654 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
655 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
656
657 canvas.fillRect(0, 0, getWidth(), getHeight(), 0xA0000000);
658
659 gl.glDisable(GL11.GL_STENCIL_TEST);
660 }
661 }
662
663 private class DetectFaceTask extends Thread {
664 private final FaceDetector.Face[] mFaces = new FaceDetector.Face[MAX_FACE_COUNT];
665 private final Bitmap mFaceBitmap;
666 private int mFaceCount;
667
668 public DetectFaceTask(Bitmap bitmap) {
669 mFaceBitmap = bitmap;
670 setName("face-detect");
671 }
672
673 @Override
674 public void run() {
675 Bitmap bitmap = mFaceBitmap;
676 FaceDetector detector = new FaceDetector(
677 bitmap.getWidth(), bitmap.getHeight(), MAX_FACE_COUNT);
678 mFaceCount = detector.findFaces(bitmap, mFaces);
679 mMainHandler.sendMessage(
680 mMainHandler.obtainMessage(MSG_UPDATE_FACES, this));
681 }
682
683 private RectF getFaceRect(FaceDetector.Face face) {
684 PointF point = new PointF();
685 face.getMidPoint(point);
686
687 int width = mFaceBitmap.getWidth();
688 int height = mFaceBitmap.getHeight();
689 float rx = face.eyesDistance() * FACE_EYE_RATIO;
690 float ry = rx;
691 float aspect = mAspectRatio;
692 if (aspect != UNSPECIFIED) {
693 if (aspect > 1) {
694 rx = ry * aspect;
695 } else {
696 ry = rx / aspect;
697 }
698 }
699
700 RectF r = new RectF(
701 point.x - rx, point.y - ry, point.x + rx, point.y + ry);
702 r.intersect(0, 0, width, height);
703
704 if (aspect != UNSPECIFIED) {
705 if (r.width() / r.height() > aspect) {
706 float w = r.height() * aspect;
707 r.left = (r.left + r.right - w) * 0.5f;
708 r.right = r.left + w;
709 } else {
710 float h = r.width() / aspect;
711 r.top = (r.top + r.bottom - h) * 0.5f;
712 r.bottom = r.top + h;
713 }
714 }
715
716 r.left /= width;
717 r.right /= width;
718 r.top /= height;
719 r.bottom /= height;
720 return r;
721 }
722
723 public void updateFaces() {
724 if (mFaceCount > 1) {
725 for (int i = 0, n = mFaceCount; i < n; ++i) {
726 mFaceDetectionView.addFace(getFaceRect(mFaces[i]));
727 }
728 mFaceDetectionView.setVisibility(GLView.VISIBLE);
729 Toast.makeText(mActivity.getAndroidContext(),
730 R.string.multiface_crop_help, Toast.LENGTH_SHORT).show();
731 } else if (mFaceCount == 1) {
732 mFaceDetectionView.setVisibility(GLView.INVISIBLE);
733 mHighlightRectangle.setRectangle(getFaceRect(mFaces[0]));
734 mHighlightRectangle.setVisibility(GLView.VISIBLE);
735 } else /*mFaceCount == 0*/ {
736 mHighlightRectangle.setInitRectangle();
737 mHighlightRectangle.setVisibility(GLView.VISIBLE);
738 }
739 }
740 }
741
742 public void setDataModel(TileImageView.Model dataModel, int rotation) {
743 if (((rotation / 90) & 0x01) != 0) {
744 mImageWidth = dataModel.getImageHeight();
745 mImageHeight = dataModel.getImageWidth();
746 } else {
747 mImageWidth = dataModel.getImageWidth();
748 mImageHeight = dataModel.getImageHeight();
749 }
750
751 mImageRotation = rotation;
752
753 mImageView.setModel(dataModel);
754 mAnimation.initialize();
755 }
756
757 public void detectFaces(Bitmap bitmap) {
758 int rotation = mImageRotation;
759 int width = bitmap.getWidth();
760 int height = bitmap.getHeight();
Chih-Chung Chang4e051902012-02-11 07:19:47 +0800761 float scale = FloatMath.sqrt((float) FACE_PIXEL_COUNT / (width * height));
Owen Lina2fba682011-08-17 22:07:43 +0800762
763 // faceBitmap is a correctly rotated bitmap, as viewed by a user.
764 Bitmap faceBitmap;
765 if (((rotation / 90) & 1) == 0) {
766 int w = (Math.round(width * scale) & ~1); // must be even
767 int h = Math.round(height * scale);
768 faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
769 Canvas canvas = new Canvas(faceBitmap);
770 canvas.rotate(rotation, w / 2, h / 2);
771 canvas.scale((float) w / width, (float) h / height);
772 canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
773 } else {
774 int w = (Math.round(height * scale) & ~1); // must be even
775 int h = Math.round(width * scale);
776 faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
777 Canvas canvas = new Canvas(faceBitmap);
778 canvas.translate(w / 2, h / 2);
779 canvas.rotate(rotation);
780 canvas.translate(-h / 2, -w / 2);
781 canvas.scale((float) w / height, (float) h / width);
782 canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
783 }
784 new DetectFaceTask(faceBitmap).start();
785 }
786
787 public void initializeHighlightRectangle() {
788 mHighlightRectangle.setInitRectangle();
789 mHighlightRectangle.setVisibility(GLView.VISIBLE);
790 }
791
792 public void resume() {
793 mImageView.prepareTextures();
794 }
795
796 public void pause() {
797 mImageView.freeTextures();
798 }
799}
800