blob: 6a52d72c533f4b9bbcb01db833681ec39aaca987 [file] [log] [blame]
The Android Open Source Projectb64d3452009-03-03 19:32:20 -08001/*
2 * Copyright (C) 2007 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
17package com.android.camera;
18
Owen Lin101d5282009-04-03 16:20:08 -070019import com.android.camera.gallery.IImage;
20import com.android.camera.gallery.IImageList;
21
Chih-Chung Changc830ac22009-08-24 16:42:21 +080022import android.app.WallpaperManager;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080023import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.graphics.Bitmap;
27import android.graphics.Canvas;
28import android.graphics.Matrix;
Chih-Chung Chang724e9b32009-05-19 14:35:15 +080029import android.graphics.Path;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080030import android.graphics.PointF;
31import android.graphics.PorterDuff;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080032import android.graphics.Rect;
33import android.graphics.RectF;
34import android.graphics.Region;
35import android.media.FaceDetector;
36import android.net.Uri;
37import android.os.Bundle;
38import android.os.Handler;
39import android.provider.MediaStore;
40import android.util.AttributeSet;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080041import android.util.Log;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080042import android.view.MotionEvent;
43import android.view.View;
44import android.view.Window;
Wu-cheng Li640ad872009-07-30 17:14:34 +080045import android.view.WindowManager;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080046import android.widget.Toast;
47
Chih-Chung Chang724e9b32009-05-19 14:35:15 +080048import java.io.File;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080049import java.io.IOException;
50import java.io.OutputStream;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080051import java.util.ArrayList;
Owen Lin482a3a52009-06-26 15:47:39 -070052import java.util.concurrent.CountDownLatch;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080053
Ray Chen993105a2009-04-10 02:11:35 -070054/**
55 * The activity can crop specific region of interest from an image.
56 */
Owen Lin482a3a52009-06-26 15:47:39 -070057public class CropImage extends MonitoredActivity {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080058 private static final String TAG = "CropImage";
Chih-Chung Chang724e9b32009-05-19 14:35:15 +080059
60 // These are various options can be specified in the intent.
61 private Bitmap.CompressFormat mOutputFormat =
Chih-Chung Changd8209aa2009-04-07 11:45:12 -070062 Bitmap.CompressFormat.JPEG; // only used with mSaveUri
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080063 private Uri mSaveUri = null;
Chih-Chung Changc830ac22009-08-24 16:42:21 +080064 private boolean mSetWallpaper = false;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080065 private int mAspectX, mAspectY;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080066 private boolean mDoFaceDetection = true;
67 private boolean mCircleCrop = false;
Owen Lin482a3a52009-06-26 15:47:39 -070068 private final Handler mHandler = new Handler();
Chih-Chung Chang724e9b32009-05-19 14:35:15 +080069
70 // These options specifiy the output image size and whether we should
71 // scale the output to fit it (or just crop it).
72 private int mOutputX, mOutputY;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080073 private boolean mScale;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080074 private boolean mScaleUp = true;
75
Chih-Chung Chang724e9b32009-05-19 14:35:15 +080076 boolean mWaitingToPick; // Whether we are wait the user to pick a face.
77 boolean mSaving; // Whether the "save" button is already clicked.
Owen Lin482a3a52009-06-26 15:47:39 -070078
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -070079 private CropImageView mImageView;
80 private ContentResolver mContentResolver;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080081
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -070082 private Bitmap mBitmap;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080083 HighlightView mCrop;
84
Chih-Chung Chang724e9b32009-05-19 14:35:15 +080085 private IImageList mAllImages;
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -070086 private IImage mImage;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080087
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080088 @Override
89 public void onCreate(Bundle icicle) {
90 super.onCreate(icicle);
91 mContentResolver = getContentResolver();
92
93 requestWindowFeature(Window.FEATURE_NO_TITLE);
94 setContentView(R.layout.cropimage);
95
96 mImageView = (CropImageView) findViewById(R.id.image);
97
98 MenuHelper.showStorageToast(this);
99
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800100 Intent intent = getIntent();
101 Bundle extras = intent.getExtras();
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800102
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800103 if (extras != null) {
104 if (extras.getString("circleCrop") != null) {
105 mCircleCrop = true;
106 mAspectX = 1;
107 mAspectY = 1;
108 }
109 mSaveUri = (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT);
110 if (mSaveUri != null) {
111 String outputFormatString = extras.getString("outputFormat");
112 if (outputFormatString != null) {
113 mOutputFormat = Bitmap.CompressFormat.valueOf(
114 outputFormatString);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800115 }
Chih-Chung Changc830ac22009-08-24 16:42:21 +0800116 } else {
117 mSetWallpaper = extras.getBoolean("setWallpaper");
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800118 }
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800119 mBitmap = (Bitmap) extras.getParcelable("data");
120 mAspectX = extras.getInt("aspectX");
121 mAspectY = extras.getInt("aspectY");
122 mOutputX = extras.getInt("outputX");
123 mOutputY = extras.getInt("outputY");
124 mScale = extras.getBoolean("scale", true);
125 mScaleUp = extras.getBoolean("scaleUpIfNeeded", true);
126 mDoFaceDetection = extras.containsKey("noFaceDetection")
127 ? !extras.getBoolean("noFaceDetection")
128 : true;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800129 }
130
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800131 if (mBitmap == null) {
132 Uri target = intent.getData();
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800133 mAllImages = ImageManager.makeImageList(mContentResolver, target,
134 ImageManager.SORT_ASCENDING);
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800135 mImage = mAllImages.getImageForUri(target);
136 if (mImage != null) {
137 // Don't read in really large bitmaps. Use the (big) thumbnail
138 // instead.
139 // TODO when saving the resulting bitmap use the
140 // decode/crop/encode api so we don't lose any resolution.
Ray Chen012d0f32009-07-20 16:33:41 +0800141 mBitmap = mImage.thumbBitmap(IImage.ROTATE_AS_NEEDED);
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800142 }
143 }
144
145 if (mBitmap == null) {
146 finish();
147 return;
148 }
149
Wu-cheng Li6b6a6d52009-07-31 14:29:08 +0800150 // Make UI fullscreen.
151 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
152
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700153 findViewById(R.id.discard).setOnClickListener(
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800154 new View.OnClickListener() {
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700155 public void onClick(View v) {
156 setResult(RESULT_CANCELED);
157 finish();
158 }
159 });
160
161 findViewById(R.id.save).setOnClickListener(
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800162 new View.OnClickListener() {
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700163 public void onClick(View v) {
164 onSaveClicked();
165 }
166 });
Owen Lin482a3a52009-06-26 15:47:39 -0700167
168 startFaceDetection();
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700169 }
170
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700171 private void startFaceDetection() {
172 if (isFinishing()) {
173 return;
174 }
Owen Lin482a3a52009-06-26 15:47:39 -0700175
Chih-Chung Chang7db87ab2009-04-12 21:42:43 -0700176 mImageView.setImageBitmapResetBase(mBitmap, true);
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700177
Owen Lin482a3a52009-06-26 15:47:39 -0700178 Util.startBackgroundJob(this, null,
179 getResources().getString(R.string.runningFaceDetection),
180 new Runnable() {
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700181 public void run() {
Owen Lin482a3a52009-06-26 15:47:39 -0700182 final CountDownLatch latch = new CountDownLatch(1);
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700183 final Bitmap b = (mImage != null)
Chih-Chung Changce033a52009-07-27 13:03:29 +0800184 ? mImage.fullSizeBitmap(IImage.UNCONSTRAINED,
185 1024 * 1024)
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700186 : mBitmap;
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700187 mHandler.post(new Runnable() {
188 public void run() {
189 if (b != mBitmap && b != null) {
Chih-Chung Chang7db87ab2009-04-12 21:42:43 -0700190 mImageView.setImageBitmapResetBase(b, true);
Chih-Chung Chang93f74652009-07-09 19:37:33 +0800191 mBitmap.recycle();
192 mBitmap = b;
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700193 }
194 if (mImageView.getScale() == 1F) {
Chih-Chung Chang0a475e12009-04-16 11:42:12 +0800195 mImageView.center(true, true);
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700196 }
Owen Lin482a3a52009-06-26 15:47:39 -0700197 latch.countDown();
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700198 }
199 });
Owen Lin482a3a52009-06-26 15:47:39 -0700200 try {
201 latch.await();
202 } catch (InterruptedException e) {
203 throw new RuntimeException(e);
204 }
205 mRunFaceDetection.run();
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700206 }
Owen Lin482a3a52009-06-26 15:47:39 -0700207 }, mHandler);
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700208 }
Owen Lin937fc482009-04-14 02:02:51 -0700209
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700210 private void onSaveClicked() {
211 // TODO this code needs to change to use the decode/crop/encode single
212 // step api so that we don't require that the whole (possibly large)
213 // bitmap doesn't have to be read into memory
Chih-Chung Chang93f74652009-07-09 19:37:33 +0800214 if (mCrop == null) {
215 return;
216 }
217
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800218 if (mSaving) return;
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700219 mSaving = true;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800220
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800221 Bitmap croppedImage;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800222
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800223 // If the output is required to a specific size, create an new image
224 // with the cropped image in the center and the extra space filled.
225 if (mOutputX != 0 && mOutputY != 0 && !mScale) {
226 // Don't scale the image but instead fill it so it's the
227 // required dimension
228 croppedImage = Bitmap.createBitmap(mOutputX, mOutputY,
229 Bitmap.Config.RGB_565);
230 Canvas canvas = new Canvas(croppedImage);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800231
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800232 Rect srcRect = mCrop.getCropRect();
233 Rect dstRect = new Rect(0, 0, mOutputX, mOutputY);
234
235 int dx = (srcRect.width() - dstRect.width()) / 2;
236 int dy = (srcRect.height() - dstRect.height()) / 2;
237
238 // If the srcRect is too big, use the center part of it.
239 srcRect.inset(Math.max(0, dx), Math.max(0, dy));
240
241 // If the dstRect is too big, use the center part of it.
242 dstRect.inset(Math.max(0, -dx), Math.max(0, -dy));
243
244 // Draw the cropped bitmap in the center
245 canvas.drawBitmap(mBitmap, srcRect, dstRect, null);
246
247 // Release bitmap memory as soon as possible
248 mImageView.clear();
249 mBitmap.recycle();
250 } else {
251 Rect r = mCrop.getCropRect();
252
253 int width = r.width();
254 int height = r.height();
255
256 // If we are circle cropping, we want alpha channel, which is the
257 // third param here.
258 croppedImage = Bitmap.createBitmap(width, height,
259 mCircleCrop
260 ? Bitmap.Config.ARGB_8888
261 : Bitmap.Config.RGB_565);
262
Chih-Chung Chang93f74652009-07-09 19:37:33 +0800263 Canvas canvas = new Canvas(croppedImage);
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800264 Rect dstRect = new Rect(0, 0, width, height);
265 canvas.drawBitmap(mBitmap, r, dstRect, null);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800266
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800267 // Release bitmap memory as soon as possible
268 mImageView.clear();
269 mBitmap.recycle();
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800270
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800271 if (mCircleCrop) {
272 // OK, so what's all this about?
273 // Bitmaps are inherently rectangular but we want to return
274 // something that's basically a circle. So we fill in the
275 // area around the circle with alpha. Note the all important
276 // PortDuff.Mode.CLEAR.
277 Canvas c = new Canvas(croppedImage);
278 Path p = new Path();
279 p.addCircle(width / 2F, height / 2F, width / 2F,
280 Path.Direction.CW);
281 c.clipPath(p, Region.Op.DIFFERENCE);
282 c.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
283 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800284
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800285 // If the required dimension is specified, scale the image.
286 if (mOutputX != 0 && mOutputY != 0 && mScale) {
287 croppedImage = Util.transform(new Matrix(), croppedImage,
288 mOutputX, mOutputY, mScaleUp, Util.RECYCLE_INPUT);
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700289 }
290 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800291
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800292 mImageView.setImageBitmapResetBase(croppedImage, true);
293 mImageView.center(true, true);
294 mImageView.mHighlightViews.clear();
295
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700296 // Return the cropped image directly or save it to the specified URI.
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700297 Bundle myExtras = getIntent().getExtras();
298 if (myExtras != null && (myExtras.getParcelable("data") != null
299 || myExtras.getBoolean("return-data"))) {
300 Bundle extras = new Bundle();
Chih-Chung Chang93f74652009-07-09 19:37:33 +0800301 extras.putParcelable("data", croppedImage);
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700302 setResult(RESULT_OK,
303 (new Intent()).setAction("inline-data").putExtras(extras));
304 finish();
305 } else {
Chih-Chung Chang93f74652009-07-09 19:37:33 +0800306 final Bitmap b = croppedImage;
Chih-Chung Chang17dc8112009-08-24 17:58:49 +0800307 final int msdId = mSetWallpaper
308 ? R.string.wallpaper
309 : R.string.savingImage;
Owen Lin482a3a52009-06-26 15:47:39 -0700310 Util.startBackgroundJob(this, null,
Chih-Chung Chang17dc8112009-08-24 17:58:49 +0800311 getResources().getString(msdId),
Owen Lin482a3a52009-06-26 15:47:39 -0700312 new Runnable() {
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700313 public void run() {
Chih-Chung Chang93f74652009-07-09 19:37:33 +0800314 saveOutput(b);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800315 }
Owen Lin482a3a52009-06-26 15:47:39 -0700316 }, mHandler);
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800317 }
318 }
319
Chih-Chung Chang93f74652009-07-09 19:37:33 +0800320 private void saveOutput(Bitmap croppedImage) {
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800321 if (mSaveUri != null) {
322 OutputStream outputStream = null;
323 try {
324 outputStream = mContentResolver.openOutputStream(mSaveUri);
325 if (outputStream != null) {
Chih-Chung Chang93f74652009-07-09 19:37:33 +0800326 croppedImage.compress(mOutputFormat, 75, outputStream);
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800327 }
328 } catch (IOException ex) {
Owen Lin33025342009-05-20 21:44:47 -0700329 // TODO: report error to caller
330 Log.e(TAG, "Cannot open file: " + mSaveUri, ex);
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800331 } finally {
332 Util.closeSilently(outputStream);
333 }
334 Bundle extras = new Bundle();
335 setResult(RESULT_OK, new Intent(mSaveUri.toString())
336 .putExtras(extras));
Chih-Chung Changc830ac22009-08-24 16:42:21 +0800337 } else if (mSetWallpaper) {
338 try {
339 WallpaperManager.getInstance(this).setBitmap(croppedImage);
340 setResult(RESULT_OK);
341 } catch (IOException e) {
342 Log.e(TAG, "Failed to set wallpaper.", e);
343 setResult(RESULT_CANCELED);
344 }
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800345 } else {
346 Bundle extras = new Bundle();
347 extras.putString("rect", mCrop.getCropRect().toString());
348
349 File oldPath = new File(mImage.getDataPath());
350 File directory = new File(oldPath.getParent());
351
352 int x = 0;
353 String fileName = oldPath.getName();
354 fileName = fileName.substring(0, fileName.lastIndexOf("."));
355
356 // Try file-1.jpg, file-2.jpg, ... until we find a filename which
357 // does not exist yet.
358 while (true) {
359 x += 1;
360 String candidate = directory.toString()
361 + "/" + fileName + "-" + x + ".jpg";
362 boolean exists = (new File(candidate)).exists();
363 if (!exists) {
364 break;
365 }
366 }
367
368 try {
Chih-Chung Chang140229c2009-09-28 10:47:18 -0700369 int[] degree = new int[1];
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800370 Uri newUri = ImageManager.addImage(
371 mContentResolver,
372 mImage.getTitle(),
373 mImage.getDateTaken(),
374 null, // TODO this null is going to cause us to lose
375 // the location (gps).
Ray Chenb02ebbe2009-09-25 13:43:31 -0700376 directory.toString(), fileName + "-" + x + ".jpg",
Chih-Chung Chang140229c2009-09-28 10:47:18 -0700377 croppedImage, null,
378 degree);
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800379
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800380 setResult(RESULT_OK, new Intent()
381 .setAction(newUri.toString())
382 .putExtras(extras));
383 } catch (Exception ex) {
384 // basically ignore this or put up
385 // some ui saying we failed
Owen Lin482a3a52009-06-26 15:47:39 -0700386 Log.e(TAG, "store image fail, continue anyway", ex);
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800387 }
388 }
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800389
390 final Bitmap b = croppedImage;
391 mHandler.post(new Runnable() {
392 public void run() {
393 mImageView.clear();
394 b.recycle();
395 }
396 });
397
Chih-Chung Chang724e9b32009-05-19 14:35:15 +0800398 finish();
Ray Chen993105a2009-04-10 02:11:35 -0700399 }
Owen Lin937fc482009-04-14 02:02:51 -0700400
401 @Override
Owen Lin482a3a52009-06-26 15:47:39 -0700402 protected void onPause() {
Ray Chen993105a2009-04-10 02:11:35 -0700403 super.onPause();
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800404 }
405
Owen Lin482a3a52009-06-26 15:47:39 -0700406 @Override
407 protected void onDestroy() {
Chih-Chung Chang048e9db2009-07-31 12:28:40 +0800408 if (mAllImages != null) {
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800409 mAllImages.close();
Chih-Chung Chang048e9db2009-07-31 12:28:40 +0800410 }
Owen Lin482a3a52009-06-26 15:47:39 -0700411 super.onDestroy();
412 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800413
414 Runnable mRunFaceDetection = new Runnable() {
Owen Linbbc560b2009-04-17 11:31:27 +0800415 @SuppressWarnings("hiding")
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800416 float mScale = 1F;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800417 Matrix mImageMatrix;
418 FaceDetector.Face[] mFaces = new FaceDetector.Face[3];
419 int mNumFaces;
420
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700421 // For each face, we create a HightlightView for it.
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800422 private void handleFace(FaceDetector.Face f) {
423 PointF midPoint = new PointF();
424
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700425 int r = ((int) (f.eyesDistance() * mScale)) * 2;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800426 f.getMidPoint(midPoint);
427 midPoint.x *= mScale;
428 midPoint.y *= mScale;
429
430 int midX = (int) midPoint.x;
431 int midY = (int) midPoint.y;
432
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700433 HighlightView hv = new HighlightView(mImageView);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800434
435 int width = mBitmap.getWidth();
436 int height = mBitmap.getHeight();
437
438 Rect imageRect = new Rect(0, 0, width, height);
439
440 RectF faceRect = new RectF(midX, midY, midX, midY);
441 faceRect.inset(-r, -r);
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700442 if (faceRect.left < 0) {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800443 faceRect.inset(-faceRect.left, -faceRect.left);
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700444 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800445
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700446 if (faceRect.top < 0) {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800447 faceRect.inset(-faceRect.top, -faceRect.top);
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700448 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800449
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700450 if (faceRect.right > imageRect.right) {
451 faceRect.inset(faceRect.right - imageRect.right,
452 faceRect.right - imageRect.right);
453 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800454
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700455 if (faceRect.bottom > imageRect.bottom) {
456 faceRect.inset(faceRect.bottom - imageRect.bottom,
457 faceRect.bottom - imageRect.bottom);
458 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800459
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700460 hv.setup(mImageMatrix, imageRect, faceRect, mCircleCrop,
461 mAspectX != 0 && mAspectY != 0);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800462
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800463 mImageView.add(hv);
464 }
465
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700466 // Create a default HightlightView if we found no face in the picture.
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800467 private void makeDefault() {
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700468 HighlightView hv = new HighlightView(mImageView);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800469
470 int width = mBitmap.getWidth();
471 int height = mBitmap.getHeight();
472
473 Rect imageRect = new Rect(0, 0, width, height);
474
475 // make the default size about 4/5 of the width or height
476 int cropWidth = Math.min(width, height) * 4 / 5;
477 int cropHeight = cropWidth;
478
479 if (mAspectX != 0 && mAspectY != 0) {
480 if (mAspectX > mAspectY) {
481 cropHeight = cropWidth * mAspectY / mAspectX;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800482 } else {
483 cropWidth = cropHeight * mAspectX / mAspectY;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800484 }
485 }
486
487 int x = (width - cropWidth) / 2;
488 int y = (height - cropHeight) / 2;
489
490 RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700491 hv.setup(mImageMatrix, imageRect, cropRect, mCircleCrop,
492 mAspectX != 0 && mAspectY != 0);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800493 mImageView.add(hv);
494 }
495
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700496 // Scale the image down for faster face detection.
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800497 private Bitmap prepareBitmap() {
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700498 if (mBitmap == null) {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800499 return null;
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700500 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800501
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800502 // 256 pixels wide is enough.
503 if (mBitmap.getWidth() > 256) {
Owen Lin937fc482009-04-14 02:02:51 -0700504 mScale = 256.0F / mBitmap.getWidth();
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800505 }
506 Matrix matrix = new Matrix();
507 matrix.setScale(mScale, mScale);
508 Bitmap faceBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap
509 .getWidth(), mBitmap.getHeight(), matrix, true);
510 return faceBitmap;
511 }
512
513 public void run() {
514 mImageMatrix = mImageView.getImageMatrix();
515 Bitmap faceBitmap = prepareBitmap();
516
517 mScale = 1.0F / mScale;
518 if (faceBitmap != null && mDoFaceDetection) {
519 FaceDetector detector = new FaceDetector(faceBitmap.getWidth(),
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700520 faceBitmap.getHeight(), mFaces.length);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800521 mNumFaces = detector.findFaces(faceBitmap, mFaces);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800522 }
Chih-Chung Chang93f74652009-07-09 19:37:33 +0800523
524 if (faceBitmap != null && faceBitmap != mBitmap) {
525 faceBitmap.recycle();
526 }
527
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800528 mHandler.post(new Runnable() {
529 public void run() {
530 mWaitingToPick = mNumFaces > 1;
531 if (mNumFaces > 0) {
532 for (int i = 0; i < mNumFaces; i++) {
533 handleFace(mFaces[i]);
534 }
535 } else {
536 makeDefault();
537 }
538 mImageView.invalidate();
539 if (mImageView.mHighlightViews.size() == 1) {
540 mCrop = mImageView.mHighlightViews.get(0);
541 mCrop.setFocus(true);
542 }
543
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800544 if (mNumFaces > 1) {
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700545 Toast t = Toast.makeText(CropImage.this,
546 R.string.multiface_crop_help,
547 Toast.LENGTH_SHORT);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800548 t.show();
549 }
550 }
551 });
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800552 }
553 };
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800554}
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700555
556class CropImageView extends ImageViewTouchBase {
557 ArrayList<HighlightView> mHighlightViews = new ArrayList<HighlightView>();
558 HighlightView mMotionHighlightView = null;
559 float mLastX, mLastY;
560 int mMotionEdge;
561
562 @Override
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700563 protected void onLayout(boolean changed, int left, int top,
564 int right, int bottom) {
565 super.onLayout(changed, left, top, right, bottom);
Chih-Chung Chang1f463a62009-07-30 17:59:09 +0800566 if (mBitmapDisplayed.getBitmap() != null) {
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700567 for (HighlightView hv : mHighlightViews) {
568 hv.mMatrix.set(getImageMatrix());
569 hv.invalidate();
570 if (hv.mIsFocused) {
571 centerBasedOnHighlightView(hv);
572 }
573 }
574 }
575 }
576
577 public CropImageView(Context context, AttributeSet attrs) {
578 super(context, attrs);
579 }
580
Owen Lin937fc482009-04-14 02:02:51 -0700581 @Override
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700582 protected void zoomTo(float scale, float centerX, float centerY) {
583 super.zoomTo(scale, centerX, centerY);
584 for (HighlightView hv : mHighlightViews) {
585 hv.mMatrix.set(getImageMatrix());
586 hv.invalidate();
587 }
588 }
589
Owen Lin937fc482009-04-14 02:02:51 -0700590 @Override
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700591 protected void zoomIn() {
592 super.zoomIn();
593 for (HighlightView hv : mHighlightViews) {
594 hv.mMatrix.set(getImageMatrix());
595 hv.invalidate();
596 }
597 }
598
Owen Lin937fc482009-04-14 02:02:51 -0700599 @Override
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700600 protected void zoomOut() {
601 super.zoomOut();
602 for (HighlightView hv : mHighlightViews) {
603 hv.mMatrix.set(getImageMatrix());
604 hv.invalidate();
605 }
606 }
607
608 @Override
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700609 protected void postTranslate(float deltaX, float deltaY) {
610 super.postTranslate(deltaX, deltaY);
611 for (int i = 0; i < mHighlightViews.size(); i++) {
612 HighlightView hv = mHighlightViews.get(i);
613 hv.mMatrix.postTranslate(deltaX, deltaY);
614 hv.invalidate();
615 }
616 }
617
618 // According to the event's position, change the focus to the first
619 // hitting cropping rectangle.
620 private void recomputeFocus(MotionEvent event) {
621 for (int i = 0; i < mHighlightViews.size(); i++) {
622 HighlightView hv = mHighlightViews.get(i);
623 hv.setFocus(false);
624 hv.invalidate();
625 }
626
627 for (int i = 0; i < mHighlightViews.size(); i++) {
628 HighlightView hv = mHighlightViews.get(i);
629 int edge = hv.getHit(event.getX(), event.getY());
630 if (edge != HighlightView.GROW_NONE) {
631 if (!hv.hasFocus()) {
632 hv.setFocus(true);
633 hv.invalidate();
634 }
635 break;
636 }
637 }
638 invalidate();
639 }
640
641 @Override
642 public boolean onTouchEvent(MotionEvent event) {
643 CropImage cropImage = (CropImage) mContext;
644 if (cropImage.mSaving) {
645 return false;
646 }
647
648 switch (event.getAction()) {
649 case MotionEvent.ACTION_DOWN:
650 if (cropImage.mWaitingToPick) {
651 recomputeFocus(event);
652 } else {
653 for (int i = 0; i < mHighlightViews.size(); i++) {
654 HighlightView hv = mHighlightViews.get(i);
655 int edge = hv.getHit(event.getX(), event.getY());
656 if (edge != HighlightView.GROW_NONE) {
657 mMotionEdge = edge;
658 mMotionHighlightView = hv;
659 mLastX = event.getX();
660 mLastY = event.getY();
661 mMotionHighlightView.setMode(
662 (edge == HighlightView.MOVE)
663 ? HighlightView.ModifyMode.Move
664 : HighlightView.ModifyMode.Grow);
665 break;
666 }
667 }
668 }
669 break;
670 case MotionEvent.ACTION_UP:
671 if (cropImage.mWaitingToPick) {
672 for (int i = 0; i < mHighlightViews.size(); i++) {
673 HighlightView hv = mHighlightViews.get(i);
674 if (hv.hasFocus()) {
675 cropImage.mCrop = hv;
676 for (int j = 0; j < mHighlightViews.size(); j++) {
677 if (j == i) {
678 continue;
679 }
680 mHighlightViews.get(j).setHidden(true);
681 }
682 centerBasedOnHighlightView(hv);
683 ((CropImage) mContext).mWaitingToPick = false;
684 return true;
685 }
686 }
687 } else if (mMotionHighlightView != null) {
688 centerBasedOnHighlightView(mMotionHighlightView);
689 mMotionHighlightView.setMode(
690 HighlightView.ModifyMode.None);
691 }
692 mMotionHighlightView = null;
693 break;
694 case MotionEvent.ACTION_MOVE:
695 if (cropImage.mWaitingToPick) {
696 recomputeFocus(event);
697 } else if (mMotionHighlightView != null) {
698 mMotionHighlightView.handleMotion(mMotionEdge,
699 event.getX() - mLastX,
700 event.getY() - mLastY);
701 mLastX = event.getX();
702 mLastY = event.getY();
703
704 if (true) {
705 // This section of code is optional. It has some user
706 // benefit in that moving the crop rectangle against
707 // the edge of the screen causes scrolling but it means
708 // that the crop rectangle is no longer fixed under
709 // the user's finger.
710 ensureVisible(mMotionHighlightView);
711 }
712 }
713 break;
714 }
715
716 switch (event.getAction()) {
717 case MotionEvent.ACTION_UP:
Chih-Chung Chang0a475e12009-04-16 11:42:12 +0800718 center(true, true);
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700719 break;
720 case MotionEvent.ACTION_MOVE:
721 // if we're not zoomed then there's no point in even allowing
722 // the user to move the image around. This call to center puts
723 // it back to the normalized location (with false meaning don't
724 // animate).
725 if (getScale() == 1F) {
Chih-Chung Chang0a475e12009-04-16 11:42:12 +0800726 center(true, true);
Chih-Chung Chang8f7c4a42009-04-08 22:35:03 -0700727 }
728 break;
729 }
730
731 return true;
732 }
733
734 // Pan the displayed image to make sure the cropping rectangle is visible.
735 private void ensureVisible(HighlightView hv) {
736 Rect r = hv.mDrawRect;
737
738 int panDeltaX1 = Math.max(0, mLeft - r.left);
739 int panDeltaX2 = Math.min(0, mRight - r.right);
740
741 int panDeltaY1 = Math.max(0, mTop - r.top);
742 int panDeltaY2 = Math.min(0, mBottom - r.bottom);
743
744 int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
745 int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;
746
747 if (panDeltaX != 0 || panDeltaY != 0) {
748 panBy(panDeltaX, panDeltaY);
749 }
750 }
751
752 // If the cropping rectangle's size changed significantly, change the
753 // view's center and scale according to the cropping rectangle.
754 private void centerBasedOnHighlightView(HighlightView hv) {
755 Rect drawRect = hv.mDrawRect;
756
757 float width = drawRect.width();
758 float height = drawRect.height();
759
760 float thisWidth = getWidth();
761 float thisHeight = getHeight();
762
763 float z1 = thisWidth / width * .6F;
764 float z2 = thisHeight / height * .6F;
765
766 float zoom = Math.min(z1, z2);
767 zoom = zoom * this.getScale();
768 zoom = Math.max(1F, zoom);
769
770 if ((Math.abs(zoom - getScale()) / zoom) > .1) {
771 float [] coordinates = new float[] {hv.mCropRect.centerX(),
772 hv.mCropRect.centerY()};
773 getImageMatrix().mapPoints(coordinates);
774 zoomTo(zoom, coordinates[0], coordinates[1], 300F);
775 }
776
777 ensureVisible(hv);
778 }
779
780 @Override
781 protected void onDraw(Canvas canvas) {
782 super.onDraw(canvas);
783 for (int i = 0; i < mHighlightViews.size(); i++) {
784 mHighlightViews.get(i).draw(canvas);
785 }
786 }
787
788 public void add(HighlightView hv) {
789 mHighlightViews.add(hv);
790 invalidate();
791 }
792}