/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gallery3d.ui; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.RectF; import android.media.FaceDetector; import android.os.Handler; import android.os.Message; import android.util.FloatMath; import android.view.MotionEvent; import android.view.animation.DecelerateInterpolator; import android.widget.Toast; import com.android.gallery3d.R; import com.android.gallery3d.anim.Animation; import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.common.Utils; import java.util.ArrayList; import javax.microedition.khronos.opengles.GL11; /** * The activity can crop specific region of interest from an image. */ public class CropView extends GLView { @SuppressWarnings("unused") private static final String TAG = "CropView"; private static final int FACE_PIXEL_COUNT = 120000; // around 400x300 private static final int COLOR_OUTLINE = 0xFF008AFF; private static final int COLOR_FACE_OUTLINE = 0xFF000000; private static final float OUTLINE_WIDTH = 3f; private static final int SIZE_UNKNOWN = -1; private static final int TOUCH_TOLERANCE = 30; private static final float MIN_SELECTION_LENGTH = 16f; public static final float UNSPECIFIED = -1f; private static final int MAX_FACE_COUNT = 3; private static final float FACE_EYE_RATIO = 2f; private static final int ANIMATION_DURATION = 1250; private static final int MOVE_LEFT = 1; private static final int MOVE_TOP = 2; private static final int MOVE_RIGHT = 4; private static final int MOVE_BOTTOM = 8; private static final int MOVE_BLOCK = 16; private static final float MAX_SELECTION_RATIO = 0.8f; private static final float MIN_SELECTION_RATIO = 0.4f; private static final float SELECTION_RATIO = 0.60f; private static final int ANIMATION_TRIGGER = 64; private static final int MSG_UPDATE_FACES = 1; private float mAspectRatio = UNSPECIFIED; private float mSpotlightRatioX = 0; private float mSpotlightRatioY = 0; private Handler mMainHandler; private FaceHighlightView mFaceDetectionView; private HighlightRectangle mHighlightRectangle; private TileImageView mImageView; private AnimationController mAnimation = new AnimationController(); private int mImageWidth = SIZE_UNKNOWN; private int mImageHeight = SIZE_UNKNOWN; private AbstractGalleryActivity mActivity; private GLPaint mPaint = new GLPaint(); private GLPaint mFacePaint = new GLPaint(); private int mImageRotation; public CropView(AbstractGalleryActivity activity) { mActivity = activity; mImageView = new TileImageView(activity); mFaceDetectionView = new FaceHighlightView(); mHighlightRectangle = new HighlightRectangle(); addComponent(mImageView); addComponent(mFaceDetectionView); addComponent(mHighlightRectangle); mHighlightRectangle.setVisibility(GLView.INVISIBLE); mPaint.setColor(COLOR_OUTLINE); mPaint.setLineWidth(OUTLINE_WIDTH); mFacePaint.setColor(COLOR_FACE_OUTLINE); mFacePaint.setLineWidth(OUTLINE_WIDTH); mMainHandler = new SynchronizedHandler(activity.getGLRoot()) { @Override public void handleMessage(Message message) { Utils.assertTrue(message.what == MSG_UPDATE_FACES); ((DetectFaceTask) message.obj).updateFaces(); } }; } public void setAspectRatio(float ratio) { mAspectRatio = ratio; } public void setSpotlightRatio(float ratioX, float ratioY) { mSpotlightRatioX = ratioX; mSpotlightRatioY = ratioY; } @Override public void onLayout(boolean changed, int l, int t, int r, int b) { int width = r - l; int height = b - t; mFaceDetectionView.layout(0, 0, width, height); mHighlightRectangle.layout(0, 0, width, height); mImageView.layout(0, 0, width, height); if (mImageHeight != SIZE_UNKNOWN) { mAnimation.initialize(); if (mHighlightRectangle.getVisibility() == GLView.VISIBLE) { mAnimation.parkNow( mHighlightRectangle.mHighlightRect); } } } private boolean setImageViewPosition(int centerX, int centerY, float scale) { int inverseX = mImageWidth - centerX; int inverseY = mImageHeight - centerY; TileImageView t = mImageView; int rotation = mImageRotation; switch (rotation) { case 0: return t.setPosition(centerX, centerY, scale, 0); case 90: return t.setPosition(centerY, inverseX, scale, 90); case 180: return t.setPosition(inverseX, inverseY, scale, 180); case 270: return t.setPosition(inverseY, centerX, scale, 270); default: throw new IllegalArgumentException(String.valueOf(rotation)); } } @Override public void render(GLCanvas canvas) { AnimationController a = mAnimation; if (a.calculate(AnimationTime.get())) invalidate(); setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale()); super.render(canvas); } @Override public void renderBackground(GLCanvas canvas) { canvas.clearBuffer(); } public RectF getCropRectangle() { if (mHighlightRectangle.getVisibility() == GLView.INVISIBLE) return null; RectF rect = mHighlightRectangle.mHighlightRect; RectF result = new RectF(rect.left * mImageWidth, rect.top * mImageHeight, rect.right * mImageWidth, rect.bottom * mImageHeight); return result; } public int getImageWidth() { return mImageWidth; } public int getImageHeight() { return mImageHeight; } private class FaceHighlightView extends GLView { private static final int INDEX_NONE = -1; private ArrayList mFaces = new ArrayList(); private RectF mRect = new RectF(); private int mPressedFaceIndex = INDEX_NONE; public void addFace(RectF faceRect) { mFaces.add(faceRect); invalidate(); } private void renderFace(GLCanvas canvas, RectF face, boolean pressed) { GL11 gl = canvas.getGLInstance(); if (pressed) { gl.glEnable(GL11.GL_STENCIL_TEST); gl.glClear(GL11.GL_STENCIL_BUFFER_BIT); gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1); } RectF r = mAnimation.mapRect(face, mRect); canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT); canvas.drawRect(r.left, r.top, r.width(), r.height(), mFacePaint); if (pressed) { gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); } } @Override protected void renderBackground(GLCanvas canvas) { ArrayList faces = mFaces; for (int i = 0, n = faces.size(); i < n; ++i) { renderFace(canvas, faces.get(i), i == mPressedFaceIndex); } GL11 gl = canvas.getGLInstance(); if (mPressedFaceIndex != INDEX_NONE) { gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); canvas.fillRect(0, 0, getWidth(), getHeight(), 0x66000000); gl.glDisable(GL11.GL_STENCIL_TEST); } } private void setPressedFace(int index) { if (mPressedFaceIndex == index) return; mPressedFaceIndex = index; invalidate(); } private int getFaceIndexByPosition(float x, float y) { ArrayList faces = mFaces; for (int i = 0, n = faces.size(); i < n; ++i) { RectF r = mAnimation.mapRect(faces.get(i), mRect); if (r.contains(x, y)) return i; } return INDEX_NONE; } @Override protected boolean onTouch(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: { setPressedFace(getFaceIndexByPosition(x, y)); break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { int index = mPressedFaceIndex; setPressedFace(INDEX_NONE); if (index != INDEX_NONE) { mHighlightRectangle.setRectangle(mFaces.get(index)); mHighlightRectangle.setVisibility(GLView.VISIBLE); setVisibility(GLView.INVISIBLE); } } } return true; } } private class AnimationController extends Animation { private int mCurrentX; private int mCurrentY; private float mCurrentScale; private int mStartX; private int mStartY; private float mStartScale; private int mTargetX; private int mTargetY; private float mTargetScale; public AnimationController() { setDuration(ANIMATION_DURATION); setInterpolator(new DecelerateInterpolator(4)); } public void initialize() { mCurrentX = mImageWidth / 2; mCurrentY = mImageHeight / 2; mCurrentScale = Math.min(2, Math.min( (float) getWidth() / mImageWidth, (float) getHeight() / mImageHeight)); } public void startParkingAnimation(RectF highlight) { RectF r = mAnimation.mapRect(highlight, new RectF()); int width = getWidth(); int height = getHeight(); float wr = r.width() / width; float hr = r.height() / height; final int d = ANIMATION_TRIGGER; if (wr >= MIN_SELECTION_RATIO && wr < MAX_SELECTION_RATIO && hr >= MIN_SELECTION_RATIO && hr < MAX_SELECTION_RATIO && r.left >= d && r.right < width - d && r.top >= d && r.bottom < height - d) return; mStartX = mCurrentX; mStartY = mCurrentY; mStartScale = mCurrentScale; calculateTarget(highlight); start(); } public void parkNow(RectF highlight) { calculateTarget(highlight); forceStop(); mStartX = mCurrentX = mTargetX; mStartY = mCurrentY = mTargetY; mStartScale = mCurrentScale = mTargetScale; } public void inverseMapPoint(PointF point) { float s = mCurrentScale; point.x = Utils.clamp(((point.x - getWidth() * 0.5f) / s + mCurrentX) / mImageWidth, 0, 1); point.y = Utils.clamp(((point.y - getHeight() * 0.5f) / s + mCurrentY) / mImageHeight, 0, 1); } public RectF mapRect(RectF input, RectF output) { float offsetX = getWidth() * 0.5f; float offsetY = getHeight() * 0.5f; int x = mCurrentX; int y = mCurrentY; float s = mCurrentScale; output.set( offsetX + (input.left * mImageWidth - x) * s, offsetY + (input.top * mImageHeight - y) * s, offsetX + (input.right * mImageWidth - x) * s, offsetY + (input.bottom * mImageHeight - y) * s); return output; } @Override protected void onCalculate(float progress) { mCurrentX = Math.round(mStartX + (mTargetX - mStartX) * progress); mCurrentY = Math.round(mStartY + (mTargetY - mStartY) * progress); mCurrentScale = mStartScale + (mTargetScale - mStartScale) * progress; if (mCurrentX == mTargetX && mCurrentY == mTargetY && mCurrentScale == mTargetScale) forceStop(); } public int getCenterX() { return mCurrentX; } public int getCenterY() { return mCurrentY; } public float getScale() { return mCurrentScale; } private void calculateTarget(RectF highlight) { float width = getWidth(); float height = getHeight(); if (mImageWidth != SIZE_UNKNOWN) { float minScale = Math.min(width / mImageWidth, height / mImageHeight); float scale = Utils.clamp(SELECTION_RATIO * Math.min( width / (highlight.width() * mImageWidth), height / (highlight.height() * mImageHeight)), minScale, 2f); int centerX = Math.round( mImageWidth * (highlight.left + highlight.right) * 0.5f); int centerY = Math.round( mImageHeight * (highlight.top + highlight.bottom) * 0.5f); if (Math.round(mImageWidth * scale) > width) { int limitX = Math.round(width * 0.5f / scale); centerX = Math.round( (highlight.left + highlight.right) * mImageWidth / 2); centerX = Utils.clamp(centerX, limitX, mImageWidth - limitX); } else { centerX = mImageWidth / 2; } if (Math.round(mImageHeight * scale) > height) { int limitY = Math.round(height * 0.5f / scale); centerY = Math.round( (highlight.top + highlight.bottom) * mImageHeight / 2); centerY = Utils.clamp(centerY, limitY, mImageHeight - limitY); } else { centerY = mImageHeight / 2; } mTargetX = centerX; mTargetY = centerY; mTargetScale = scale; } } } private class HighlightRectangle extends GLView { private RectF mHighlightRect = new RectF(0.25f, 0.25f, 0.75f, 0.75f); private RectF mTempRect = new RectF(); private PointF mTempPoint = new PointF(); private ResourceTexture mArrow; private int mMovingEdges = 0; private float mReferenceX; private float mReferenceY; public HighlightRectangle() { mArrow = new ResourceTexture(mActivity.getAndroidContext(), R.drawable.camera_crop_holo); } public void setInitRectangle() { float targetRatio = mAspectRatio == UNSPECIFIED ? 1f : mAspectRatio * mImageHeight / mImageWidth; float w = SELECTION_RATIO / 2f; float h = SELECTION_RATIO / 2f; if (targetRatio > 1) { h = w / targetRatio; } else { w = h * targetRatio; } mHighlightRect.set(0.5f - w, 0.5f - h, 0.5f + w, 0.5f + h); } public void setRectangle(RectF faceRect) { mHighlightRect.set(faceRect); mAnimation.startParkingAnimation(faceRect); invalidate(); } private void moveEdges(MotionEvent event) { float scale = mAnimation.getScale(); float dx = (event.getX() - mReferenceX) / scale / mImageWidth; float dy = (event.getY() - mReferenceY) / scale / mImageHeight; mReferenceX = event.getX(); mReferenceY = event.getY(); RectF r = mHighlightRect; if ((mMovingEdges & MOVE_BLOCK) != 0) { dx = Utils.clamp(dx, -r.left, 1 - r.right); dy = Utils.clamp(dy, -r.top , 1 - r.bottom); r.top += dy; r.bottom += dy; r.left += dx; r.right += dx; } else { PointF point = mTempPoint; point.set(mReferenceX, mReferenceY); mAnimation.inverseMapPoint(point); float left = r.left + MIN_SELECTION_LENGTH / mImageWidth; float right = r.right - MIN_SELECTION_LENGTH / mImageWidth; float top = r.top + MIN_SELECTION_LENGTH / mImageHeight; float bottom = r.bottom - MIN_SELECTION_LENGTH / mImageHeight; if ((mMovingEdges & MOVE_RIGHT) != 0) { r.right = Utils.clamp(point.x, left, 1f); } if ((mMovingEdges & MOVE_LEFT) != 0) { r.left = Utils.clamp(point.x, 0, right); } if ((mMovingEdges & MOVE_TOP) != 0) { r.top = Utils.clamp(point.y, 0, bottom); } if ((mMovingEdges & MOVE_BOTTOM) != 0) { r.bottom = Utils.clamp(point.y, top, 1f); } if (mAspectRatio != UNSPECIFIED) { float targetRatio = mAspectRatio * mImageHeight / mImageWidth; if (r.width() / r.height() > targetRatio) { float height = r.width() / targetRatio; if ((mMovingEdges & MOVE_BOTTOM) != 0) { r.bottom = Utils.clamp(r.top + height, top, 1f); } else { r.top = Utils.clamp(r.bottom - height, 0, bottom); } } else { float width = r.height() * targetRatio; if ((mMovingEdges & MOVE_LEFT) != 0) { r.left = Utils.clamp(r.right - width, 0, right); } else { r.right = Utils.clamp(r.left + width, left, 1f); } } if (r.width() / r.height() > targetRatio) { float width = r.height() * targetRatio; if ((mMovingEdges & MOVE_LEFT) != 0) { r.left = Utils.clamp(r.right - width, 0, right); } else { r.right = Utils.clamp(r.left + width, left, 1f); } } else { float height = r.width() / targetRatio; if ((mMovingEdges & MOVE_BOTTOM) != 0) { r.bottom = Utils.clamp(r.top + height, top, 1f); } else { r.top = Utils.clamp(r.bottom - height, 0, bottom); } } } } invalidate(); } private void setMovingEdges(MotionEvent event) { RectF r = mAnimation.mapRect(mHighlightRect, mTempRect); float x = event.getX(); float y = event.getY(); if (x > r.left + TOUCH_TOLERANCE && x < r.right - TOUCH_TOLERANCE && y > r.top + TOUCH_TOLERANCE && y < r.bottom - TOUCH_TOLERANCE) { mMovingEdges = MOVE_BLOCK; return; } boolean inVerticalRange = (r.top - TOUCH_TOLERANCE) <= y && y <= (r.bottom + TOUCH_TOLERANCE); boolean inHorizontalRange = (r.left - TOUCH_TOLERANCE) <= x && x <= (r.right + TOUCH_TOLERANCE); if (inVerticalRange) { boolean left = Math.abs(x - r.left) <= TOUCH_TOLERANCE; boolean right = Math.abs(x - r.right) <= TOUCH_TOLERANCE; if (left && right) { left = Math.abs(x - r.left) < Math.abs(x - r.right); right = !left; } if (left) mMovingEdges |= MOVE_LEFT; if (right) mMovingEdges |= MOVE_RIGHT; if (mAspectRatio != UNSPECIFIED && inHorizontalRange) { mMovingEdges |= (y > (r.top + r.bottom) / 2) ? MOVE_BOTTOM : MOVE_TOP; } } if (inHorizontalRange) { boolean top = Math.abs(y - r.top) <= TOUCH_TOLERANCE; boolean bottom = Math.abs(y - r.bottom) <= TOUCH_TOLERANCE; if (top && bottom) { top = Math.abs(y - r.top) < Math.abs(y - r.bottom); bottom = !top; } if (top) mMovingEdges |= MOVE_TOP; if (bottom) mMovingEdges |= MOVE_BOTTOM; if (mAspectRatio != UNSPECIFIED && inVerticalRange) { mMovingEdges |= (x > (r.left + r.right) / 2) ? MOVE_RIGHT : MOVE_LEFT; } } } @Override protected boolean onTouch(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mReferenceX = event.getX(); mReferenceY = event.getY(); setMovingEdges(event); invalidate(); return true; } case MotionEvent.ACTION_MOVE: moveEdges(event); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { mMovingEdges = 0; mAnimation.startParkingAnimation(mHighlightRect); invalidate(); return true; } } return true; } @Override protected void renderBackground(GLCanvas canvas) { RectF r = mAnimation.mapRect(mHighlightRect, mTempRect); drawHighlightRectangle(canvas, r); float centerY = (r.top + r.bottom) / 2; float centerX = (r.left + r.right) / 2; boolean notMoving = mMovingEdges == 0; if ((mMovingEdges & MOVE_RIGHT) != 0 || notMoving) { mArrow.draw(canvas, Math.round(r.right - mArrow.getWidth() / 2), Math.round(centerY - mArrow.getHeight() / 2)); } if ((mMovingEdges & MOVE_LEFT) != 0 || notMoving) { mArrow.draw(canvas, Math.round(r.left - mArrow.getWidth() / 2), Math.round(centerY - mArrow.getHeight() / 2)); } if ((mMovingEdges & MOVE_TOP) != 0 || notMoving) { mArrow.draw(canvas, Math.round(centerX - mArrow.getWidth() / 2), Math.round(r.top - mArrow.getHeight() / 2)); } if ((mMovingEdges & MOVE_BOTTOM) != 0 || notMoving) { mArrow.draw(canvas, Math.round(centerX - mArrow.getWidth() / 2), Math.round(r.bottom - mArrow.getHeight() / 2)); } } private void drawHighlightRectangle(GLCanvas canvas, RectF r) { GL11 gl = canvas.getGLInstance(); gl.glLineWidth(3.0f); gl.glEnable(GL11.GL_LINE_SMOOTH); gl.glEnable(GL11.GL_STENCIL_TEST); gl.glClear(GL11.GL_STENCIL_BUFFER_BIT); gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1); if (mSpotlightRatioX == 0 || mSpotlightRatioY == 0) { canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT); canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint); } else { float sx = r.width() * mSpotlightRatioX; float sy = r.height() * mSpotlightRatioY; float cx = r.centerX(); float cy = r.centerY(); canvas.fillRect(cx - sx / 2, cy - sy / 2, sx, sy, Color.TRANSPARENT); canvas.drawRect(cx - sx / 2, cy - sy / 2, sx, sy, mPaint); canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint); gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); canvas.drawRect(cx - sy / 2, cy - sx / 2, sy, sx, mPaint); canvas.fillRect(cx - sy / 2, cy - sx / 2, sy, sx, Color.TRANSPARENT); canvas.fillRect(r.left, r.top, r.width(), r.height(), 0x80000000); } gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); canvas.fillRect(0, 0, getWidth(), getHeight(), 0xA0000000); gl.glDisable(GL11.GL_STENCIL_TEST); } } private class DetectFaceTask extends Thread { private final FaceDetector.Face[] mFaces = new FaceDetector.Face[MAX_FACE_COUNT]; private final Bitmap mFaceBitmap; private int mFaceCount; public DetectFaceTask(Bitmap bitmap) { mFaceBitmap = bitmap; setName("face-detect"); } @Override public void run() { Bitmap bitmap = mFaceBitmap; FaceDetector detector = new FaceDetector( bitmap.getWidth(), bitmap.getHeight(), MAX_FACE_COUNT); mFaceCount = detector.findFaces(bitmap, mFaces); mMainHandler.sendMessage( mMainHandler.obtainMessage(MSG_UPDATE_FACES, this)); } private RectF getFaceRect(FaceDetector.Face face) { PointF point = new PointF(); face.getMidPoint(point); int width = mFaceBitmap.getWidth(); int height = mFaceBitmap.getHeight(); float rx = face.eyesDistance() * FACE_EYE_RATIO; float ry = rx; float aspect = mAspectRatio; if (aspect != UNSPECIFIED) { if (aspect > 1) { rx = ry * aspect; } else { ry = rx / aspect; } } RectF r = new RectF( point.x - rx, point.y - ry, point.x + rx, point.y + ry); r.intersect(0, 0, width, height); if (aspect != UNSPECIFIED) { if (r.width() / r.height() > aspect) { float w = r.height() * aspect; r.left = (r.left + r.right - w) * 0.5f; r.right = r.left + w; } else { float h = r.width() / aspect; r.top = (r.top + r.bottom - h) * 0.5f; r.bottom = r.top + h; } } r.left /= width; r.right /= width; r.top /= height; r.bottom /= height; return r; } public void updateFaces() { if (mFaceCount > 1) { for (int i = 0, n = mFaceCount; i < n; ++i) { mFaceDetectionView.addFace(getFaceRect(mFaces[i])); } mFaceDetectionView.setVisibility(GLView.VISIBLE); Toast.makeText(mActivity.getAndroidContext(), R.string.multiface_crop_help, Toast.LENGTH_SHORT).show(); } else if (mFaceCount == 1) { mFaceDetectionView.setVisibility(GLView.INVISIBLE); mHighlightRectangle.setRectangle(getFaceRect(mFaces[0])); mHighlightRectangle.setVisibility(GLView.VISIBLE); } else /*mFaceCount == 0*/ { mHighlightRectangle.setInitRectangle(); mHighlightRectangle.setVisibility(GLView.VISIBLE); } } } public void setDataModel(TileImageView.Model dataModel, int rotation) { if (((rotation / 90) & 0x01) != 0) { mImageWidth = dataModel.getImageHeight(); mImageHeight = dataModel.getImageWidth(); } else { mImageWidth = dataModel.getImageWidth(); mImageHeight = dataModel.getImageHeight(); } mImageRotation = rotation; mImageView.setModel(dataModel); mAnimation.initialize(); } public void detectFaces(Bitmap bitmap) { int rotation = mImageRotation; int width = bitmap.getWidth(); int height = bitmap.getHeight(); float scale = FloatMath.sqrt((float) FACE_PIXEL_COUNT / (width * height)); // faceBitmap is a correctly rotated bitmap, as viewed by a user. Bitmap faceBitmap; if (((rotation / 90) & 1) == 0) { int w = (Math.round(width * scale) & ~1); // must be even int h = Math.round(height * scale); faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565); Canvas canvas = new Canvas(faceBitmap); canvas.rotate(rotation, w / 2, h / 2); canvas.scale((float) w / width, (float) h / height); canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG)); } else { int w = (Math.round(height * scale) & ~1); // must be even int h = Math.round(width * scale); faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565); Canvas canvas = new Canvas(faceBitmap); canvas.translate(w / 2, h / 2); canvas.rotate(rotation); canvas.translate(-h / 2, -w / 2); canvas.scale((float) w / height, (float) h / width); canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG)); } new DetectFaceTask(faceBitmap).start(); } public void initializeHighlightRectangle() { mHighlightRectangle.setInitRectangle(); mHighlightRectangle.setVisibility(GLView.VISIBLE); } public void resume() { mImageView.prepareTextures(); } public void pause() { mImageView.freeTextures(); } }