• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.gallery3d.ui;
18 
19 import android.graphics.Bitmap;
20 import android.graphics.Bitmap.Config;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Paint;
24 import android.graphics.PointF;
25 import android.graphics.RectF;
26 import android.media.FaceDetector;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.util.FloatMath;
30 import android.view.MotionEvent;
31 import android.view.animation.DecelerateInterpolator;
32 import android.widget.Toast;
33 
34 import com.android.gallery3d.R;
35 import com.android.gallery3d.anim.Animation;
36 import com.android.gallery3d.app.GalleryActivity;
37 import com.android.gallery3d.common.Utils;
38 
39 import java.util.ArrayList;
40 
41 import javax.microedition.khronos.opengles.GL11;
42 
43 /**
44  * The activity can crop specific region of interest from an image.
45  */
46 public 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 
CropView(GalleryActivity activity)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 
setAspectRatio(float ratio)128     public void setAspectRatio(float ratio) {
129         mAspectRatio = ratio;
130     }
131 
setSpotlightRatio(float ratioX, float ratioY)132     public void setSpotlightRatio(float ratioX, float ratioY) {
133         mSpotlightRatioX = ratioX;
134         mSpotlightRatioY = ratioY;
135     }
136 
137     @Override
onLayout(boolean changed, int l, int t, int r, int b)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 
setImageViewPosition(int centerX, int centerY, float scale)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
render(GLCanvas canvas)169     public void render(GLCanvas canvas) {
170         AnimationController a = mAnimation;
171         if (a.calculate(AnimationTime.get())) invalidate();
172         setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale());
173         super.render(canvas);
174     }
175 
176     @Override
renderBackground(GLCanvas canvas)177     public void renderBackground(GLCanvas canvas) {
178         canvas.clearBuffer();
179     }
180 
getCropRectangle()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 
getImageWidth()189     public int getImageWidth() {
190         return mImageWidth;
191     }
192 
getImageHeight()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 
addFace(RectF faceRect)203         public void addFace(RectF faceRect) {
204             mFaces.add(faceRect);
205             invalidate();
206         }
207 
renderFace(GLCanvas canvas, RectF face, boolean pressed)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
renderBackground(GLCanvas canvas)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 
setPressedFace(int index)241         private void setPressedFace(int index) {
242             if (mPressedFaceIndex == index) return;
243             mPressedFaceIndex = index;
244             invalidate();
245         }
246 
getFaceIndexByPosition(float x, float y)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
onTouch(MotionEvent event)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 
AnimationController()292         public AnimationController() {
293             setDuration(ANIMATION_DURATION);
294             setInterpolator(new DecelerateInterpolator(4));
295         }
296 
initialize()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 
startParkingAnimation(RectF highlight)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 
parkNow(RectF highlight)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 
inverseMapPoint(PointF point)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 
mapRect(RectF input, RectF output)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
onCalculate(float progress)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 
getCenterX()365         public int getCenterX() {
366             return mCurrentX;
367         }
368 
getCenterY()369         public int getCenterY() {
370             return mCurrentY;
371         }
372 
getScale()373         public float getScale() {
374             return mCurrentScale;
375         }
376 
calculateTarget(RectF highlight)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 
420         private ResourceTexture mArrow;
421 
422         private int mMovingEdges = 0;
423         private float mReferenceX;
424         private float mReferenceY;
425 
HighlightRectangle()426         public HighlightRectangle() {
427             mArrow = new ResourceTexture(mActivity.getAndroidContext(),
428                     R.drawable.camera_crop_holo);
429         }
430 
setInitRectangle()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 
setRectangle(RectF faceRect)445         public void setRectangle(RectF faceRect) {
446             mHighlightRect.set(faceRect);
447             mAnimation.startParkingAnimation(faceRect);
448             invalidate();
449         }
450 
moveEdges(MotionEvent event)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 
setMovingEdges(MotionEvent event)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
onTouch(MotionEvent event)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
renderBackground(GLCanvas canvas)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;
600             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));
605             }
606             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));
610             }
611             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));
615             }
616             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));
620             }
621         }
622 
drawHighlightRectangle(GLCanvas canvas, RectF r)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 
DetectFaceTask(Bitmap bitmap)668         public DetectFaceTask(Bitmap bitmap) {
669             mFaceBitmap = bitmap;
670             setName("face-detect");
671         }
672 
673         @Override
run()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 
getFaceRect(FaceDetector.Face face)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 
updateFaces()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 
setDataModel(TileImageView.Model dataModel, int rotation)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 
detectFaces(Bitmap bitmap)757     public void detectFaces(Bitmap bitmap) {
758         int rotation = mImageRotation;
759         int width = bitmap.getWidth();
760         int height = bitmap.getHeight();
761         float scale = FloatMath.sqrt((float) FACE_PIXEL_COUNT / (width * height));
762 
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 
initializeHighlightRectangle()787     public void initializeHighlightRectangle() {
788         mHighlightRectangle.setInitRectangle();
789         mHighlightRectangle.setVisibility(GLView.VISIBLE);
790     }
791 
resume()792     public void resume() {
793         mImageView.prepareTextures();
794     }
795 
pause()796     public void pause() {
797         mImageView.freeTextures();
798     }
799 }
800 
801