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 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 
    101     public CropView(GalleryActivity activity) {
    102         mActivity = activity;
    103         mImageView = new TileImageView(activity);
    104         mFaceDetectionView = new FaceHighlightView();
    105         mHighlightRectangle = new HighlightRectangle();
    106 
    107         addComponent(mImageView);
    108         addComponent(mFaceDetectionView);
    109         addComponent(mHighlightRectangle);
    110 
    111         mHighlightRectangle.setVisibility(GLView.INVISIBLE);
    112 
    113         mPaint.setColor(COLOR_OUTLINE);
    114         mPaint.setLineWidth(OUTLINE_WIDTH);
    115 
    116         mFacePaint.setColor(COLOR_FACE_OUTLINE);
    117         mFacePaint.setLineWidth(OUTLINE_WIDTH);
    118 
    119         mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
    120             @Override
    121             public void handleMessage(Message message) {
    122                 Utils.assertTrue(message.what == MSG_UPDATE_FACES);
    123                 ((DetectFaceTask) message.obj).updateFaces();
    124             }
    125         };
    126     }
    127 
    128     public void setAspectRatio(float ratio) {
    129         mAspectRatio = ratio;
    130     }
    131 
    132     public void setSpotlightRatio(float ratioX, float ratioY) {
    133         mSpotlightRatioX = ratioX;
    134         mSpotlightRatioY = ratioY;
    135     }
    136 
    137     @Override
    138     public void onLayout(boolean changed, int l, int t, int r, int b) {
    139         int width = r - l;
    140         int height = b - t;
    141 
    142         mFaceDetectionView.layout(0, 0, width, height);
    143         mHighlightRectangle.layout(0, 0, width, height);
    144         mImageView.layout(0, 0, width, height);
    145         if (mImageHeight != SIZE_UNKNOWN) {
    146             mAnimation.initialize();
    147             if (mHighlightRectangle.getVisibility() == GLView.VISIBLE) {
    148                 mAnimation.parkNow(
    149                         mHighlightRectangle.mHighlightRect);
    150             }
    151         }
    152     }
    153 
    154     private boolean setImageViewPosition(int centerX, int centerY, float scale) {
    155         int inverseX = mImageWidth - centerX;
    156         int inverseY = mImageHeight - centerY;
    157         TileImageView t = mImageView;
    158         int rotation = mImageRotation;
    159         switch (rotation) {
    160             case 0: return t.setPosition(centerX, centerY, scale, 0);
    161             case 90: return t.setPosition(centerY, inverseX, scale, 90);
    162             case 180: return t.setPosition(inverseX, inverseY, scale, 180);
    163             case 270: return t.setPosition(inverseY, centerX, scale, 270);
    164             default: throw new IllegalArgumentException(String.valueOf(rotation));
    165         }
    166     }
    167 
    168     @Override
    169     public void render(GLCanvas canvas) {
    170         AnimationController a = mAnimation;
    171         if (a.calculate(AnimationTime.get())) invalidate();
    172         setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale());
    173         super.render(canvas);
    174     }
    175 
    176     @Override
    177     public void renderBackground(GLCanvas canvas) {
    178         canvas.clearBuffer();
    179     }
    180 
    181     public RectF getCropRectangle() {
    182         if (mHighlightRectangle.getVisibility() == GLView.INVISIBLE) return null;
    183         RectF rect = mHighlightRectangle.mHighlightRect;
    184         RectF result = new RectF(rect.left * mImageWidth, rect.top * mImageHeight,
    185                 rect.right * mImageWidth, rect.bottom * mImageHeight);
    186         return result;
    187     }
    188 
    189     public int getImageWidth() {
    190         return mImageWidth;
    191     }
    192 
    193     public int getImageHeight() {
    194         return mImageHeight;
    195     }
    196 
    197     private class FaceHighlightView extends GLView {
    198         private static final int INDEX_NONE = -1;
    199         private ArrayList<RectF> mFaces = new ArrayList<RectF>();
    200         private RectF mRect = new RectF();
    201         private int mPressedFaceIndex = INDEX_NONE;
    202 
    203         public void addFace(RectF faceRect) {
    204             mFaces.add(faceRect);
    205             invalidate();
    206         }
    207 
    208         private void renderFace(GLCanvas canvas, RectF face, boolean pressed) {
    209             GL11 gl = canvas.getGLInstance();
    210             if (pressed) {
    211                 gl.glEnable(GL11.GL_STENCIL_TEST);
    212                 gl.glClear(GL11.GL_STENCIL_BUFFER_BIT);
    213                 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
    214                 gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
    215             }
    216 
    217             RectF r = mAnimation.mapRect(face, mRect);
    218             canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT);
    219             canvas.drawRect(r.left, r.top, r.width(), r.height(), mFacePaint);
    220 
    221             if (pressed) {
    222                 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
    223             }
    224         }
    225 
    226         @Override
    227         protected void renderBackground(GLCanvas canvas) {
    228             ArrayList<RectF> faces = mFaces;
    229             for (int i = 0, n = faces.size(); i < n; ++i) {
    230                 renderFace(canvas, faces.get(i), i == mPressedFaceIndex);
    231             }
    232 
    233             GL11 gl = canvas.getGLInstance();
    234             if (mPressedFaceIndex != INDEX_NONE) {
    235                 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
    236                 canvas.fillRect(0, 0, getWidth(), getHeight(), 0x66000000);
    237                 gl.glDisable(GL11.GL_STENCIL_TEST);
    238             }
    239         }
    240 
    241         private void setPressedFace(int index) {
    242             if (mPressedFaceIndex == index) return;
    243             mPressedFaceIndex = index;
    244             invalidate();
    245         }
    246 
    247         private int getFaceIndexByPosition(float x, float y) {
    248             ArrayList<RectF> faces = mFaces;
    249             for (int i = 0, n = faces.size(); i < n; ++i) {
    250                 RectF r = mAnimation.mapRect(faces.get(i), mRect);
    251                 if (r.contains(x, y)) return i;
    252             }
    253             return INDEX_NONE;
    254         }
    255 
    256         @Override
    257         protected boolean onTouch(MotionEvent event) {
    258             float x = event.getX();
    259             float y = event.getY();
    260             switch (event.getAction()) {
    261                 case MotionEvent.ACTION_DOWN:
    262                 case MotionEvent.ACTION_MOVE: {
    263                     setPressedFace(getFaceIndexByPosition(x, y));
    264                     break;
    265                 }
    266                 case MotionEvent.ACTION_CANCEL:
    267                 case MotionEvent.ACTION_UP: {
    268                     int index = mPressedFaceIndex;
    269                     setPressedFace(INDEX_NONE);
    270                     if (index != INDEX_NONE) {
    271                         mHighlightRectangle.setRectangle(mFaces.get(index));
    272                         mHighlightRectangle.setVisibility(GLView.VISIBLE);
    273                         setVisibility(GLView.INVISIBLE);
    274                     }
    275                 }
    276             }
    277             return true;
    278         }
    279     }
    280 
    281     private class AnimationController extends Animation {
    282         private int mCurrentX;
    283         private int mCurrentY;
    284         private float mCurrentScale;
    285         private int mStartX;
    286         private int mStartY;
    287         private float mStartScale;
    288         private int mTargetX;
    289         private int mTargetY;
    290         private float mTargetScale;
    291 
    292         public AnimationController() {
    293             setDuration(ANIMATION_DURATION);
    294             setInterpolator(new DecelerateInterpolator(4));
    295         }
    296 
    297         public void initialize() {
    298             mCurrentX = mImageWidth / 2;
    299             mCurrentY = mImageHeight / 2;
    300             mCurrentScale = Math.min(2, Math.min(
    301                     (float) getWidth() / mImageWidth,
    302                     (float) getHeight() / mImageHeight));
    303         }
    304 
    305         public void startParkingAnimation(RectF highlight) {
    306             RectF r = mAnimation.mapRect(highlight, new RectF());
    307             int width = getWidth();
    308             int height = getHeight();
    309 
    310             float wr = r.width() / width;
    311             float hr = r.height() / height;
    312             final int d = ANIMATION_TRIGGER;
    313             if (wr >= MIN_SELECTION_RATIO && wr < MAX_SELECTION_RATIO
    314                     && hr >= MIN_SELECTION_RATIO && hr < MAX_SELECTION_RATIO
    315                     && r.left >= d && r.right < width - d
    316                     && r.top >= d && r.bottom < height - d) return;
    317 
    318             mStartX = mCurrentX;
    319             mStartY = mCurrentY;
    320             mStartScale = mCurrentScale;
    321             calculateTarget(highlight);
    322             start();
    323         }
    324 
    325         public void parkNow(RectF highlight) {
    326             calculateTarget(highlight);
    327             forceStop();
    328             mStartX = mCurrentX = mTargetX;
    329             mStartY = mCurrentY = mTargetY;
    330             mStartScale = mCurrentScale = mTargetScale;
    331         }
    332 
    333         public void inverseMapPoint(PointF point) {
    334             float s = mCurrentScale;
    335             point.x = Utils.clamp(((point.x - getWidth() * 0.5f) / s
    336                     + mCurrentX) / mImageWidth, 0, 1);
    337             point.y = Utils.clamp(((point.y - getHeight() * 0.5f) / s
    338                     + mCurrentY) / mImageHeight, 0, 1);
    339         }
    340 
    341         public RectF mapRect(RectF input, RectF output) {
    342             float offsetX = getWidth() * 0.5f;
    343             float offsetY = getHeight() * 0.5f;
    344             int x = mCurrentX;
    345             int y = mCurrentY;
    346             float s = mCurrentScale;
    347             output.set(
    348                     offsetX + (input.left * mImageWidth - x) * s,
    349                     offsetY + (input.top * mImageHeight - y) * s,
    350                     offsetX + (input.right * mImageWidth - x) * s,
    351                     offsetY + (input.bottom * mImageHeight - y) * s);
    352             return output;
    353         }
    354 
    355         @Override
    356         protected void onCalculate(float progress) {
    357             mCurrentX = Math.round(mStartX + (mTargetX - mStartX) * progress);
    358             mCurrentY = Math.round(mStartY + (mTargetY - mStartY) * progress);
    359             mCurrentScale = mStartScale + (mTargetScale - mStartScale) * progress;
    360 
    361             if (mCurrentX == mTargetX && mCurrentY == mTargetY
    362                     && mCurrentScale == mTargetScale) forceStop();
    363         }
    364 
    365         public int getCenterX() {
    366             return mCurrentX;
    367         }
    368 
    369         public int getCenterY() {
    370             return mCurrentY;
    371         }
    372 
    373         public float getScale() {
    374             return mCurrentScale;
    375         }
    376 
    377         private void calculateTarget(RectF highlight) {
    378             float width = getWidth();
    379             float height = getHeight();
    380 
    381             if (mImageWidth != SIZE_UNKNOWN) {
    382                 float minScale = Math.min(width / mImageWidth, height / mImageHeight);
    383                 float scale = Utils.clamp(SELECTION_RATIO * Math.min(
    384                         width / (highlight.width() * mImageWidth),
    385                         height / (highlight.height() * mImageHeight)), minScale, 2f);
    386                 int centerX = Math.round(
    387                         mImageWidth * (highlight.left + highlight.right) * 0.5f);
    388                 int centerY = Math.round(
    389                         mImageHeight * (highlight.top + highlight.bottom) * 0.5f);
    390 
    391                 if (Math.round(mImageWidth * scale) > width) {
    392                     int limitX = Math.round(width * 0.5f / scale);
    393                     centerX = Math.round(
    394                             (highlight.left + highlight.right) * mImageWidth / 2);
    395                     centerX = Utils.clamp(centerX, limitX, mImageWidth - limitX);
    396                 } else {
    397                     centerX = mImageWidth / 2;
    398                 }
    399                 if (Math.round(mImageHeight * scale) > height) {
    400                     int limitY = Math.round(height * 0.5f / scale);
    401                     centerY = Math.round(
    402                             (highlight.top + highlight.bottom) * mImageHeight / 2);
    403                     centerY = Utils.clamp(centerY, limitY, mImageHeight - limitY);
    404                 } else {
    405                     centerY = mImageHeight / 2;
    406                 }
    407                 mTargetX = centerX;
    408                 mTargetY = centerY;
    409                 mTargetScale = scale;
    410             }
    411         }
    412 
    413     }
    414 
    415     private class HighlightRectangle extends GLView {
    416         private RectF mHighlightRect = new RectF(0.25f, 0.25f, 0.75f, 0.75f);
    417         private RectF mTempRect = new RectF();
    418         private PointF mTempPoint = new PointF();
    419 
    420         private ResourceTexture mArrow;
    421 
    422         private int mMovingEdges = 0;
    423         private float mReferenceX;
    424         private float mReferenceY;
    425 
    426         public HighlightRectangle() {
    427             mArrow = new ResourceTexture(mActivity.getAndroidContext(),
    428                     R.drawable.camera_crop_holo);
    429         }
    430 
    431         public void setInitRectangle() {
    432             float targetRatio = mAspectRatio == UNSPECIFIED
    433                     ? 1f
    434                     : mAspectRatio * mImageHeight / mImageWidth;
    435             float w = SELECTION_RATIO / 2f;
    436             float h = SELECTION_RATIO / 2f;
    437             if (targetRatio > 1) {
    438                 h = w / targetRatio;
    439             } else {
    440                 w = h * targetRatio;
    441             }
    442             mHighlightRect.set(0.5f - w, 0.5f - h, 0.5f + w, 0.5f + h);
    443         }
    444 
    445         public void setRectangle(RectF faceRect) {
    446             mHighlightRect.set(faceRect);
    447             mAnimation.startParkingAnimation(faceRect);
    448             invalidate();
    449         }
    450 
    451         private void moveEdges(MotionEvent event) {
    452             float scale = mAnimation.getScale();
    453             float dx = (event.getX() - mReferenceX) / scale / mImageWidth;
    454             float dy = (event.getY() - mReferenceY) / scale / mImageHeight;
    455             mReferenceX = event.getX();
    456             mReferenceY = event.getY();
    457             RectF r = mHighlightRect;
    458 
    459             if ((mMovingEdges & MOVE_BLOCK) != 0) {
    460                 dx = Utils.clamp(dx, -r.left,  1 - r.right);
    461                 dy = Utils.clamp(dy, -r.top , 1 - r.bottom);
    462                 r.top += dy;
    463                 r.bottom += dy;
    464                 r.left += dx;
    465                 r.right += dx;
    466             } else {
    467                 PointF point = mTempPoint;
    468                 point.set(mReferenceX, mReferenceY);
    469                 mAnimation.inverseMapPoint(point);
    470                 float left = r.left + MIN_SELECTION_LENGTH / mImageWidth;
    471                 float right = r.right - MIN_SELECTION_LENGTH / mImageWidth;
    472                 float top = r.top + MIN_SELECTION_LENGTH / mImageHeight;
    473                 float bottom = r.bottom - MIN_SELECTION_LENGTH / mImageHeight;
    474                 if ((mMovingEdges & MOVE_RIGHT) != 0) {
    475                     r.right = Utils.clamp(point.x, left, 1f);
    476                 }
    477                 if ((mMovingEdges & MOVE_LEFT) != 0) {
    478                     r.left = Utils.clamp(point.x, 0, right);
    479                 }
    480                 if ((mMovingEdges & MOVE_TOP) != 0) {
    481                     r.top = Utils.clamp(point.y, 0, bottom);
    482                 }
    483                 if ((mMovingEdges & MOVE_BOTTOM) != 0) {
    484                     r.bottom = Utils.clamp(point.y, top, 1f);
    485                 }
    486                 if (mAspectRatio != UNSPECIFIED) {
    487                     float targetRatio = mAspectRatio * mImageHeight / mImageWidth;
    488                     if (r.width() / r.height() > targetRatio) {
    489                         float height = r.width() / targetRatio;
    490                         if ((mMovingEdges & MOVE_BOTTOM) != 0) {
    491                             r.bottom = Utils.clamp(r.top + height, top, 1f);
    492                         } else {
    493                             r.top = Utils.clamp(r.bottom - height, 0, bottom);
    494                         }
    495                     } else {
    496                         float width = r.height() * targetRatio;
    497                         if ((mMovingEdges & MOVE_LEFT) != 0) {
    498                             r.left = Utils.clamp(r.right - width, 0, right);
    499                         } else {
    500                             r.right = Utils.clamp(r.left + width, left, 1f);
    501                         }
    502                     }
    503                     if (r.width() / r.height() > targetRatio) {
    504                         float width = r.height() * targetRatio;
    505                         if ((mMovingEdges & MOVE_LEFT) != 0) {
    506                             r.left = Utils.clamp(r.right - width, 0, right);
    507                         } else {
    508                             r.right = Utils.clamp(r.left + width, left, 1f);
    509                         }
    510                     } else {
    511                         float height = r.width() / targetRatio;
    512                         if ((mMovingEdges & MOVE_BOTTOM) != 0) {
    513                             r.bottom = Utils.clamp(r.top + height, top, 1f);
    514                         } else {
    515                             r.top = Utils.clamp(r.bottom - height, 0, bottom);
    516                         }
    517                     }
    518                 }
    519             }
    520             invalidate();
    521         }
    522 
    523         private void setMovingEdges(MotionEvent event) {
    524             RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
    525             float x = event.getX();
    526             float y = event.getY();
    527 
    528             if (x > r.left + TOUCH_TOLERANCE && x < r.right - TOUCH_TOLERANCE
    529                     && y > r.top + TOUCH_TOLERANCE && y < r.bottom - TOUCH_TOLERANCE) {
    530                 mMovingEdges = MOVE_BLOCK;
    531                 return;
    532             }
    533 
    534             boolean inVerticalRange = (r.top - TOUCH_TOLERANCE) <= y
    535                     && y <= (r.bottom + TOUCH_TOLERANCE);
    536             boolean inHorizontalRange = (r.left - TOUCH_TOLERANCE) <= x
    537                     && x <= (r.right + TOUCH_TOLERANCE);
    538 
    539             if (inVerticalRange) {
    540                 boolean left = Math.abs(x - r.left) <= TOUCH_TOLERANCE;
    541                 boolean right = Math.abs(x - r.right) <= TOUCH_TOLERANCE;
    542                 if (left && right) {
    543                     left = Math.abs(x - r.left) < Math.abs(x - r.right);
    544                     right = !left;
    545                 }
    546                 if (left) mMovingEdges |= MOVE_LEFT;
    547                 if (right) mMovingEdges |= MOVE_RIGHT;
    548                 if (mAspectRatio != UNSPECIFIED && inHorizontalRange) {
    549                     mMovingEdges |= (y >
    550                             (r.top + r.bottom) / 2) ? MOVE_BOTTOM : MOVE_TOP;
    551                 }
    552             }
    553             if (inHorizontalRange) {
    554                 boolean top = Math.abs(y - r.top) <= TOUCH_TOLERANCE;
    555                 boolean bottom = Math.abs(y - r.bottom) <= TOUCH_TOLERANCE;
    556                 if (top && bottom) {
    557                     top = Math.abs(y - r.top) < Math.abs(y - r.bottom);
    558                     bottom = !top;
    559                 }
    560                 if (top) mMovingEdges |= MOVE_TOP;
    561                 if (bottom) mMovingEdges |= MOVE_BOTTOM;
    562                 if (mAspectRatio != UNSPECIFIED && inVerticalRange) {
    563                     mMovingEdges |= (x >
    564                             (r.left + r.right) / 2) ? MOVE_RIGHT : MOVE_LEFT;
    565                 }
    566             }
    567         }
    568 
    569         @Override
    570         protected boolean onTouch(MotionEvent event) {
    571             switch (event.getAction()) {
    572                 case MotionEvent.ACTION_DOWN: {
    573                     mReferenceX = event.getX();
    574                     mReferenceY = event.getY();
    575                     setMovingEdges(event);
    576                     invalidate();
    577                     return true;
    578                 }
    579                 case MotionEvent.ACTION_MOVE:
    580                     moveEdges(event);
    581                     break;
    582                 case MotionEvent.ACTION_CANCEL:
    583                 case MotionEvent.ACTION_UP: {
    584                     mMovingEdges = 0;
    585                     mAnimation.startParkingAnimation(mHighlightRect);
    586                     invalidate();
    587                     return true;
    588                 }
    589             }
    590             return true;
    591         }
    592 
    593         @Override
    594         protected void renderBackground(GLCanvas canvas) {
    595             RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
    596             drawHighlightRectangle(canvas, r);
    597 
    598             float centerY = (r.top + r.bottom) / 2;
    599             float centerX = (r.left + r.right) / 2;
    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 
    623         private void drawHighlightRectangle(GLCanvas canvas, RectF r) {
    624             GL11 gl = canvas.getGLInstance();
    625             gl.glLineWidth(3.0f);
    626             gl.glEnable(GL11.GL_LINE_SMOOTH);
    627 
    628             gl.glEnable(GL11.GL_STENCIL_TEST);
    629             gl.glClear(GL11.GL_STENCIL_BUFFER_BIT);
    630             gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
    631             gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
    632 
    633             if (mSpotlightRatioX == 0 || mSpotlightRatioY == 0) {
    634                 canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT);
    635                 canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint);
    636             } else {
    637                 float sx = r.width() * mSpotlightRatioX;
    638                 float sy = r.height() * mSpotlightRatioY;
    639                 float cx = r.centerX();
    640                 float cy = r.centerY();
    641 
    642                 canvas.fillRect(cx - sx / 2, cy - sy / 2, sx, sy, Color.TRANSPARENT);
    643                 canvas.drawRect(cx - sx / 2, cy - sy / 2, sx, sy, mPaint);
    644                 canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint);
    645 
    646                 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
    647                 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
    648 
    649                 canvas.drawRect(cx - sy / 2, cy - sx / 2, sy, sx, mPaint);
    650                 canvas.fillRect(cx - sy / 2, cy - sx / 2, sy, sx, Color.TRANSPARENT);
    651                 canvas.fillRect(r.left, r.top, r.width(), r.height(), 0x80000000);
    652             }
    653 
    654             gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
    655             gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
    656 
    657             canvas.fillRect(0, 0, getWidth(), getHeight(), 0xA0000000);
    658 
    659             gl.glDisable(GL11.GL_STENCIL_TEST);
    660         }
    661     }
    662 
    663     private class DetectFaceTask extends Thread {
    664         private final FaceDetector.Face[] mFaces = new FaceDetector.Face[MAX_FACE_COUNT];
    665         private final Bitmap mFaceBitmap;
    666         private int mFaceCount;
    667 
    668         public DetectFaceTask(Bitmap bitmap) {
    669             mFaceBitmap = bitmap;
    670             setName("face-detect");
    671         }
    672 
    673         @Override
    674         public void run() {
    675             Bitmap bitmap = mFaceBitmap;
    676             FaceDetector detector = new FaceDetector(
    677                     bitmap.getWidth(), bitmap.getHeight(), MAX_FACE_COUNT);
    678             mFaceCount = detector.findFaces(bitmap, mFaces);
    679             mMainHandler.sendMessage(
    680                     mMainHandler.obtainMessage(MSG_UPDATE_FACES, this));
    681         }
    682 
    683         private RectF getFaceRect(FaceDetector.Face face) {
    684             PointF point = new PointF();
    685             face.getMidPoint(point);
    686 
    687             int width = mFaceBitmap.getWidth();
    688             int height = mFaceBitmap.getHeight();
    689             float rx = face.eyesDistance() * FACE_EYE_RATIO;
    690             float ry = rx;
    691             float aspect = mAspectRatio;
    692             if (aspect != UNSPECIFIED) {
    693                 if (aspect > 1) {
    694                     rx = ry * aspect;
    695                 } else {
    696                     ry = rx / aspect;
    697                 }
    698             }
    699 
    700             RectF r = new RectF(
    701                     point.x - rx, point.y - ry, point.x + rx, point.y + ry);
    702             r.intersect(0, 0, width, height);
    703 
    704             if (aspect != UNSPECIFIED) {
    705                 if (r.width() / r.height() > aspect) {
    706                     float w = r.height() * aspect;
    707                     r.left = (r.left + r.right - w) * 0.5f;
    708                     r.right = r.left + w;
    709                 } else {
    710                     float h = r.width() / aspect;
    711                     r.top =  (r.top + r.bottom - h) * 0.5f;
    712                     r.bottom = r.top + h;
    713                 }
    714             }
    715 
    716             r.left /= width;
    717             r.right /= width;
    718             r.top /= height;
    719             r.bottom /= height;
    720             return r;
    721         }
    722 
    723         public void updateFaces() {
    724             if (mFaceCount > 1) {
    725                 for (int i = 0, n = mFaceCount; i < n; ++i) {
    726                     mFaceDetectionView.addFace(getFaceRect(mFaces[i]));
    727                 }
    728                 mFaceDetectionView.setVisibility(GLView.VISIBLE);
    729                 Toast.makeText(mActivity.getAndroidContext(),
    730                         R.string.multiface_crop_help, Toast.LENGTH_SHORT).show();
    731             } else if (mFaceCount == 1) {
    732                 mFaceDetectionView.setVisibility(GLView.INVISIBLE);
    733                 mHighlightRectangle.setRectangle(getFaceRect(mFaces[0]));
    734                 mHighlightRectangle.setVisibility(GLView.VISIBLE);
    735             } else /*mFaceCount == 0*/ {
    736                 mHighlightRectangle.setInitRectangle();
    737                 mHighlightRectangle.setVisibility(GLView.VISIBLE);
    738             }
    739         }
    740     }
    741 
    742     public void setDataModel(TileImageView.Model dataModel, int rotation) {
    743         if (((rotation / 90) & 0x01) != 0) {
    744             mImageWidth = dataModel.getImageHeight();
    745             mImageHeight = dataModel.getImageWidth();
    746         } else {
    747             mImageWidth = dataModel.getImageWidth();
    748             mImageHeight = dataModel.getImageHeight();
    749         }
    750 
    751         mImageRotation = rotation;
    752 
    753         mImageView.setModel(dataModel);
    754         mAnimation.initialize();
    755     }
    756 
    757     public void detectFaces(Bitmap bitmap) {
    758         int rotation = mImageRotation;
    759         int width = bitmap.getWidth();
    760         int height = bitmap.getHeight();
    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 
    787     public void initializeHighlightRectangle() {
    788         mHighlightRectangle.setInitRectangle();
    789         mHighlightRectangle.setVisibility(GLView.VISIBLE);
    790     }
    791 
    792     public void resume() {
    793         mImageView.prepareTextures();
    794     }
    795 
    796     public void pause() {
    797         mImageView.freeTextures();
    798     }
    799 }
    800 
    801