2 * Copyright (C) 2010 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.android.gallery3d.ui;
19 import com.android.gallery3d.R;
20 import com.android.gallery3d.anim.Animation;
21 import com.android.gallery3d.app.GalleryActivity;
22 import com.android.gallery3d.common.Utils;
24 import android.graphics.Bitmap;
25 import android.graphics.Bitmap.Config;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.Paint;
29 import android.graphics.PointF;
30 import android.graphics.RectF;
31 import android.media.FaceDetector;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.view.MotionEvent;
35 import android.view.animation.DecelerateInterpolator;
36 import android.widget.Toast;
38 import java.util.ArrayList;
39 import javax.microedition.khronos.opengles.GL11;
42 * The activity can crop specific region of interest from an image.
44 public class CropView extends GLView {
45 private static final String TAG = "CropView";
47 private static final int FACE_PIXEL_COUNT = 120000; // around 400x300
49 private static final int COLOR_OUTLINE = 0xFF008AFF;
50 private static final int COLOR_FACE_OUTLINE = 0xFF000000;
52 private static final float OUTLINE_WIDTH = 3f;
54 private static final int SIZE_UNKNOWN = -1;
55 private static final int TOUCH_TOLERANCE = 30;
57 private static final float MIN_SELECTION_LENGTH = 16f;
58 public static final float UNSPECIFIED = -1f;
60 private static final int MAX_FACE_COUNT = 3;
61 private static final float FACE_EYE_RATIO = 2f;
63 private static final int ANIMATION_DURATION = 1250;
65 private static final int MOVE_LEFT = 1;
66 private static final int MOVE_TOP = 2;
67 private static final int MOVE_RIGHT = 4;
68 private static final int MOVE_BOTTOM = 8;
69 private static final int MOVE_BLOCK = 16;
71 private static final float MAX_SELECTION_RATIO = 0.8f;
72 private static final float MIN_SELECTION_RATIO = 0.4f;
73 private static final float SELECTION_RATIO = 0.60f;
74 private static final int ANIMATION_TRIGGER = 64;
76 private static final int MSG_UPDATE_FACES = 1;
78 private float mAspectRatio = UNSPECIFIED;
79 private float mSpotlightRatioX = 0;
80 private float mSpotlightRatioY = 0;
82 private Handler mMainHandler;
84 private FaceHighlightView mFaceDetectionView;
85 private HighlightRectangle mHighlightRectangle;
86 private TileImageView mImageView;
87 private AnimationController mAnimation = new AnimationController();
89 private int mImageWidth = SIZE_UNKNOWN;
90 private int mImageHeight = SIZE_UNKNOWN;
92 private GalleryActivity mActivity;
94 private GLPaint mPaint = new GLPaint();
95 private GLPaint mFacePaint = new GLPaint();
97 private int mImageRotation;
99 public CropView(GalleryActivity activity) {
100 mActivity = activity;
101 mImageView = new TileImageView(activity);
102 mFaceDetectionView = new FaceHighlightView();
103 mHighlightRectangle = new HighlightRectangle();
105 addComponent(mImageView);
106 addComponent(mFaceDetectionView);
107 addComponent(mHighlightRectangle);
109 mHighlightRectangle.setVisibility(GLView.INVISIBLE);
111 mPaint.setColor(COLOR_OUTLINE);
112 mPaint.setLineWidth(OUTLINE_WIDTH);
114 mFacePaint.setColor(COLOR_FACE_OUTLINE);
115 mFacePaint.setLineWidth(OUTLINE_WIDTH);
117 mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
119 public void handleMessage(Message message) {
120 Utils.assertTrue(message.what == MSG_UPDATE_FACES);
121 ((DetectFaceTask) message.obj).updateFaces();
126 public void setAspectRatio(float ratio) {
127 mAspectRatio = ratio;
130 public void setSpotlightRatio(float ratioX, float ratioY) {
131 mSpotlightRatioX = ratioX;
132 mSpotlightRatioY = ratioY;
136 public void onLayout(boolean changed, int l, int t, int r, int b) {
140 mFaceDetectionView.layout(0, 0, width, height);
141 mHighlightRectangle.layout(0, 0, width, height);
142 mImageView.layout(0, 0, width, height);
143 if (mImageHeight != SIZE_UNKNOWN) {
144 mAnimation.initialize();
145 if (mHighlightRectangle.getVisibility() == GLView.VISIBLE) {
147 mHighlightRectangle.mHighlightRect);
152 private boolean setImageViewPosition(int centerX, int centerY, float scale) {
153 int inverseX = mImageWidth - centerX;
154 int inverseY = mImageHeight - centerY;
155 TileImageView t = mImageView;
156 int rotation = mImageRotation;
158 case 0: return t.setPosition(centerX, centerY, scale, 0);
159 case 90: return t.setPosition(centerY, inverseX, scale, 90);
160 case 180: return t.setPosition(inverseX, inverseY, scale, 180);
161 case 270: return t.setPosition(inverseY, centerX, scale, 270);
162 default: throw new IllegalArgumentException(String.valueOf(rotation));
167 public void render(GLCanvas canvas) {
168 AnimationController a = mAnimation;
169 if (a.calculate(canvas.currentAnimationTimeMillis())) invalidate();
170 setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale());
171 super.render(canvas);
175 public void renderBackground(GLCanvas canvas) {
176 canvas.clearBuffer();
179 public RectF getCropRectangle() {
180 if (mHighlightRectangle.getVisibility() == GLView.INVISIBLE) return null;
181 RectF rect = mHighlightRectangle.mHighlightRect;
182 RectF result = new RectF(rect.left * mImageWidth, rect.top * mImageHeight,
183 rect.right * mImageWidth, rect.bottom * mImageHeight);
187 public int getImageWidth() {
191 public int getImageHeight() {
195 private class FaceHighlightView extends GLView {
196 private static final int INDEX_NONE = -1;
197 private ArrayList<RectF> mFaces = new ArrayList<RectF>();
198 private RectF mRect = new RectF();
199 private int mPressedFaceIndex = INDEX_NONE;
201 public void addFace(RectF faceRect) {
202 mFaces.add(faceRect);
206 private void renderFace(GLCanvas canvas, RectF face, boolean pressed) {
207 GL11 gl = canvas.getGLInstance();
209 gl.glEnable(GL11.GL_STENCIL_TEST);
210 gl.glClear(GL11.GL_STENCIL_BUFFER_BIT);
211 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
212 gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
215 RectF r = mAnimation.mapRect(face, mRect);
216 canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT);
217 canvas.drawRect(r.left, r.top, r.width(), r.height(), mFacePaint);
220 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
225 protected void renderBackground(GLCanvas canvas) {
226 ArrayList<RectF> faces = mFaces;
227 for (int i = 0, n = faces.size(); i < n; ++i) {
228 renderFace(canvas, faces.get(i), i == mPressedFaceIndex);
231 GL11 gl = canvas.getGLInstance();
232 if (mPressedFaceIndex != INDEX_NONE) {
233 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
234 canvas.fillRect(0, 0, getWidth(), getHeight(), 0x66000000);
235 gl.glDisable(GL11.GL_STENCIL_TEST);
239 private void setPressedFace(int index) {
240 if (mPressedFaceIndex == index) return;
241 mPressedFaceIndex = index;
245 private int getFaceIndexByPosition(float x, float y) {
246 ArrayList<RectF> faces = mFaces;
247 for (int i = 0, n = faces.size(); i < n; ++i) {
248 RectF r = mAnimation.mapRect(faces.get(i), mRect);
249 if (r.contains(x, y)) return i;
255 protected boolean onTouch(MotionEvent event) {
256 float x = event.getX();
257 float y = event.getY();
258 switch (event.getAction()) {
259 case MotionEvent.ACTION_DOWN:
260 case MotionEvent.ACTION_MOVE: {
261 setPressedFace(getFaceIndexByPosition(x, y));
264 case MotionEvent.ACTION_CANCEL:
265 case MotionEvent.ACTION_UP: {
266 int index = mPressedFaceIndex;
267 setPressedFace(INDEX_NONE);
268 if (index != INDEX_NONE) {
269 mHighlightRectangle.setRectangle(mFaces.get(index));
270 mHighlightRectangle.setVisibility(GLView.VISIBLE);
271 setVisibility(GLView.INVISIBLE);
279 private class AnimationController extends Animation {
280 private int mCurrentX;
281 private int mCurrentY;
282 private float mCurrentScale;
285 private float mStartScale;
286 private int mTargetX;
287 private int mTargetY;
288 private float mTargetScale;
290 public AnimationController() {
291 setDuration(ANIMATION_DURATION);
292 setInterpolator(new DecelerateInterpolator(4));
295 public void initialize() {
296 mCurrentX = mImageWidth / 2;
297 mCurrentY = mImageHeight / 2;
298 mCurrentScale = Math.min(2, Math.min(
299 (float) getWidth() / mImageWidth,
300 (float) getHeight() / mImageHeight));
303 public void startParkingAnimation(RectF highlight) {
304 RectF r = mAnimation.mapRect(highlight, new RectF());
305 int width = getWidth();
306 int height = getHeight();
308 float wr = r.width() / width;
309 float hr = r.height() / height;
310 final int d = ANIMATION_TRIGGER;
311 if (wr >= MIN_SELECTION_RATIO && wr < MAX_SELECTION_RATIO
312 && hr >= MIN_SELECTION_RATIO && hr < MAX_SELECTION_RATIO
313 && r.left >= d && r.right < width - d
314 && r.top >= d && r.bottom < height - d) return;
318 mStartScale = mCurrentScale;
319 calculateTarget(highlight);
323 public void parkNow(RectF highlight) {
324 calculateTarget(highlight);
326 mStartX = mCurrentX = mTargetX;
327 mStartY = mCurrentY = mTargetY;
328 mStartScale = mCurrentScale = mTargetScale;
331 public void inverseMapPoint(PointF point) {
332 float s = mCurrentScale;
333 point.x = Utils.clamp(((point.x - getWidth() * 0.5f) / s
334 + mCurrentX) / mImageWidth, 0, 1);
335 point.y = Utils.clamp(((point.y - getHeight() * 0.5f) / s
336 + mCurrentY) / mImageHeight, 0, 1);
339 public RectF mapRect(RectF input, RectF output) {
340 float offsetX = getWidth() * 0.5f;
341 float offsetY = getHeight() * 0.5f;
344 float s = mCurrentScale;
346 offsetX + (input.left * mImageWidth - x) * s,
347 offsetY + (input.top * mImageHeight - y) * s,
348 offsetX + (input.right * mImageWidth - x) * s,
349 offsetY + (input.bottom * mImageHeight - y) * s);
354 protected void onCalculate(float progress) {
355 mCurrentX = Math.round(mStartX + (mTargetX - mStartX) * progress);
356 mCurrentY = Math.round(mStartY + (mTargetY - mStartY) * progress);
357 mCurrentScale = mStartScale + (mTargetScale - mStartScale) * progress;
359 if (mCurrentX == mTargetX && mCurrentY == mTargetY
360 && mCurrentScale == mTargetScale) forceStop();
363 public int getCenterX() {
367 public int getCenterY() {
371 public float getScale() {
372 return mCurrentScale;
375 private void calculateTarget(RectF highlight) {
376 float width = getWidth();
377 float height = getHeight();
379 if (mImageWidth != SIZE_UNKNOWN) {
380 float minScale = Math.min(width / mImageWidth, height / mImageHeight);
381 float scale = Utils.clamp(SELECTION_RATIO * Math.min(
382 width / (highlight.width() * mImageWidth),
383 height / (highlight.height() * mImageHeight)), minScale, 2f);
384 int centerX = Math.round(
385 mImageWidth * (highlight.left + highlight.right) * 0.5f);
386 int centerY = Math.round(
387 mImageHeight * (highlight.top + highlight.bottom) * 0.5f);
389 if (Math.round(mImageWidth * scale) > width) {
390 int limitX = Math.round(width * 0.5f / scale);
391 centerX = Math.round(
392 (highlight.left + highlight.right) * mImageWidth / 2);
393 centerX = Utils.clamp(centerX, limitX, mImageWidth - limitX);
395 centerX = mImageWidth / 2;
397 if (Math.round(mImageHeight * scale) > height) {
398 int limitY = Math.round(height * 0.5f / scale);
399 centerY = Math.round(
400 (highlight.top + highlight.bottom) * mImageHeight / 2);
401 centerY = Utils.clamp(centerY, limitY, mImageHeight - limitY);
403 centerY = mImageHeight / 2;
407 mTargetScale = scale;
413 private class HighlightRectangle extends GLView {
414 private RectF mHighlightRect = new RectF(0.25f, 0.25f, 0.75f, 0.75f);
415 private RectF mTempRect = new RectF();
416 private PointF mTempPoint = new PointF();
418 private ResourceTexture mArrow;
420 private int mMovingEdges = 0;
421 private float mReferenceX;
422 private float mReferenceY;
424 public HighlightRectangle() {
425 mArrow = new ResourceTexture(mActivity.getAndroidContext(),
426 R.drawable.camera_crop_holo);
429 public void setInitRectangle() {
430 float targetRatio = mAspectRatio == UNSPECIFIED
432 : mAspectRatio * mImageHeight / mImageWidth;
433 float w = SELECTION_RATIO / 2f;
434 float h = SELECTION_RATIO / 2f;
435 if (targetRatio > 1) {
440 mHighlightRect.set(0.5f - w, 0.5f - h, 0.5f + w, 0.5f + h);
443 public void setRectangle(RectF faceRect) {
444 mHighlightRect.set(faceRect);
445 mAnimation.startParkingAnimation(faceRect);
449 private void moveEdges(MotionEvent event) {
450 float scale = mAnimation.getScale();
451 float dx = (event.getX() - mReferenceX) / scale / mImageWidth;
452 float dy = (event.getY() - mReferenceY) / scale / mImageHeight;
453 mReferenceX = event.getX();
454 mReferenceY = event.getY();
455 RectF r = mHighlightRect;
457 if ((mMovingEdges & MOVE_BLOCK) != 0) {
458 dx = Utils.clamp(dx, -r.left, 1 - r.right);
459 dy = Utils.clamp(dy, -r.top , 1 - r.bottom);
465 PointF point = mTempPoint;
466 point.set(mReferenceX, mReferenceY);
467 mAnimation.inverseMapPoint(point);
468 float left = r.left + MIN_SELECTION_LENGTH / mImageWidth;
469 float right = r.right - MIN_SELECTION_LENGTH / mImageWidth;
470 float top = r.top + MIN_SELECTION_LENGTH / mImageHeight;
471 float bottom = r.bottom - MIN_SELECTION_LENGTH / mImageHeight;
472 if ((mMovingEdges & MOVE_RIGHT) != 0) {
473 r.right = Utils.clamp(point.x, left, 1f);
475 if ((mMovingEdges & MOVE_LEFT) != 0) {
476 r.left = Utils.clamp(point.x, 0, right);
478 if ((mMovingEdges & MOVE_TOP) != 0) {
479 r.top = Utils.clamp(point.y, 0, bottom);
481 if ((mMovingEdges & MOVE_BOTTOM) != 0) {
482 r.bottom = Utils.clamp(point.y, top, 1f);
484 if (mAspectRatio != UNSPECIFIED) {
485 float targetRatio = mAspectRatio * mImageHeight / mImageWidth;
486 if (r.width() / r.height() > targetRatio) {
487 float height = r.width() / targetRatio;
488 if ((mMovingEdges & MOVE_BOTTOM) != 0) {
489 r.bottom = Utils.clamp(r.top + height, top, 1f);
491 r.top = Utils.clamp(r.bottom - height, 0, bottom);
494 float width = r.height() * targetRatio;
495 if ((mMovingEdges & MOVE_LEFT) != 0) {
496 r.left = Utils.clamp(r.right - width, 0, right);
498 r.right = Utils.clamp(r.left + width, left, 1f);
501 if (r.width() / r.height() > targetRatio) {
502 float width = r.height() * targetRatio;
503 if ((mMovingEdges & MOVE_LEFT) != 0) {
504 r.left = Utils.clamp(r.right - width, 0, right);
506 r.right = Utils.clamp(r.left + width, left, 1f);
509 float height = r.width() / targetRatio;
510 if ((mMovingEdges & MOVE_BOTTOM) != 0) {
511 r.bottom = Utils.clamp(r.top + height, top, 1f);
513 r.top = Utils.clamp(r.bottom - height, 0, bottom);
521 private void setMovingEdges(MotionEvent event) {
522 RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
523 float x = event.getX();
524 float y = event.getY();
526 if (x > r.left + TOUCH_TOLERANCE && x < r.right - TOUCH_TOLERANCE
527 && y > r.top + TOUCH_TOLERANCE && y < r.bottom - TOUCH_TOLERANCE) {
528 mMovingEdges = MOVE_BLOCK;
532 boolean inVerticalRange = (r.top - TOUCH_TOLERANCE) <= y
533 && y <= (r.bottom + TOUCH_TOLERANCE);
534 boolean inHorizontalRange = (r.left - TOUCH_TOLERANCE) <= x
535 && x <= (r.right + TOUCH_TOLERANCE);
537 if (inVerticalRange) {
538 boolean left = Math.abs(x - r.left) <= TOUCH_TOLERANCE;
539 boolean right = Math.abs(x - r.right) <= TOUCH_TOLERANCE;
541 left = Math.abs(x - r.left) < Math.abs(x - r.right);
544 if (left) mMovingEdges |= MOVE_LEFT;
545 if (right) mMovingEdges |= MOVE_RIGHT;
546 if (mAspectRatio != UNSPECIFIED && inHorizontalRange) {
548 (r.top + r.bottom) / 2) ? MOVE_BOTTOM : MOVE_TOP;
551 if (inHorizontalRange) {
552 boolean top = Math.abs(y - r.top) <= TOUCH_TOLERANCE;
553 boolean bottom = Math.abs(y - r.bottom) <= TOUCH_TOLERANCE;
555 top = Math.abs(y - r.top) < Math.abs(y - r.bottom);
558 if (top) mMovingEdges |= MOVE_TOP;
559 if (bottom) mMovingEdges |= MOVE_BOTTOM;
560 if (mAspectRatio != UNSPECIFIED && inVerticalRange) {
562 (r.left + r.right) / 2) ? MOVE_RIGHT : MOVE_LEFT;
568 protected boolean onTouch(MotionEvent event) {
569 switch (event.getAction()) {
570 case MotionEvent.ACTION_DOWN: {
571 mReferenceX = event.getX();
572 mReferenceY = event.getY();
573 setMovingEdges(event);
577 case MotionEvent.ACTION_MOVE:
580 case MotionEvent.ACTION_CANCEL:
581 case MotionEvent.ACTION_UP: {
583 mAnimation.startParkingAnimation(mHighlightRect);
592 protected void renderBackground(GLCanvas canvas) {
593 RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
594 drawHighlightRectangle(canvas, r);
596 float centerY = (r.top + r.bottom) / 2;
597 float centerX = (r.left + r.right) / 2;
598 boolean notMoving = mMovingEdges == 0;
599 if ((mMovingEdges & MOVE_RIGHT) != 0 || notMoving) {
601 Math.round(r.right - mArrow.getWidth() / 2),
602 Math.round(centerY - mArrow.getHeight() / 2));
604 if ((mMovingEdges & MOVE_LEFT) != 0 || notMoving) {
606 Math.round(r.left - mArrow.getWidth() / 2),
607 Math.round(centerY - mArrow.getHeight() / 2));
609 if ((mMovingEdges & MOVE_TOP) != 0 || notMoving) {
611 Math.round(centerX - mArrow.getWidth() / 2),
612 Math.round(r.top - mArrow.getHeight() / 2));
614 if ((mMovingEdges & MOVE_BOTTOM) != 0 || notMoving) {
616 Math.round(centerX - mArrow.getWidth() / 2),
617 Math.round(r.bottom - mArrow.getHeight() / 2));
621 private void drawHighlightRectangle(GLCanvas canvas, RectF r) {
622 GL11 gl = canvas.getGLInstance();
623 gl.glLineWidth(3.0f);
624 gl.glEnable(GL11.GL_LINE_SMOOTH);
626 gl.glEnable(GL11.GL_STENCIL_TEST);
627 gl.glClear(GL11.GL_STENCIL_BUFFER_BIT);
628 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
629 gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
631 if (mSpotlightRatioX == 0 || mSpotlightRatioY == 0) {
632 canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT);
633 canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint);
635 float sx = r.width() * mSpotlightRatioX;
636 float sy = r.height() * mSpotlightRatioY;
637 float cx = r.centerX();
638 float cy = r.centerY();
640 canvas.fillRect(cx - sx / 2, cy - sy / 2, sx, sy, Color.TRANSPARENT);
641 canvas.drawRect(cx - sx / 2, cy - sy / 2, sx, sy, mPaint);
642 canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint);
644 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
645 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
647 canvas.drawRect(cx - sy / 2, cy - sx / 2, sy, sx, mPaint);
648 canvas.fillRect(cx - sy / 2, cy - sx / 2, sy, sx, Color.TRANSPARENT);
649 canvas.fillRect(r.left, r.top, r.width(), r.height(), 0x80000000);
652 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
653 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
655 canvas.fillRect(0, 0, getWidth(), getHeight(), 0xA0000000);
657 gl.glDisable(GL11.GL_STENCIL_TEST);
661 private class DetectFaceTask extends Thread {
662 private final FaceDetector.Face[] mFaces = new FaceDetector.Face[MAX_FACE_COUNT];
663 private final Bitmap mFaceBitmap;
664 private int mFaceCount;
666 public DetectFaceTask(Bitmap bitmap) {
667 mFaceBitmap = bitmap;
668 setName("face-detect");
673 Bitmap bitmap = mFaceBitmap;
674 FaceDetector detector = new FaceDetector(
675 bitmap.getWidth(), bitmap.getHeight(), MAX_FACE_COUNT);
676 mFaceCount = detector.findFaces(bitmap, mFaces);
677 mMainHandler.sendMessage(
678 mMainHandler.obtainMessage(MSG_UPDATE_FACES, this));
681 private RectF getFaceRect(FaceDetector.Face face) {
682 PointF point = new PointF();
683 face.getMidPoint(point);
685 int width = mFaceBitmap.getWidth();
686 int height = mFaceBitmap.getHeight();
687 float rx = face.eyesDistance() * FACE_EYE_RATIO;
689 float aspect = mAspectRatio;
690 if (aspect != UNSPECIFIED) {
699 point.x - rx, point.y - ry, point.x + rx, point.y + ry);
700 r.intersect(0, 0, width, height);
702 if (aspect != UNSPECIFIED) {
703 if (r.width() / r.height() > aspect) {
704 float w = r.height() * aspect;
705 r.left = (r.left + r.right - w) * 0.5f;
706 r.right = r.left + w;
708 float h = r.width() / aspect;
709 r.top = (r.top + r.bottom - h) * 0.5f;
710 r.bottom = r.top + h;
721 public void updateFaces() {
722 if (mFaceCount > 1) {
723 for (int i = 0, n = mFaceCount; i < n; ++i) {
724 mFaceDetectionView.addFace(getFaceRect(mFaces[i]));
726 mFaceDetectionView.setVisibility(GLView.VISIBLE);
727 Toast.makeText(mActivity.getAndroidContext(),
728 R.string.multiface_crop_help, Toast.LENGTH_SHORT).show();
729 } else if (mFaceCount == 1) {
730 mFaceDetectionView.setVisibility(GLView.INVISIBLE);
731 mHighlightRectangle.setRectangle(getFaceRect(mFaces[0]));
732 mHighlightRectangle.setVisibility(GLView.VISIBLE);
733 } else /*mFaceCount == 0*/ {
734 mHighlightRectangle.setInitRectangle();
735 mHighlightRectangle.setVisibility(GLView.VISIBLE);
740 public void setDataModel(TileImageView.Model dataModel, int rotation) {
741 if (((rotation / 90) & 0x01) != 0) {
742 mImageWidth = dataModel.getImageHeight();
743 mImageHeight = dataModel.getImageWidth();
745 mImageWidth = dataModel.getImageWidth();
746 mImageHeight = dataModel.getImageHeight();
749 mImageRotation = rotation;
751 mImageView.setModel(dataModel);
752 mAnimation.initialize();
755 public void detectFaces(Bitmap bitmap) {
756 int rotation = mImageRotation;
757 int width = bitmap.getWidth();
758 int height = bitmap.getHeight();
759 float scale = (float) Math.sqrt(
760 (double) FACE_PIXEL_COUNT / (width * height));
762 // faceBitmap is a correctly rotated bitmap, as viewed by a user.
764 if (((rotation / 90) & 1) == 0) {
765 int w = (Math.round(width * scale) & ~1); // must be even
766 int h = Math.round(height * scale);
767 faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
768 Canvas canvas = new Canvas(faceBitmap);
769 canvas.rotate(rotation, w / 2, h / 2);
770 canvas.scale((float) w / width, (float) h / height);
771 canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
773 int w = (Math.round(height * scale) & ~1); // must be even
774 int h = Math.round(width * scale);
775 faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
776 Canvas canvas = new Canvas(faceBitmap);
777 canvas.translate(w / 2, h / 2);
778 canvas.rotate(rotation);
779 canvas.translate(-h / 2, -w / 2);
780 canvas.scale((float) w / height, (float) h / width);
781 canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
783 new DetectFaceTask(faceBitmap).start();
786 public void initializeHighlightRectangle() {
787 mHighlightRectangle.setInitRectangle();
788 mHighlightRectangle.setVisibility(GLView.VISIBLE);
791 public void resume() {
792 mImageView.prepareTextures();
795 public void pause() {
796 mImageView.freeTextures();