summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ui/CropView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/ui/CropView.java')
-rw-r--r--src/com/android/gallery3d/ui/CropView.java801
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();
+ }
+}
+