1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.gallery3d.ui; 18 19 import com.android.gallery3d.R; 20 import com.android.gallery3d.anim.Animation; 21 import com.android.gallery3d.app.GalleryActivity; 22 import com.android.gallery3d.common.Utils; 23 24 import android.graphics.Bitmap; 25 import android.graphics.Bitmap.Config; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.Paint; 29 import android.graphics.PointF; 30 import android.graphics.RectF; 31 import android.media.FaceDetector; 32 import android.os.Handler; 33 import android.os.Message; 34 import android.view.MotionEvent; 35 import android.view.animation.DecelerateInterpolator; 36 import android.widget.Toast; 37 38 import java.util.ArrayList; 39 import javax.microedition.khronos.opengles.GL11; 40 41 /** 42 * The activity can crop specific region of interest from an image. 43 */ 44 public class CropView extends GLView { 45 private static final String TAG = "CropView"; 46 47 private static final int FACE_PIXEL_COUNT = 120000; // around 400x300 48 49 private static final int COLOR_OUTLINE = 0xFF008AFF; 50 private static final int COLOR_FACE_OUTLINE = 0xFF000000; 51 52 private static final float OUTLINE_WIDTH = 3f; 53 54 private static final int SIZE_UNKNOWN = -1; 55 private static final int TOUCH_TOLERANCE = 30; 56 57 private static final float MIN_SELECTION_LENGTH = 16f; 58 public static final float UNSPECIFIED = -1f; 59 60 private static final int MAX_FACE_COUNT = 3; 61 private static final float FACE_EYE_RATIO = 2f; 62 63 private static final int ANIMATION_DURATION = 1250; 64 65 private static final int MOVE_LEFT = 1; 66 private static final int MOVE_TOP = 2; 67 private static final int MOVE_RIGHT = 4; 68 private static final int MOVE_BOTTOM = 8; 69 private static final int MOVE_BLOCK = 16; 70 71 private static final float MAX_SELECTION_RATIO = 0.8f; 72 private static final float MIN_SELECTION_RATIO = 0.4f; 73 private static final float SELECTION_RATIO = 0.60f; 74 private static final int ANIMATION_TRIGGER = 64; 75 76 private static final int MSG_UPDATE_FACES = 1; 77 78 private float mAspectRatio = UNSPECIFIED; 79 private float mSpotlightRatioX = 0; 80 private float mSpotlightRatioY = 0; 81 82 private Handler mMainHandler; 83 84 private FaceHighlightView mFaceDetectionView; 85 private HighlightRectangle mHighlightRectangle; 86 private TileImageView mImageView; 87 private AnimationController mAnimation = new AnimationController(); 88 89 private int mImageWidth = SIZE_UNKNOWN; 90 private int mImageHeight = SIZE_UNKNOWN; 91 92 private GalleryActivity mActivity; 93 94 private GLPaint mPaint = new GLPaint(); 95 private GLPaint mFacePaint = new GLPaint(); 96 97 private int mImageRotation; 98 99 public CropView(GalleryActivity activity) { 100 mActivity = activity; 101 mImageView = new TileImageView(activity); 102 mFaceDetectionView = new FaceHighlightView(); 103 mHighlightRectangle = new HighlightRectangle(); 104 105 addComponent(mImageView); 106 addComponent(mFaceDetectionView); 107 addComponent(mHighlightRectangle); 108 109 mHighlightRectangle.setVisibility(GLView.INVISIBLE); 110 111 mPaint.setColor(COLOR_OUTLINE); 112 mPaint.setLineWidth(OUTLINE_WIDTH); 113 114 mFacePaint.setColor(COLOR_FACE_OUTLINE); 115 mFacePaint.setLineWidth(OUTLINE_WIDTH); 116 117 mMainHandler = new SynchronizedHandler(activity.getGLRoot()) { 118 @Override 119 public void handleMessage(Message message) { 120 Utils.assertTrue(message.what == MSG_UPDATE_FACES); 121 ((DetectFaceTask) message.obj).updateFaces(); 122 } 123 }; 124 } 125 126 public void setAspectRatio(float ratio) { 127 mAspectRatio = ratio; 128 } 129 130 public void setSpotlightRatio(float ratioX, float ratioY) { 131 mSpotlightRatioX = ratioX; 132 mSpotlightRatioY = ratioY; 133 } 134 135 @Override 136 public void onLayout(boolean changed, int l, int t, int r, int b) { 137 int width = r - l; 138 int height = b - t; 139 140 mFaceDetectionView.layout(0, 0, width, height); 141 mHighlightRectangle.layout(0, 0, width, height); 142 mImageView.layout(0, 0, width, height); 143 if (mImageHeight != SIZE_UNKNOWN) { 144 mAnimation.initialize(); 145 if (mHighlightRectangle.getVisibility() == GLView.VISIBLE) { 146 mAnimation.parkNow( 147 mHighlightRectangle.mHighlightRect); 148 } 149 } 150 } 151 152 private boolean setImageViewPosition(int centerX, int centerY, float scale) { 153 int inverseX = mImageWidth - centerX; 154 int inverseY = mImageHeight - centerY; 155 TileImageView t = mImageView; 156 int rotation = mImageRotation; 157 switch (rotation) { 158 case 0: return t.setPosition(centerX, centerY, scale, 0); 159 case 90: return t.setPosition(centerY, inverseX, scale, 90); 160 case 180: return t.setPosition(inverseX, inverseY, scale, 180); 161 case 270: return t.setPosition(inverseY, centerX, scale, 270); 162 default: throw new IllegalArgumentException(String.valueOf(rotation)); 163 } 164 } 165 166 @Override 167 public void render(GLCanvas canvas) { 168 AnimationController a = mAnimation; 169 if (a.calculate(canvas.currentAnimationTimeMillis())) invalidate(); 170 setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale()); 171 super.render(canvas); 172 } 173 174 @Override 175 public void renderBackground(GLCanvas canvas) { 176 canvas.clearBuffer(); 177 } 178 179 public RectF getCropRectangle() { 180 if (mHighlightRectangle.getVisibility() == GLView.INVISIBLE) return null; 181 RectF rect = mHighlightRectangle.mHighlightRect; 182 RectF result = new RectF(rect.left * mImageWidth, rect.top * mImageHeight, 183 rect.right * mImageWidth, rect.bottom * mImageHeight); 184 return result; 185 } 186 187 public int getImageWidth() { 188 return mImageWidth; 189 } 190 191 public int getImageHeight() { 192 return mImageHeight; 193 } 194 195 private class FaceHighlightView extends GLView { 196 private static final int INDEX_NONE = -1; 197 private ArrayList<RectF> mFaces = new ArrayList<RectF>(); 198 private RectF mRect = new RectF(); 199 private int mPressedFaceIndex = INDEX_NONE; 200 201 public void addFace(RectF faceRect) { 202 mFaces.add(faceRect); 203 invalidate(); 204 } 205 206 private void renderFace(GLCanvas canvas, RectF face, boolean pressed) { 207 GL11 gl = canvas.getGLInstance(); 208 if (pressed) { 209 gl.glEnable(GL11.GL_STENCIL_TEST); 210 gl.glClear(GL11.GL_STENCIL_BUFFER_BIT); 211 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); 212 gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1); 213 } 214 215 RectF r = mAnimation.mapRect(face, mRect); 216 canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT); 217 canvas.drawRect(r.left, r.top, r.width(), r.height(), mFacePaint); 218 219 if (pressed) { 220 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); 221 } 222 } 223 224 @Override 225 protected void renderBackground(GLCanvas canvas) { 226 ArrayList<RectF> faces = mFaces; 227 for (int i = 0, n = faces.size(); i < n; ++i) { 228 renderFace(canvas, faces.get(i), i == mPressedFaceIndex); 229 } 230 231 GL11 gl = canvas.getGLInstance(); 232 if (mPressedFaceIndex != INDEX_NONE) { 233 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); 234 canvas.fillRect(0, 0, getWidth(), getHeight(), 0x66000000); 235 gl.glDisable(GL11.GL_STENCIL_TEST); 236 } 237 } 238 239 private void setPressedFace(int index) { 240 if (mPressedFaceIndex == index) return; 241 mPressedFaceIndex = index; 242 invalidate(); 243 } 244 245 private int getFaceIndexByPosition(float x, float y) { 246 ArrayList<RectF> faces = mFaces; 247 for (int i = 0, n = faces.size(); i < n; ++i) { 248 RectF r = mAnimation.mapRect(faces.get(i), mRect); 249 if (r.contains(x, y)) return i; 250 } 251 return INDEX_NONE; 252 } 253 254 @Override 255 protected boolean onTouch(MotionEvent event) { 256 float x = event.getX(); 257 float y = event.getY(); 258 switch (event.getAction()) { 259 case MotionEvent.ACTION_DOWN: 260 case MotionEvent.ACTION_MOVE: { 261 setPressedFace(getFaceIndexByPosition(x, y)); 262 break; 263 } 264 case MotionEvent.ACTION_CANCEL: 265 case MotionEvent.ACTION_UP: { 266 int index = mPressedFaceIndex; 267 setPressedFace(INDEX_NONE); 268 if (index != INDEX_NONE) { 269 mHighlightRectangle.setRectangle(mFaces.get(index)); 270 mHighlightRectangle.setVisibility(GLView.VISIBLE); 271 setVisibility(GLView.INVISIBLE); 272 } 273 } 274 } 275 return true; 276 } 277 } 278 279 private class AnimationController extends Animation { 280 private int mCurrentX; 281 private int mCurrentY; 282 private float mCurrentScale; 283 private int mStartX; 284 private int mStartY; 285 private float mStartScale; 286 private int mTargetX; 287 private int mTargetY; 288 private float mTargetScale; 289 290 public AnimationController() { 291 setDuration(ANIMATION_DURATION); 292 setInterpolator(new DecelerateInterpolator(4)); 293 } 294 295 public void initialize() { 296 mCurrentX = mImageWidth / 2; 297 mCurrentY = mImageHeight / 2; 298 mCurrentScale = Math.min(2, Math.min( 299 (float) getWidth() / mImageWidth, 300 (float) getHeight() / mImageHeight)); 301 } 302 303 public void startParkingAnimation(RectF highlight) { 304 RectF r = mAnimation.mapRect(highlight, new RectF()); 305 int width = getWidth(); 306 int height = getHeight(); 307 308 float wr = r.width() / width; 309 float hr = r.height() / height; 310 final int d = ANIMATION_TRIGGER; 311 if (wr >= MIN_SELECTION_RATIO && wr < MAX_SELECTION_RATIO 312 && hr >= MIN_SELECTION_RATIO && hr < MAX_SELECTION_RATIO 313 && r.left >= d && r.right < width - d 314 && r.top >= d && r.bottom < height - d) return; 315 316 mStartX = mCurrentX; 317 mStartY = mCurrentY; 318 mStartScale = mCurrentScale; 319 calculateTarget(highlight); 320 start(); 321 } 322 323 public void parkNow(RectF highlight) { 324 calculateTarget(highlight); 325 forceStop(); 326 mStartX = mCurrentX = mTargetX; 327 mStartY = mCurrentY = mTargetY; 328 mStartScale = mCurrentScale = mTargetScale; 329 } 330 331 public void inverseMapPoint(PointF point) { 332 float s = mCurrentScale; 333 point.x = Utils.clamp(((point.x - getWidth() * 0.5f) / s 334 + mCurrentX) / mImageWidth, 0, 1); 335 point.y = Utils.clamp(((point.y - getHeight() * 0.5f) / s 336 + mCurrentY) / mImageHeight, 0, 1); 337 } 338 339 public RectF mapRect(RectF input, RectF output) { 340 float offsetX = getWidth() * 0.5f; 341 float offsetY = getHeight() * 0.5f; 342 int x = mCurrentX; 343 int y = mCurrentY; 344 float s = mCurrentScale; 345 output.set( 346 offsetX + (input.left * mImageWidth - x) * s, 347 offsetY + (input.top * mImageHeight - y) * s, 348 offsetX + (input.right * mImageWidth - x) * s, 349 offsetY + (input.bottom * mImageHeight - y) * s); 350 return output; 351 } 352 353 @Override 354 protected void onCalculate(float progress) { 355 mCurrentX = Math.round(mStartX + (mTargetX - mStartX) * progress); 356 mCurrentY = Math.round(mStartY + (mTargetY - mStartY) * progress); 357 mCurrentScale = mStartScale + (mTargetScale - mStartScale) * progress; 358 359 if (mCurrentX == mTargetX && mCurrentY == mTargetY 360 && mCurrentScale == mTargetScale) forceStop(); 361 } 362 363 public int getCenterX() { 364 return mCurrentX; 365 } 366 367 public int getCenterY() { 368 return mCurrentY; 369 } 370 371 public float getScale() { 372 return mCurrentScale; 373 } 374 375 private void calculateTarget(RectF highlight) { 376 float width = getWidth(); 377 float height = getHeight(); 378 379 if (mImageWidth != SIZE_UNKNOWN) { 380 float minScale = Math.min(width / mImageWidth, height / mImageHeight); 381 float scale = Utils.clamp(SELECTION_RATIO * Math.min( 382 width / (highlight.width() * mImageWidth), 383 height / (highlight.height() * mImageHeight)), minScale, 2f); 384 int centerX = Math.round( 385 mImageWidth * (highlight.left + highlight.right) * 0.5f); 386 int centerY = Math.round( 387 mImageHeight * (highlight.top + highlight.bottom) * 0.5f); 388 389 if (Math.round(mImageWidth * scale) > width) { 390 int limitX = Math.round(width * 0.5f / scale); 391 centerX = Math.round( 392 (highlight.left + highlight.right) * mImageWidth / 2); 393 centerX = Utils.clamp(centerX, limitX, mImageWidth - limitX); 394 } else { 395 centerX = mImageWidth / 2; 396 } 397 if (Math.round(mImageHeight * scale) > height) { 398 int limitY = Math.round(height * 0.5f / scale); 399 centerY = Math.round( 400 (highlight.top + highlight.bottom) * mImageHeight / 2); 401 centerY = Utils.clamp(centerY, limitY, mImageHeight - limitY); 402 } else { 403 centerY = mImageHeight / 2; 404 } 405 mTargetX = centerX; 406 mTargetY = centerY; 407 mTargetScale = scale; 408 } 409 } 410 411 } 412 413 private class HighlightRectangle extends GLView { 414 private RectF mHighlightRect = new RectF(0.25f, 0.25f, 0.75f, 0.75f); 415 private RectF mTempRect = new RectF(); 416 private PointF mTempPoint = new PointF(); 417 418 private ResourceTexture mArrow; 419 420 private int mMovingEdges = 0; 421 private float mReferenceX; 422 private float mReferenceY; 423 424 public HighlightRectangle() { 425 mArrow = new ResourceTexture(mActivity.getAndroidContext(), 426 R.drawable.camera_crop_holo); 427 } 428 429 public void setInitRectangle() { 430 float targetRatio = mAspectRatio == UNSPECIFIED 431 ? 1f 432 : mAspectRatio * mImageHeight / mImageWidth; 433 float w = SELECTION_RATIO / 2f; 434 float h = SELECTION_RATIO / 2f; 435 if (targetRatio > 1) { 436 h = w / targetRatio; 437 } else { 438 w = h * targetRatio; 439 } 440 mHighlightRect.set(0.5f - w, 0.5f - h, 0.5f + w, 0.5f + h); 441 } 442 443 public void setRectangle(RectF faceRect) { 444 mHighlightRect.set(faceRect); 445 mAnimation.startParkingAnimation(faceRect); 446 invalidate(); 447 } 448 449 private void moveEdges(MotionEvent event) { 450 float scale = mAnimation.getScale(); 451 float dx = (event.getX() - mReferenceX) / scale / mImageWidth; 452 float dy = (event.getY() - mReferenceY) / scale / mImageHeight; 453 mReferenceX = event.getX(); 454 mReferenceY = event.getY(); 455 RectF r = mHighlightRect; 456 457 if ((mMovingEdges & MOVE_BLOCK) != 0) { 458 dx = Utils.clamp(dx, -r.left, 1 - r.right); 459 dy = Utils.clamp(dy, -r.top , 1 - r.bottom); 460 r.top += dy; 461 r.bottom += dy; 462 r.left += dx; 463 r.right += dx; 464 } else { 465 PointF point = mTempPoint; 466 point.set(mReferenceX, mReferenceY); 467 mAnimation.inverseMapPoint(point); 468 float left = r.left + MIN_SELECTION_LENGTH / mImageWidth; 469 float right = r.right - MIN_SELECTION_LENGTH / mImageWidth; 470 float top = r.top + MIN_SELECTION_LENGTH / mImageHeight; 471 float bottom = r.bottom - MIN_SELECTION_LENGTH / mImageHeight; 472 if ((mMovingEdges & MOVE_RIGHT) != 0) { 473 r.right = Utils.clamp(point.x, left, 1f); 474 } 475 if ((mMovingEdges & MOVE_LEFT) != 0) { 476 r.left = Utils.clamp(point.x, 0, right); 477 } 478 if ((mMovingEdges & MOVE_TOP) != 0) { 479 r.top = Utils.clamp(point.y, 0, bottom); 480 } 481 if ((mMovingEdges & MOVE_BOTTOM) != 0) { 482 r.bottom = Utils.clamp(point.y, top, 1f); 483 } 484 if (mAspectRatio != UNSPECIFIED) { 485 float targetRatio = mAspectRatio * mImageHeight / mImageWidth; 486 if (r.width() / r.height() > targetRatio) { 487 float height = r.width() / targetRatio; 488 if ((mMovingEdges & MOVE_BOTTOM) != 0) { 489 r.bottom = Utils.clamp(r.top + height, top, 1f); 490 } else { 491 r.top = Utils.clamp(r.bottom - height, 0, bottom); 492 } 493 } else { 494 float width = r.height() * targetRatio; 495 if ((mMovingEdges & MOVE_LEFT) != 0) { 496 r.left = Utils.clamp(r.right - width, 0, right); 497 } else { 498 r.right = Utils.clamp(r.left + width, left, 1f); 499 } 500 } 501 if (r.width() / r.height() > targetRatio) { 502 float width = r.height() * targetRatio; 503 if ((mMovingEdges & MOVE_LEFT) != 0) { 504 r.left = Utils.clamp(r.right - width, 0, right); 505 } else { 506 r.right = Utils.clamp(r.left + width, left, 1f); 507 } 508 } else { 509 float height = r.width() / targetRatio; 510 if ((mMovingEdges & MOVE_BOTTOM) != 0) { 511 r.bottom = Utils.clamp(r.top + height, top, 1f); 512 } else { 513 r.top = Utils.clamp(r.bottom - height, 0, bottom); 514 } 515 } 516 } 517 } 518 invalidate(); 519 } 520 521 private void setMovingEdges(MotionEvent event) { 522 RectF r = mAnimation.mapRect(mHighlightRect, mTempRect); 523 float x = event.getX(); 524 float y = event.getY(); 525 526 if (x > r.left + TOUCH_TOLERANCE && x < r.right - TOUCH_TOLERANCE 527 && y > r.top + TOUCH_TOLERANCE && y < r.bottom - TOUCH_TOLERANCE) { 528 mMovingEdges = MOVE_BLOCK; 529 return; 530 } 531 532 boolean inVerticalRange = (r.top - TOUCH_TOLERANCE) <= y 533 && y <= (r.bottom + TOUCH_TOLERANCE); 534 boolean inHorizontalRange = (r.left - TOUCH_TOLERANCE) <= x 535 && x <= (r.right + TOUCH_TOLERANCE); 536 537 if (inVerticalRange) { 538 boolean left = Math.abs(x - r.left) <= TOUCH_TOLERANCE; 539 boolean right = Math.abs(x - r.right) <= TOUCH_TOLERANCE; 540 if (left && right) { 541 left = Math.abs(x - r.left) < Math.abs(x - r.right); 542 right = !left; 543 } 544 if (left) mMovingEdges |= MOVE_LEFT; 545 if (right) mMovingEdges |= MOVE_RIGHT; 546 if (mAspectRatio != UNSPECIFIED && inHorizontalRange) { 547 mMovingEdges |= (y > 548 (r.top + r.bottom) / 2) ? MOVE_BOTTOM : MOVE_TOP; 549 } 550 } 551 if (inHorizontalRange) { 552 boolean top = Math.abs(y - r.top) <= TOUCH_TOLERANCE; 553 boolean bottom = Math.abs(y - r.bottom) <= TOUCH_TOLERANCE; 554 if (top && bottom) { 555 top = Math.abs(y - r.top) < Math.abs(y - r.bottom); 556 bottom = !top; 557 } 558 if (top) mMovingEdges |= MOVE_TOP; 559 if (bottom) mMovingEdges |= MOVE_BOTTOM; 560 if (mAspectRatio != UNSPECIFIED && inVerticalRange) { 561 mMovingEdges |= (x > 562 (r.left + r.right) / 2) ? MOVE_RIGHT : MOVE_LEFT; 563 } 564 } 565 } 566 567 @Override 568 protected boolean onTouch(MotionEvent event) { 569 switch (event.getAction()) { 570 case MotionEvent.ACTION_DOWN: { 571 mReferenceX = event.getX(); 572 mReferenceY = event.getY(); 573 setMovingEdges(event); 574 invalidate(); 575 return true; 576 } 577 case MotionEvent.ACTION_MOVE: 578 moveEdges(event); 579 break; 580 case MotionEvent.ACTION_CANCEL: 581 case MotionEvent.ACTION_UP: { 582 mMovingEdges = 0; 583 mAnimation.startParkingAnimation(mHighlightRect); 584 invalidate(); 585 return true; 586 } 587 } 588 return true; 589 } 590 591 @Override 592 protected void renderBackground(GLCanvas canvas) { 593 RectF r = mAnimation.mapRect(mHighlightRect, mTempRect); 594 drawHighlightRectangle(canvas, r); 595 596 float centerY = (r.top + r.bottom) / 2; 597 float centerX = (r.left + r.right) / 2; 598 boolean notMoving = mMovingEdges == 0; 599 if ((mMovingEdges & MOVE_RIGHT) != 0 || notMoving) { 600 mArrow.draw(canvas, 601 Math.round(r.right - mArrow.getWidth() / 2), 602 Math.round(centerY - mArrow.getHeight() / 2)); 603 } 604 if ((mMovingEdges & MOVE_LEFT) != 0 || notMoving) { 605 mArrow.draw(canvas, 606 Math.round(r.left - mArrow.getWidth() / 2), 607 Math.round(centerY - mArrow.getHeight() / 2)); 608 } 609 if ((mMovingEdges & MOVE_TOP) != 0 || notMoving) { 610 mArrow.draw(canvas, 611 Math.round(centerX - mArrow.getWidth() / 2), 612 Math.round(r.top - mArrow.getHeight() / 2)); 613 } 614 if ((mMovingEdges & MOVE_BOTTOM) != 0 || notMoving) { 615 mArrow.draw(canvas, 616 Math.round(centerX - mArrow.getWidth() / 2), 617 Math.round(r.bottom - mArrow.getHeight() / 2)); 618 } 619 } 620 621 private void drawHighlightRectangle(GLCanvas canvas, RectF r) { 622 GL11 gl = canvas.getGLInstance(); 623 gl.glLineWidth(3.0f); 624 gl.glEnable(GL11.GL_LINE_SMOOTH); 625 626 gl.glEnable(GL11.GL_STENCIL_TEST); 627 gl.glClear(GL11.GL_STENCIL_BUFFER_BIT); 628 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); 629 gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1); 630 631 if (mSpotlightRatioX == 0 || mSpotlightRatioY == 0) { 632 canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT); 633 canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint); 634 } else { 635 float sx = r.width() * mSpotlightRatioX; 636 float sy = r.height() * mSpotlightRatioY; 637 float cx = r.centerX(); 638 float cy = r.centerY(); 639 640 canvas.fillRect(cx - sx / 2, cy - sy / 2, sx, sy, Color.TRANSPARENT); 641 canvas.drawRect(cx - sx / 2, cy - sy / 2, sx, sy, mPaint); 642 canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint); 643 644 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); 645 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); 646 647 canvas.drawRect(cx - sy / 2, cy - sx / 2, sy, sx, mPaint); 648 canvas.fillRect(cx - sy / 2, cy - sx / 2, sy, sx, Color.TRANSPARENT); 649 canvas.fillRect(r.left, r.top, r.width(), r.height(), 0x80000000); 650 } 651 652 gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); 653 gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); 654 655 canvas.fillRect(0, 0, getWidth(), getHeight(), 0xA0000000); 656 657 gl.glDisable(GL11.GL_STENCIL_TEST); 658 } 659 } 660 661 private class DetectFaceTask extends Thread { 662 private final FaceDetector.Face[] mFaces = new FaceDetector.Face[MAX_FACE_COUNT]; 663 private final Bitmap mFaceBitmap; 664 private int mFaceCount; 665 666 public DetectFaceTask(Bitmap bitmap) { 667 mFaceBitmap = bitmap; 668 setName("face-detect"); 669 } 670 671 @Override 672 public void run() { 673 Bitmap bitmap = mFaceBitmap; 674 FaceDetector detector = new FaceDetector( 675 bitmap.getWidth(), bitmap.getHeight(), MAX_FACE_COUNT); 676 mFaceCount = detector.findFaces(bitmap, mFaces); 677 mMainHandler.sendMessage( 678 mMainHandler.obtainMessage(MSG_UPDATE_FACES, this)); 679 } 680 681 private RectF getFaceRect(FaceDetector.Face face) { 682 PointF point = new PointF(); 683 face.getMidPoint(point); 684 685 int width = mFaceBitmap.getWidth(); 686 int height = mFaceBitmap.getHeight(); 687 float rx = face.eyesDistance() * FACE_EYE_RATIO; 688 float ry = rx; 689 float aspect = mAspectRatio; 690 if (aspect != UNSPECIFIED) { 691 if (aspect > 1) { 692 rx = ry * aspect; 693 } else { 694 ry = rx / aspect; 695 } 696 } 697 698 RectF r = new RectF( 699 point.x - rx, point.y - ry, point.x + rx, point.y + ry); 700 r.intersect(0, 0, width, height); 701 702 if (aspect != UNSPECIFIED) { 703 if (r.width() / r.height() > aspect) { 704 float w = r.height() * aspect; 705 r.left = (r.left + r.right - w) * 0.5f; 706 r.right = r.left + w; 707 } else { 708 float h = r.width() / aspect; 709 r.top = (r.top + r.bottom - h) * 0.5f; 710 r.bottom = r.top + h; 711 } 712 } 713 714 r.left /= width; 715 r.right /= width; 716 r.top /= height; 717 r.bottom /= height; 718 return r; 719 } 720 721 public void updateFaces() { 722 if (mFaceCount > 1) { 723 for (int i = 0, n = mFaceCount; i < n; ++i) { 724 mFaceDetectionView.addFace(getFaceRect(mFaces[i])); 725 } 726 mFaceDetectionView.setVisibility(GLView.VISIBLE); 727 Toast.makeText(mActivity.getAndroidContext(), 728 R.string.multiface_crop_help, Toast.LENGTH_SHORT).show(); 729 } else if (mFaceCount == 1) { 730 mFaceDetectionView.setVisibility(GLView.INVISIBLE); 731 mHighlightRectangle.setRectangle(getFaceRect(mFaces[0])); 732 mHighlightRectangle.setVisibility(GLView.VISIBLE); 733 } else /*mFaceCount == 0*/ { 734 mHighlightRectangle.setInitRectangle(); 735 mHighlightRectangle.setVisibility(GLView.VISIBLE); 736 } 737 } 738 } 739 740 public void setDataModel(TileImageView.Model dataModel, int rotation) { 741 if (((rotation / 90) & 0x01) != 0) { 742 mImageWidth = dataModel.getImageHeight(); 743 mImageHeight = dataModel.getImageWidth(); 744 } else { 745 mImageWidth = dataModel.getImageWidth(); 746 mImageHeight = dataModel.getImageHeight(); 747 } 748 749 mImageRotation = rotation; 750 751 mImageView.setModel(dataModel); 752 mAnimation.initialize(); 753 } 754 755 public void detectFaces(Bitmap bitmap) { 756 int rotation = mImageRotation; 757 int width = bitmap.getWidth(); 758 int height = bitmap.getHeight(); 759 float scale = (float) Math.sqrt( 760 (double) FACE_PIXEL_COUNT / (width * height)); 761 762 // faceBitmap is a correctly rotated bitmap, as viewed by a user. 763 Bitmap faceBitmap; 764 if (((rotation / 90) & 1) == 0) { 765 int w = (Math.round(width * scale) & ~1); // must be even 766 int h = Math.round(height * scale); 767 faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565); 768 Canvas canvas = new Canvas(faceBitmap); 769 canvas.rotate(rotation, w / 2, h / 2); 770 canvas.scale((float) w / width, (float) h / height); 771 canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG)); 772 } else { 773 int w = (Math.round(height * scale) & ~1); // must be even 774 int h = Math.round(width * scale); 775 faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565); 776 Canvas canvas = new Canvas(faceBitmap); 777 canvas.translate(w / 2, h / 2); 778 canvas.rotate(rotation); 779 canvas.translate(-h / 2, -w / 2); 780 canvas.scale((float) w / height, (float) h / width); 781 canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG)); 782 } 783 new DetectFaceTask(faceBitmap).start(); 784 } 785 786 public void initializeHighlightRectangle() { 787 mHighlightRectangle.setInitRectangle(); 788 mHighlightRectangle.setVisibility(GLView.VISIBLE); 789 } 790 791 public void resume() { 792 mImageView.prepareTextures(); 793 } 794 795 public void pause() { 796 mImageView.freeTextures(); 797 } 798 } 799 800