Home | History | Annotate | Download | only in ui
      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 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;
     23 
     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;
     37 
     38 import java.util.ArrayList;
     39 import javax.microedition.khronos.opengles.GL11;
     40 
     41 /**
     42  * The activity can crop specific region of interest from an image.
     43  */
     44 public class CropView extends GLView {
     45     private static final String TAG = "CropView";
     46 
     47     private static final int FACE_PIXEL_COUNT = 120000; // around 400x300
     48 
     49     private static final int COLOR_OUTLINE = 0xFF008AFF;
     50     private static final int COLOR_FACE_OUTLINE = 0xFF000000;
     51 
     52     private static final float OUTLINE_WIDTH = 3f;
     53 
     54     private static final int SIZE_UNKNOWN = -1;
     55     private static final int TOUCH_TOLERANCE = 30;
     56 
     57     private static final float MIN_SELECTION_LENGTH = 16f;
     58     public static final float UNSPECIFIED = -1f;
     59 
     60     private static final int MAX_FACE_COUNT = 3;
     61     private static final float FACE_EYE_RATIO = 2f;
     62 
     63     private static final int ANIMATION_DURATION = 1250;
     64 
     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;
     70 
     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;
     75 
     76     private static final int MSG_UPDATE_FACES = 1;
     77 
     78     private float mAspectRatio = UNSPECIFIED;
     79     private float mSpotlightRatioX = 0;
     80     private float mSpotlightRatioY = 0;
     81 
     82     private Handler mMainHandler;
     83 
     84     private FaceHighlightView mFaceDetectionView;
     85     private HighlightRectangle mHighlightRectangle;
     86     private TileImageView mImageView;
     87     private AnimationController mAnimation = new AnimationController();
     88 
     89     private int mImageWidth = SIZE_UNKNOWN;
     90     private int mImageHeight = SIZE_UNKNOWN;
     91 
     92     private GalleryActivity mActivity;
     93 
     94     private GLPaint mPaint = new GLPaint();
     95     private GLPaint mFacePaint = new GLPaint();
     96 
     97     private int mImageRotation;
     98 
     99     public CropView(GalleryActivity activity) {
    100         mActivity = activity;
    101         mImageView = new TileImageView(activity);
    102         mFaceDetectionView = new FaceHighlightView();
    103         mHighlightRectangle = new HighlightRectangle();
    104 
    105         addComponent(mImageView);
    106         addComponent(mFaceDetectionView);
    107         addComponent(mHighlightRectangle);
    108 
    109         mHighlightRectangle.setVisibility(GLView.INVISIBLE);
    110 
    111         mPaint.setColor(COLOR_OUTLINE);
    112         mPaint.setLineWidth(OUTLINE_WIDTH);
    113 
    114         mFacePaint.setColor(COLOR_FACE_OUTLINE);
    115         mFacePaint.setLineWidth(OUTLINE_WIDTH);
    116 
    117         mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
    118             @Override
    119             public void handleMessage(Message message) {
    120                 Utils.assertTrue(message.what == MSG_UPDATE_FACES);
    121                 ((DetectFaceTask) message.obj).updateFaces();
    122             }
    123         };
    124     }
    125 
    126     public void setAspectRatio(float ratio) {
    127         mAspectRatio = ratio;
    128     }
    129 
    130     public void setSpotlightRatio(float ratioX, float ratioY) {
    131         mSpotlightRatioX = ratioX;
    132         mSpotlightRatioY = ratioY;
    133     }
    134 
    135     @Override
    136     public void onLayout(boolean changed, int l, int t, int r, int b) {
    137         int width = r - l;
    138         int height = b - t;
    139 
    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) {
    146                 mAnimation.parkNow(
    147                         mHighlightRectangle.mHighlightRect);
    148             }
    149         }
    150     }
    151 
    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;
    157         switch (rotation) {
    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));
    163         }
    164     }
    165 
    166     @Override
    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);
    172     }
    173 
    174     @Override
    175     public void renderBackground(GLCanvas canvas) {
    176         canvas.clearBuffer();
    177     }
    178 
    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);
    184         return result;
    185     }
    186 
    187     public int getImageWidth() {
    188         return mImageWidth;
    189     }
    190 
    191     public int getImageHeight() {
    192         return mImageHeight;
    193     }
    194 
    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;
    200 
    201         public void addFace(RectF faceRect) {
    202             mFaces.add(faceRect);
    203             invalidate();
    204         }
    205 
    206         private void renderFace(GLCanvas canvas, RectF face, boolean pressed) {
    207             GL11 gl = canvas.getGLInstance();
    208             if (pressed) {
    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);
    213             }
    214 
    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);
    218 
    219             if (pressed) {
    220                 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
    221             }
    222         }
    223 
    224         @Override
    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);
    229             }
    230 
    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);
    236             }
    237         }
    238 
    239         private void setPressedFace(int index) {
    240             if (mPressedFaceIndex == index) return;
    241             mPressedFaceIndex = index;
    242             invalidate();
    243         }
    244 
    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;
    250             }
    251             return INDEX_NONE;
    252         }
    253 
    254         @Override
    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));
    262                     break;
    263                 }
    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);
    272                     }
    273                 }
    274             }
    275             return true;
    276         }
    277     }
    278 
    279     private class AnimationController extends Animation {
    280         private int mCurrentX;
    281         private int mCurrentY;
    282         private float mCurrentScale;
    283         private int mStartX;
    284         private int mStartY;
    285         private float mStartScale;
    286         private int mTargetX;
    287         private int mTargetY;
    288         private float mTargetScale;
    289 
    290         public AnimationController() {
    291             setDuration(ANIMATION_DURATION);
    292             setInterpolator(new DecelerateInterpolator(4));
    293         }
    294 
    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));
    301         }
    302 
    303         public void startParkingAnimation(RectF highlight) {
    304             RectF r = mAnimation.mapRect(highlight, new RectF());
    305             int width = getWidth();
    306             int height = getHeight();
    307 
    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;
    315 
    316             mStartX = mCurrentX;
    317             mStartY = mCurrentY;
    318             mStartScale = mCurrentScale;
    319             calculateTarget(highlight);
    320             start();
    321         }
    322 
    323         public void parkNow(RectF highlight) {
    324             calculateTarget(highlight);
    325             forceStop();
    326             mStartX = mCurrentX = mTargetX;
    327             mStartY = mCurrentY = mTargetY;
    328             mStartScale = mCurrentScale = mTargetScale;
    329         }
    330 
    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);
    337         }
    338 
    339         public RectF mapRect(RectF input, RectF output) {
    340             float offsetX = getWidth() * 0.5f;
    341             float offsetY = getHeight() * 0.5f;
    342             int x = mCurrentX;
    343             int y = mCurrentY;
    344             float s = mCurrentScale;
    345             output.set(
    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);
    350             return output;
    351         }
    352 
    353         @Override
    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;
    358 
    359             if (mCurrentX == mTargetX && mCurrentY == mTargetY
    360                     && mCurrentScale == mTargetScale) forceStop();
    361         }
    362 
    363         public int getCenterX() {
    364             return mCurrentX;
    365         }
    366 
    367         public int getCenterY() {
    368             return mCurrentY;
    369         }
    370 
    371         public float getScale() {
    372             return mCurrentScale;
    373         }
    374 
    375         private void calculateTarget(RectF highlight) {
    376             float width = getWidth();
    377             float height = getHeight();
    378 
    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);
    388 
    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);
    394                 } else {
    395                     centerX = mImageWidth / 2;
    396                 }
    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);
    402                 } else {
    403                     centerY = mImageHeight / 2;
    404                 }
    405                 mTargetX = centerX;
    406                 mTargetY = centerY;
    407                 mTargetScale = scale;
    408             }
    409         }
    410 
    411     }
    412 
    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();
    417 
    418         private ResourceTexture mArrow;
    419 
    420         private int mMovingEdges = 0;
    421         private float mReferenceX;
    422         private float mReferenceY;
    423 
    424         public HighlightRectangle() {
    425             mArrow = new ResourceTexture(mActivity.getAndroidContext(),
    426                     R.drawable.camera_crop_holo);
    427         }
    428 
    429         public void setInitRectangle() {
    430             float targetRatio = mAspectRatio == UNSPECIFIED
    431                     ? 1f
    432                     : mAspectRatio * mImageHeight / mImageWidth;
    433             float w = SELECTION_RATIO / 2f;
    434             float h = SELECTION_RATIO / 2f;
    435             if (targetRatio > 1) {
    436                 h = w / targetRatio;
    437             } else {
    438                 w = h * targetRatio;
    439             }
    440             mHighlightRect.set(0.5f - w, 0.5f - h, 0.5f + w, 0.5f + h);
    441         }
    442 
    443         public void setRectangle(RectF faceRect) {
    444             mHighlightRect.set(faceRect);
    445             mAnimation.startParkingAnimation(faceRect);
    446             invalidate();
    447         }
    448 
    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;
    456 
    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);
    460                 r.top += dy;
    461                 r.bottom += dy;
    462                 r.left += dx;
    463                 r.right += dx;
    464             } else {
    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);
    474                 }
    475                 if ((mMovingEdges & MOVE_LEFT) != 0) {
    476                     r.left = Utils.clamp(point.x, 0, right);
    477                 }
    478                 if ((mMovingEdges & MOVE_TOP) != 0) {
    479                     r.top = Utils.clamp(point.y, 0, bottom);
    480                 }
    481                 if ((mMovingEdges & MOVE_BOTTOM) != 0) {
    482                     r.bottom = Utils.clamp(point.y, top, 1f);
    483                 }
    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);
    490                         } else {
    491                             r.top = Utils.clamp(r.bottom - height, 0, bottom);
    492                         }
    493                     } else {
    494                         float width = r.height() * targetRatio;
    495                         if ((mMovingEdges & MOVE_LEFT) != 0) {
    496                             r.left = Utils.clamp(r.right - width, 0, right);
    497                         } else {
    498                             r.right = Utils.clamp(r.left + width, left, 1f);
    499                         }
    500                     }
    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);
    505                         } else {
    506                             r.right = Utils.clamp(r.left + width, left, 1f);
    507                         }
    508                     } else {
    509                         float height = r.width() / targetRatio;
    510                         if ((mMovingEdges & MOVE_BOTTOM) != 0) {
    511                             r.bottom = Utils.clamp(r.top + height, top, 1f);
    512                         } else {
    513                             r.top = Utils.clamp(r.bottom - height, 0, bottom);
    514                         }
    515                     }
    516                 }
    517             }
    518             invalidate();
    519         }
    520 
    521         private void setMovingEdges(MotionEvent event) {
    522             RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
    523             float x = event.getX();
    524             float y = event.getY();
    525 
    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;
    529                 return;
    530             }
    531 
    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);
    536 
    537             if (inVerticalRange) {
    538                 boolean left = Math.abs(x - r.left) <= TOUCH_TOLERANCE;
    539                 boolean right = Math.abs(x - r.right) <= TOUCH_TOLERANCE;
    540                 if (left && right) {
    541                     left = Math.abs(x - r.left) < Math.abs(x - r.right);
    542                     right = !left;
    543                 }
    544                 if (left) mMovingEdges |= MOVE_LEFT;
    545                 if (right) mMovingEdges |= MOVE_RIGHT;
    546                 if (mAspectRatio != UNSPECIFIED && inHorizontalRange) {
    547                     mMovingEdges |= (y >
    548                             (r.top + r.bottom) / 2) ? MOVE_BOTTOM : MOVE_TOP;
    549                 }
    550             }
    551             if (inHorizontalRange) {
    552                 boolean top = Math.abs(y - r.top) <= TOUCH_TOLERANCE;
    553                 boolean bottom = Math.abs(y - r.bottom) <= TOUCH_TOLERANCE;
    554                 if (top && bottom) {
    555                     top = Math.abs(y - r.top) < Math.abs(y - r.bottom);
    556                     bottom = !top;
    557                 }
    558                 if (top) mMovingEdges |= MOVE_TOP;
    559                 if (bottom) mMovingEdges |= MOVE_BOTTOM;
    560                 if (mAspectRatio != UNSPECIFIED && inVerticalRange) {
    561                     mMovingEdges |= (x >
    562                             (r.left + r.right) / 2) ? MOVE_RIGHT : MOVE_LEFT;
    563                 }
    564             }
    565         }
    566 
    567         @Override
    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);
    574                     invalidate();
    575                     return true;
    576                 }
    577                 case MotionEvent.ACTION_MOVE:
    578                     moveEdges(event);
    579                     break;
    580                 case MotionEvent.ACTION_CANCEL:
    581                 case MotionEvent.ACTION_UP: {
    582                     mMovingEdges = 0;
    583                     mAnimation.startParkingAnimation(mHighlightRect);
    584                     invalidate();
    585                     return true;
    586                 }
    587             }
    588             return true;
    589         }
    590 
    591         @Override
    592         protected void renderBackground(GLCanvas canvas) {
    593             RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
    594             drawHighlightRectangle(canvas, r);
    595 
    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) {
    600                 mArrow.draw(canvas,
    601                         Math.round(r.right - mArrow.getWidth() / 2),
    602                         Math.round(centerY - mArrow.getHeight() / 2));
    603             }
    604             if ((mMovingEdges & MOVE_LEFT) != 0 || notMoving) {
    605                 mArrow.draw(canvas,
    606                         Math.round(r.left - mArrow.getWidth() / 2),
    607                         Math.round(centerY - mArrow.getHeight() / 2));
    608             }
    609             if ((mMovingEdges & MOVE_TOP) != 0 || notMoving) {
    610                 mArrow.draw(canvas,
    611                         Math.round(centerX - mArrow.getWidth() / 2),
    612                         Math.round(r.top - mArrow.getHeight() / 2));
    613             }
    614             if ((mMovingEdges & MOVE_BOTTOM) != 0 || notMoving) {
    615                 mArrow.draw(canvas,
    616                         Math.round(centerX - mArrow.getWidth() / 2),
    617                         Math.round(r.bottom - mArrow.getHeight() / 2));
    618             }
    619         }
    620 
    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);
    625 
    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);
    630 
    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);
    634             } else {
    635                 float sx = r.width() * mSpotlightRatioX;
    636                 float sy = r.height() * mSpotlightRatioY;
    637                 float cx = r.centerX();
    638                 float cy = r.centerY();
    639 
    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);
    643 
    644                 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
    645                 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
    646 
    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);
    650             }
    651 
    652             gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
    653             gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
    654 
    655             canvas.fillRect(0, 0, getWidth(), getHeight(), 0xA0000000);
    656 
    657             gl.glDisable(GL11.GL_STENCIL_TEST);
    658         }
    659     }
    660 
    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;
    665 
    666         public DetectFaceTask(Bitmap bitmap) {
    667             mFaceBitmap = bitmap;
    668             setName("face-detect");
    669         }
    670 
    671         @Override
    672         public void run() {
    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));
    679         }
    680 
    681         private RectF getFaceRect(FaceDetector.Face face) {
    682             PointF point = new PointF();
    683             face.getMidPoint(point);
    684 
    685             int width = mFaceBitmap.getWidth();
    686             int height = mFaceBitmap.getHeight();
    687             float rx = face.eyesDistance() * FACE_EYE_RATIO;
    688             float ry = rx;
    689             float aspect = mAspectRatio;
    690             if (aspect != UNSPECIFIED) {
    691                 if (aspect > 1) {
    692                     rx = ry * aspect;
    693                 } else {
    694                     ry = rx / aspect;
    695                 }
    696             }
    697 
    698             RectF r = new RectF(
    699                     point.x - rx, point.y - ry, point.x + rx, point.y + ry);
    700             r.intersect(0, 0, width, height);
    701 
    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;
    707                 } else {
    708                     float h = r.width() / aspect;
    709                     r.top =  (r.top + r.bottom - h) * 0.5f;
    710                     r.bottom = r.top + h;
    711                 }
    712             }
    713 
    714             r.left /= width;
    715             r.right /= width;
    716             r.top /= height;
    717             r.bottom /= height;
    718             return r;
    719         }
    720 
    721         public void updateFaces() {
    722             if (mFaceCount > 1) {
    723                 for (int i = 0, n = mFaceCount; i < n; ++i) {
    724                     mFaceDetectionView.addFace(getFaceRect(mFaces[i]));
    725                 }
    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);
    736             }
    737         }
    738     }
    739 
    740     public void setDataModel(TileImageView.Model dataModel, int rotation) {
    741         if (((rotation / 90) & 0x01) != 0) {
    742             mImageWidth = dataModel.getImageHeight();
    743             mImageHeight = dataModel.getImageWidth();
    744         } else {
    745             mImageWidth = dataModel.getImageWidth();
    746             mImageHeight = dataModel.getImageHeight();
    747         }
    748 
    749         mImageRotation = rotation;
    750 
    751         mImageView.setModel(dataModel);
    752         mAnimation.initialize();
    753     }
    754 
    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));
    761 
    762         // faceBitmap is a correctly rotated bitmap, as viewed by a user.
    763         Bitmap faceBitmap;
    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));
    772         } else {
    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));
    782         }
    783         new DetectFaceTask(faceBitmap).start();
    784     }
    785 
    786     public void initializeHighlightRectangle() {
    787         mHighlightRectangle.setInitRectangle();
    788         mHighlightRectangle.setVisibility(GLView.VISIBLE);
    789     }
    790 
    791     public void resume() {
    792         mImageView.prepareTextures();
    793     }
    794 
    795     public void pause() {
    796         mImageView.freeTextures();
    797     }
    798 }
    799 
    800