diff options
Diffstat (limited to 'src/com/android/gallery3d/ui/CropView.java')
-rw-r--r-- | src/com/android/gallery3d/ui/CropView.java | 801 |
1 files changed, 801 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/ui/CropView.java b/src/com/android/gallery3d/ui/CropView.java new file mode 100644 index 000000000..9c59c9a84 --- /dev/null +++ b/src/com/android/gallery3d/ui/CropView.java @@ -0,0 +1,801 @@ +/* + * 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 com.android.gallery3d.R; +import com.android.gallery3d.anim.Animation; +import com.android.gallery3d.app.GalleryActivity; +import com.android.gallery3d.common.Utils; + +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.view.MotionEvent; +import android.view.animation.DecelerateInterpolator; +import android.widget.Toast; + +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 { + 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 GalleryActivity mActivity; + + private GLPaint mPaint = new GLPaint(); + private GLPaint mFacePaint = new GLPaint(); + + private int mImageRotation; + + public CropView(GalleryActivity 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(canvas.currentAnimationTimeMillis())) 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<RectF> mFaces = new ArrayList<RectF>(); + 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<RectF> 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<RectF> 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 mArrowX; + private ResourceTexture mArrowY; + + private int mMovingEdges = 0; + private float mReferenceX; + private float mReferenceY; + + public HighlightRectangle() { + mArrowX = new ResourceTexture(mActivity.getAndroidContext(), + R.drawable.camera_crop_width_holo); + mArrowY = new ResourceTexture(mActivity.getAndroidContext(), + R.drawable.camera_crop_height_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; + if ((mMovingEdges & (MOVE_RIGHT | MOVE_BLOCK)) != 0) { + mArrowX.draw(canvas, + Math.round(r.right - mArrowX.getWidth() / 2), + Math.round(centerY - mArrowX.getHeight() / 2)); + } + if ((mMovingEdges & (MOVE_LEFT | MOVE_BLOCK)) != 0) { + mArrowX.draw(canvas, + Math.round(r.left - mArrowX.getWidth() / 2), + Math.round(centerY - mArrowX.getHeight() / 2)); + } + if ((mMovingEdges & (MOVE_TOP | MOVE_BLOCK)) != 0) { + mArrowY.draw(canvas, + Math.round(centerX - mArrowY.getWidth() / 2), + Math.round(r.top - mArrowY.getHeight() / 2)); + } + if ((mMovingEdges & (MOVE_BOTTOM | MOVE_BLOCK)) != 0) { + mArrowY.draw(canvas, + Math.round(centerX - mArrowY.getWidth() / 2), + Math.round(r.bottom - mArrowY.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 = (float) Math.sqrt( + (double) 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(); + } +} + |