summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/app/CropImage.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/app/CropImage.java')
-rw-r--r--src/com/android/gallery3d/app/CropImage.java850
1 files changed, 850 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java
new file mode 100644
index 000000000..6c0a0c7eb
--- /dev/null
+++ b/src/com/android/gallery3d/app/CropImage.java
@@ -0,0 +1,850 @@
+/*
+ * 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.app;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.LocalImage;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.picasasource.PicasaSource;
+import com.android.gallery3d.ui.BitmapTileProvider;
+import com.android.gallery3d.ui.CropView;
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.SynchronizedHandler;
+import com.android.gallery3d.ui.TileImageViewAdapter;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.InterruptableOutputStream;
+import com.android.gallery3d.util.ThreadPool.CancelListener;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+import android.app.ProgressDialog;
+import android.app.WallpaperManager;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.Window;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * The activity can crop specific region of interest from an image.
+ */
+public class CropImage extends AbstractGalleryActivity {
+ private static final String TAG = "CropImage";
+ public static final String ACTION_CROP = "com.android.camera.action.CROP";
+
+ private static final int MAX_PIXEL_COUNT = 5 * 1000000; // 5M pixels
+ private static final int MAX_FILE_INDEX = 1000;
+ private static final int TILE_SIZE = 512;
+ private static final int BACKUP_PIXEL_COUNT = 480000; // around 800x600
+
+ private static final int MSG_LARGE_BITMAP = 1;
+ private static final int MSG_BITMAP = 2;
+ private static final int MSG_SAVE_COMPLETE = 3;
+
+ private static final int MAX_BACKUP_IMAGE_SIZE = 320;
+ private static final int DEFAULT_COMPRESS_QUALITY = 90;
+
+ public static final String KEY_RETURN_DATA = "return-data";
+ public static final String KEY_CROPPED_RECT = "cropped-rect";
+ public static final String KEY_ASPECT_X = "aspectX";
+ public static final String KEY_ASPECT_Y = "aspectY";
+ public static final String KEY_SPOTLIGHT_X = "spotlightX";
+ public static final String KEY_SPOTLIGHT_Y = "spotlightY";
+ public static final String KEY_OUTPUT_X = "outputX";
+ public static final String KEY_OUTPUT_Y = "outputY";
+ public static final String KEY_SCALE = "scale";
+ public static final String KEY_DATA = "data";
+ public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded";
+ public static final String KEY_OUTPUT_FORMAT = "outputFormat";
+ public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper";
+ public static final String KEY_NO_FACE_DETECTION = "noFaceDetection";
+
+ private static final String KEY_STATE = "state";
+
+ private static final int STATE_INIT = 0;
+ private static final int STATE_LOADED = 1;
+ private static final int STATE_SAVING = 2;
+
+ public static final String DOWNLOAD_STRING = "download";
+ public static final File DOWNLOAD_BUCKET = new File(
+ Environment.getExternalStorageDirectory(), DOWNLOAD_STRING);
+
+ public static final String CROP_ACTION = "com.android.camera.action.CROP";
+
+ private int mState = STATE_INIT;
+
+ private CropView mCropView;
+
+ private boolean mDoFaceDetection = true;
+
+ private Handler mMainHandler;
+
+ // We keep the following members so that we can free them
+
+ // mBitmap is the unrotated bitmap we pass in to mCropView for detect faces.
+ // mCropView is responsible for rotating it to the way that it is viewed by users.
+ private Bitmap mBitmap;
+ private BitmapTileProvider mBitmapTileProvider;
+ private BitmapRegionDecoder mRegionDecoder;
+ private Bitmap mBitmapInIntent;
+ private boolean mUseRegionDecoder = false;
+
+ private ProgressDialog mProgressDialog;
+ private Future<BitmapRegionDecoder> mLoadTask;
+ private Future<Bitmap> mLoadBitmapTask;
+ private Future<Intent> mSaveTask;
+
+ private MediaItem mMediaItem;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ requestWindowFeature(Window.FEATURE_ACTION_BAR);
+ requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+
+ // Initialize UI
+ setContentView(R.layout.cropimage);
+ mCropView = new CropView(this);
+ getGLRoot().setContentPane(mCropView);
+
+ mMainHandler = new SynchronizedHandler(getGLRoot()) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_LARGE_BITMAP: {
+ mProgressDialog.dismiss();
+ onBitmapRegionDecoderAvailable((BitmapRegionDecoder) message.obj);
+ break;
+ }
+ case MSG_BITMAP: {
+ mProgressDialog.dismiss();
+ onBitmapAvailable((Bitmap) message.obj);
+ break;
+ }
+ case MSG_SAVE_COMPLETE: {
+ mProgressDialog.dismiss();
+ setResult(RESULT_OK, (Intent) message.obj);
+ finish();
+ break;
+ }
+ }
+ }
+ };
+
+ setCropParameters();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle saveState) {
+ saveState.putInt(KEY_STATE, mState);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.crop, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.cancel: {
+ setResult(RESULT_CANCELED);
+ finish();
+ break;
+ }
+ case R.id.save: {
+ onSaveClicked();
+ break;
+ }
+ }
+ return true;
+ }
+
+ private class SaveOutput implements Job<Intent> {
+ private RectF mCropRect;
+
+ public SaveOutput(RectF cropRect) {
+ mCropRect = cropRect;
+ }
+
+ public Intent run(JobContext jc) {
+ RectF cropRect = mCropRect;
+ Bundle extra = getIntent().getExtras();
+
+ Rect rect = new Rect(
+ Math.round(cropRect.left), Math.round(cropRect.top),
+ Math.round(cropRect.right), Math.round(cropRect.bottom));
+
+ Intent result = new Intent();
+ result.putExtra(KEY_CROPPED_RECT, rect);
+ Bitmap cropped = null;
+ boolean outputted = false;
+ if (extra != null) {
+ Uri uri = (Uri) extra.getParcelable(MediaStore.EXTRA_OUTPUT);
+ if (uri != null) {
+ if (jc.isCancelled()) return null;
+ outputted = true;
+ cropped = getCroppedImage(rect);
+ if (!saveBitmapToUri(jc, cropped, uri)) return null;
+ }
+ if (extra.getBoolean(KEY_RETURN_DATA, false)) {
+ if (jc.isCancelled()) return null;
+ outputted = true;
+ if (cropped == null) cropped = getCroppedImage(rect);
+ result.putExtra(KEY_DATA, cropped);
+ }
+ if (extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) {
+ if (jc.isCancelled()) return null;
+ outputted = true;
+ if (cropped == null) cropped = getCroppedImage(rect);
+ if (!setAsWallpaper(jc, cropped)) return null;
+ }
+ }
+ if (!outputted) {
+ if (jc.isCancelled()) return null;
+ if (cropped == null) cropped = getCroppedImage(rect);
+ Uri data = saveToMediaProvider(jc, cropped);
+ if (data != null) result.setData(data);
+ }
+ return result;
+ }
+ }
+
+ public static String determineCompressFormat(MediaObject obj) {
+ String compressFormat = "JPEG";
+ if (obj instanceof MediaItem) {
+ String mime = ((MediaItem) obj).getMimeType();
+ if (mime.contains("png") || mime.contains("gif")) {
+ // Set the compress format to PNG for png and gif images
+ // because they may contain alpha values.
+ compressFormat = "PNG";
+ }
+ }
+ return compressFormat;
+ }
+
+ private boolean setAsWallpaper(JobContext jc, Bitmap wallpaper) {
+ try {
+ WallpaperManager.getInstance(this).setBitmap(wallpaper);
+ } catch (IOException e) {
+ Log.w(TAG, "fail to set wall paper", e);
+ }
+ return true;
+ }
+
+ private File saveMedia(
+ JobContext jc, Bitmap cropped, File directory, String filename) {
+ // Try file-1.jpg, file-2.jpg, ... until we find a filename
+ // which does not exist yet.
+ File candidate = null;
+ String fileExtension = getFileExtension();
+ for (int i = 1; i < MAX_FILE_INDEX; ++i) {
+ candidate = new File(directory, filename + "-" + i + "."
+ + fileExtension);
+ try {
+ if (candidate.createNewFile()) break;
+ } catch (IOException e) {
+ Log.e(TAG, "fail to create new file: "
+ + candidate.getAbsolutePath(), e);
+ return null;
+ }
+ }
+ if (!candidate.exists() || !candidate.isFile()) {
+ throw new RuntimeException("cannot create file: " + filename);
+ }
+
+ candidate.setReadable(true, false);
+ candidate.setWritable(true, false);
+
+ try {
+ FileOutputStream fos = new FileOutputStream(candidate);
+ try {
+ saveBitmapToOutputStream(jc, cropped,
+ convertExtensionToCompressFormat(fileExtension), fos);
+ } finally {
+ fos.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "fail to save image: "
+ + candidate.getAbsolutePath(), e);
+ candidate.delete();
+ return null;
+ }
+
+ if (jc.isCancelled()) {
+ candidate.delete();
+ return null;
+ }
+
+ return candidate;
+ }
+
+ private Uri saveToMediaProvider(JobContext jc, Bitmap cropped) {
+ if (PicasaSource.isPicasaImage(mMediaItem)) {
+ return savePicasaImage(jc, cropped);
+ } else if (mMediaItem instanceof LocalImage) {
+ return saveLocalImage(jc, cropped);
+ } else {
+ Log.w(TAG, "no output for crop image " + mMediaItem);
+ return null;
+ }
+ }
+
+ private Uri savePicasaImage(JobContext jc, Bitmap cropped) {
+ if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) {
+ throw new RuntimeException("cannot create download folder");
+ }
+
+ String filename = PicasaSource.getImageTitle(mMediaItem);
+ int pos = filename.lastIndexOf('.');
+ if (pos >= 0) filename = filename.substring(0, pos);
+ File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename);
+ if (output == null) return null;
+
+ long now = System.currentTimeMillis() / 1000;
+ ContentValues values = new ContentValues();
+ values.put(Images.Media.TITLE, PicasaSource.getImageTitle(mMediaItem));
+ values.put(Images.Media.DISPLAY_NAME, output.getName());
+ values.put(Images.Media.DATE_TAKEN, PicasaSource.getDateTaken(mMediaItem));
+ values.put(Images.Media.DATE_MODIFIED, now);
+ values.put(Images.Media.DATE_ADDED, now);
+ values.put(Images.Media.MIME_TYPE, "image/jpeg");
+ values.put(Images.Media.ORIENTATION, 0);
+ values.put(Images.Media.DATA, output.getAbsolutePath());
+ values.put(Images.Media.SIZE, output.length());
+
+ double latitude = PicasaSource.getLatitude(mMediaItem);
+ double longitude = PicasaSource.getLongitude(mMediaItem);
+ if (GalleryUtils.isValidLocation(latitude, longitude)) {
+ values.put(Images.Media.LATITUDE, latitude);
+ values.put(Images.Media.LONGITUDE, longitude);
+ }
+ return getContentResolver().insert(
+ Images.Media.EXTERNAL_CONTENT_URI, values);
+ }
+
+ private Uri saveLocalImage(JobContext jc, Bitmap cropped) {
+ LocalImage localImage = (LocalImage) mMediaItem;
+
+ File oldPath = new File(localImage.filePath);
+ File directory = new File(oldPath.getParent());
+
+ String filename = oldPath.getName();
+ int pos = filename.lastIndexOf('.');
+ if (pos >= 0) filename = filename.substring(0, pos);
+ File output = saveMedia(jc, cropped, directory, filename);
+ if (output == null) return null;
+
+ long now = System.currentTimeMillis() / 1000;
+ ContentValues values = new ContentValues();
+ values.put(Images.Media.TITLE, localImage.caption);
+ values.put(Images.Media.DISPLAY_NAME, output.getName());
+ values.put(Images.Media.DATE_TAKEN, localImage.dateTakenInMs);
+ values.put(Images.Media.DATE_MODIFIED, now);
+ values.put(Images.Media.DATE_ADDED, now);
+ values.put(Images.Media.MIME_TYPE, "image/jpeg");
+ values.put(Images.Media.ORIENTATION, 0);
+ values.put(Images.Media.DATA, output.getAbsolutePath());
+ values.put(Images.Media.SIZE, output.length());
+
+ if (GalleryUtils.isValidLocation(localImage.latitude, localImage.longitude)) {
+ values.put(Images.Media.LATITUDE, localImage.latitude);
+ values.put(Images.Media.LONGITUDE, localImage.longitude);
+ }
+ return getContentResolver().insert(
+ Images.Media.EXTERNAL_CONTENT_URI, values);
+ }
+
+ private boolean saveBitmapToOutputStream(
+ JobContext jc, Bitmap bitmap, CompressFormat format, OutputStream os) {
+ // We wrap the OutputStream so that it can be interrupted.
+ final InterruptableOutputStream ios = new InterruptableOutputStream(os);
+ jc.setCancelListener(new CancelListener() {
+ public void onCancel() {
+ ios.interrupt();
+ }
+ });
+ try {
+ bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os);
+ if (!jc.isCancelled()) return false;
+ } finally {
+ jc.setCancelListener(null);
+ Utils.closeSilently(os);
+ }
+ return false;
+ }
+
+ private boolean saveBitmapToUri(JobContext jc, Bitmap bitmap, Uri uri) {
+ try {
+ return saveBitmapToOutputStream(jc, bitmap,
+ convertExtensionToCompressFormat(getFileExtension()),
+ getContentResolver().openOutputStream(uri));
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "cannot write output", e);
+ }
+ return true;
+ }
+
+ private CompressFormat convertExtensionToCompressFormat(String extension) {
+ return extension.equals("png")
+ ? CompressFormat.PNG
+ : CompressFormat.JPEG;
+ }
+
+ private String getFileExtension() {
+ String requestFormat = getIntent().getStringExtra(KEY_OUTPUT_FORMAT);
+ String outputFormat = (requestFormat == null)
+ ? determineCompressFormat(mMediaItem)
+ : requestFormat;
+
+ outputFormat = outputFormat.toLowerCase();
+ return (outputFormat.equals("png") || outputFormat.equals("gif"))
+ ? "png" // We don't support gif compression.
+ : "jpg";
+ }
+
+ private void onSaveClicked() {
+ Bundle extra = getIntent().getExtras();
+ RectF cropRect = mCropView.getCropRectangle();
+ if (cropRect == null) return;
+ mState = STATE_SAVING;
+ int messageId = extra != null && extra.getBoolean(KEY_SET_AS_WALLPAPER)
+ ? R.string.wallpaper
+ : R.string.saving_image;
+ mProgressDialog = ProgressDialog.show(
+ this, null, getString(messageId), true, false);
+ mSaveTask = getThreadPool().submit(new SaveOutput(cropRect),
+ new FutureListener<Intent>() {
+ public void onFutureDone(Future<Intent> future) {
+ mSaveTask = null;
+ if (future.get() == null) return;
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(
+ MSG_SAVE_COMPLETE, future.get()));
+ }
+ });
+ }
+
+ private Bitmap getCroppedImage(Rect rect) {
+ Utils.assertTrue(rect.width() > 0 && rect.height() > 0);
+
+ Bundle extras = getIntent().getExtras();
+ // (outputX, outputY) = the width and height of the returning bitmap.
+ int outputX = rect.width();
+ int outputY = rect.height();
+ if (extras != null) {
+ outputX = extras.getInt(KEY_OUTPUT_X, outputX);
+ outputY = extras.getInt(KEY_OUTPUT_Y, outputY);
+ }
+
+ if (outputX * outputY > MAX_PIXEL_COUNT) {
+ float scale = (float) Math.sqrt(
+ (double) MAX_PIXEL_COUNT / outputX / outputY);
+ Log.w(TAG, "scale down the cropped image: " + scale);
+ outputX = Math.round(scale * outputX);
+ outputY = Math.round(scale * outputY);
+ }
+
+ // (rect.width() * scaleX, rect.height() * scaleY) =
+ // the size of drawing area in output bitmap
+ float scaleX = 1;
+ float scaleY = 1;
+ Rect dest = new Rect(0, 0, outputX, outputY);
+ if (extras == null || extras.getBoolean(KEY_SCALE, true)) {
+ scaleX = (float) outputX / rect.width();
+ scaleY = (float) outputY / rect.height();
+ if (extras == null || !extras.getBoolean(
+ KEY_SCALE_UP_IF_NEEDED, false)) {
+ if (scaleX > 1f) scaleX = 1;
+ if (scaleY > 1f) scaleY = 1;
+ }
+ }
+
+ // Keep the content in the center (or crop the content)
+ int rectWidth = Math.round(rect.width() * scaleX);
+ int rectHeight = Math.round(rect.height() * scaleY);
+ dest.set(Math.round((outputX - rectWidth) / 2f),
+ Math.round((outputY - rectHeight) / 2f),
+ Math.round((outputX + rectWidth) / 2f),
+ Math.round((outputY + rectHeight) / 2f));
+
+ if (mBitmapInIntent != null) {
+ Bitmap source = mBitmapInIntent;
+ Bitmap result = Bitmap.createBitmap(
+ outputX, outputY, Config.ARGB_8888);
+ Canvas canvas = new Canvas(result);
+ canvas.drawBitmap(source, rect, dest, null);
+ return result;
+ }
+
+ int rotation = mMediaItem.getRotation();
+ rotateRectangle(rect, mCropView.getImageWidth(),
+ mCropView.getImageHeight(), 360 - rotation);
+ rotateRectangle(dest, outputX, outputY, 360 - rotation);
+ if (mUseRegionDecoder) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ int sample = BitmapUtils.computeSampleSizeLarger(
+ Math.max(scaleX, scaleY));
+ options.inSampleSize = sample;
+ if ((rect.width() / sample) == dest.width()
+ && (rect.height() / sample) == dest.height()
+ && rotation == 0) {
+ // To prevent concurrent access in GLThread
+ synchronized (mRegionDecoder) {
+ return mRegionDecoder.decodeRegion(rect, options);
+ }
+ }
+ Bitmap result = Bitmap.createBitmap(
+ outputX, outputY, Config.ARGB_8888);
+ Canvas canvas = new Canvas(result);
+ rotateCanvas(canvas, outputX, outputY, rotation);
+ drawInTiles(canvas, mRegionDecoder, rect, dest, sample);
+ return result;
+ } else {
+ Bitmap result = Bitmap.createBitmap(outputX, outputY, Config.ARGB_8888);
+ Canvas canvas = new Canvas(result);
+ rotateCanvas(canvas, outputX, outputY, rotation);
+ canvas.drawBitmap(mBitmap,
+ rect, dest, new Paint(Paint.FILTER_BITMAP_FLAG));
+ return result;
+ }
+ }
+
+ private static void rotateCanvas(
+ Canvas canvas, int width, int height, int rotation) {
+ canvas.translate(width / 2, height / 2);
+ canvas.rotate(rotation);
+ if (((rotation / 90) & 0x01) == 0) {
+ canvas.translate(-width / 2, -height / 2);
+ } else {
+ canvas.translate(-height / 2, -width / 2);
+ }
+ }
+
+ private static void rotateRectangle(
+ Rect rect, int width, int height, int rotation) {
+ if (rotation == 0 || rotation == 360) return;
+
+ int w = rect.width();
+ int h = rect.height();
+ switch (rotation) {
+ case 90: {
+ rect.top = rect.left;
+ rect.left = height - rect.bottom;
+ rect.right = rect.left + h;
+ rect.bottom = rect.top + w;
+ return;
+ }
+ case 180: {
+ rect.left = width - rect.right;
+ rect.top = height - rect.bottom;
+ rect.right = rect.left + w;
+ rect.bottom = rect.top + h;
+ return;
+ }
+ case 270: {
+ rect.left = rect.top;
+ rect.top = width - rect.right;
+ rect.right = rect.left + h;
+ rect.bottom = rect.top + w;
+ return;
+ }
+ default: throw new AssertionError();
+ }
+ }
+
+ private void drawInTiles(Canvas canvas,
+ BitmapRegionDecoder decoder, Rect rect, Rect dest, int sample) {
+ int tileSize = TILE_SIZE * sample;
+ Rect tileRect = new Rect();
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Config.ARGB_8888;
+ options.inSampleSize = sample;
+ canvas.translate(dest.left, dest.top);
+ canvas.scale((float) sample * dest.width() / rect.width(),
+ (float) sample * dest.height() / rect.height());
+ Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ for (int tx = rect.left, x = 0;
+ tx < rect.right; tx += tileSize, x += TILE_SIZE) {
+ for (int ty = rect.top, y = 0;
+ ty < rect.bottom; ty += tileSize, y += TILE_SIZE) {
+ tileRect.set(tx, ty, tx + tileSize, ty + tileSize);
+ if (tileRect.intersect(rect)) {
+ Bitmap bitmap;
+
+ // To prevent concurrent access in GLThread
+ synchronized (decoder) {
+ bitmap = decoder.decodeRegion(tileRect, options);
+ }
+ canvas.drawBitmap(bitmap, x, y, paint);
+ bitmap.recycle();
+ }
+ }
+ }
+ }
+
+ private void onBitmapRegionDecoderAvailable(
+ BitmapRegionDecoder regionDecoder) {
+
+ if (regionDecoder == null) {
+ Toast.makeText(this, "fail to load image", Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+ mRegionDecoder = regionDecoder;
+ mUseRegionDecoder = true;
+ mState = STATE_LOADED;
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ int width = regionDecoder.getWidth();
+ int height = regionDecoder.getHeight();
+ options.inSampleSize = BitmapUtils.computeSampleSize(width, height,
+ BitmapUtils.UNCONSTRAINED, BACKUP_PIXEL_COUNT);
+ mBitmap = regionDecoder.decodeRegion(
+ new Rect(0, 0, width, height), options);
+ mCropView.setDataModel(new TileImageViewAdapter(
+ mBitmap, regionDecoder), mMediaItem.getRotation());
+ if (mDoFaceDetection) {
+ mCropView.detectFaces(mBitmap);
+ } else {
+ mCropView.initializeHighlightRectangle();
+ }
+ }
+
+ private void onBitmapAvailable(Bitmap bitmap) {
+ if (bitmap == null) {
+ Toast.makeText(this, "fail to load image", Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+ mUseRegionDecoder = false;
+ mState = STATE_LOADED;
+
+ mBitmap = bitmap;
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ mCropView.setDataModel(new BitmapTileProvider(bitmap, 512),
+ mMediaItem.getRotation());
+ if (mDoFaceDetection) {
+ mCropView.detectFaces(bitmap);
+ } else {
+ mCropView.initializeHighlightRectangle();
+ }
+ }
+
+ private void setCropParameters() {
+ Bundle extras = getIntent().getExtras();
+ if (extras == null)
+ return;
+ int aspectX = extras.getInt(KEY_ASPECT_X, 0);
+ int aspectY = extras.getInt(KEY_ASPECT_Y, 0);
+ if (aspectX != 0 && aspectY != 0) {
+ mCropView.setAspectRatio((float) aspectX / aspectY);
+ }
+
+ float spotlightX = extras.getFloat(KEY_SPOTLIGHT_X, 0);
+ float spotlightY = extras.getFloat(KEY_SPOTLIGHT_Y, 0);
+ if (spotlightX != 0 && spotlightY != 0) {
+ mCropView.setSpotlightRatio(spotlightX, spotlightY);
+ }
+ }
+
+ private void initializeData() {
+ Bundle extras = getIntent().getExtras();
+
+ if (extras != null) {
+ if (extras.containsKey(KEY_NO_FACE_DETECTION)) {
+ mDoFaceDetection = !extras.getBoolean(KEY_NO_FACE_DETECTION);
+ }
+
+ mBitmapInIntent = extras.getParcelable(KEY_DATA);
+
+ if (mBitmapInIntent != null) {
+ mBitmapTileProvider =
+ new BitmapTileProvider(mBitmapInIntent, MAX_BACKUP_IMAGE_SIZE);
+ mCropView.setDataModel(mBitmapTileProvider, 0);
+ if (mDoFaceDetection) {
+ mCropView.detectFaces(mBitmapInIntent);
+ } else {
+ mCropView.initializeHighlightRectangle();
+ }
+ mState = STATE_LOADED;
+ return;
+ }
+ }
+
+ mProgressDialog = ProgressDialog.show(
+ this, null, getString(R.string.loading_image), true, false);
+
+ mMediaItem = getMediaItemFromIntentData();
+ if (mMediaItem == null) return;
+
+ boolean supportedByBitmapRegionDecoder =
+ (mMediaItem.getSupportedOperations() & MediaItem.SUPPORT_FULL_IMAGE) != 0;
+ if (supportedByBitmapRegionDecoder) {
+ mLoadTask = getThreadPool().submit(new LoadDataTask(mMediaItem),
+ new FutureListener<BitmapRegionDecoder>() {
+ public void onFutureDone(Future<BitmapRegionDecoder> future) {
+ mLoadTask = null;
+ BitmapRegionDecoder decoder = future.get();
+ if (future.isCancelled()) {
+ if (decoder != null) decoder.recycle();
+ return;
+ }
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(
+ MSG_LARGE_BITMAP, decoder));
+ }
+ });
+ } else {
+ mLoadBitmapTask = getThreadPool().submit(new LoadBitmapDataTask(mMediaItem),
+ new FutureListener<Bitmap>() {
+ public void onFutureDone(Future<Bitmap> future) {
+ mLoadBitmapTask = null;
+ Bitmap bitmap = future.get();
+ if (future.isCancelled()) {
+ if (bitmap != null) bitmap.recycle();
+ return;
+ }
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(
+ MSG_BITMAP, bitmap));
+ }
+ });
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mState == STATE_INIT) initializeData();
+ if (mState == STATE_SAVING) onSaveClicked();
+
+ // TODO: consider to do it in GLView system
+ GLRoot root = getGLRoot();
+ root.lockRenderThread();
+ try {
+ mCropView.resume();
+ } finally {
+ root.unlockRenderThread();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ Future<BitmapRegionDecoder> loadTask = mLoadTask;
+ if (loadTask != null && !loadTask.isDone()) {
+ // load in progress, try to cancel it
+ loadTask.cancel();
+ loadTask.waitDone();
+ mProgressDialog.dismiss();
+ }
+
+ Future<Bitmap> loadBitmapTask = mLoadBitmapTask;
+ if (loadBitmapTask != null && !loadBitmapTask.isDone()) {
+ // load in progress, try to cancel it
+ loadBitmapTask.cancel();
+ loadBitmapTask.waitDone();
+ mProgressDialog.dismiss();
+ }
+
+ Future<Intent> saveTask = mSaveTask;
+ if (saveTask != null && !saveTask.isDone()) {
+ // save in progress, try to cancel it
+ saveTask.cancel();
+ saveTask.waitDone();
+ mProgressDialog.dismiss();
+ }
+ GLRoot root = getGLRoot();
+ root.lockRenderThread();
+ try {
+ mCropView.pause();
+ } finally {
+ root.unlockRenderThread();
+ }
+ }
+
+ private MediaItem getMediaItemFromIntentData() {
+ Uri uri = getIntent().getData();
+ DataManager manager = getDataManager();
+ if (uri == null) {
+ Log.w(TAG, "no data given");
+ return null;
+ }
+ Path path = manager.findPathByUri(uri);
+ if (path == null) {
+ Log.w(TAG, "cannot get path for: " + uri);
+ return null;
+ }
+ return (MediaItem) manager.getMediaObject(path);
+ }
+
+ private class LoadDataTask implements Job<BitmapRegionDecoder> {
+ MediaItem mItem;
+
+ public LoadDataTask(MediaItem item) {
+ mItem = item;
+ }
+
+ public BitmapRegionDecoder run(JobContext jc) {
+ return mItem == null ? null : mItem.requestLargeImage().run(jc);
+ }
+ }
+
+ private class LoadBitmapDataTask implements Job<Bitmap> {
+ MediaItem mItem;
+
+ public LoadBitmapDataTask(MediaItem item) {
+ mItem = item;
+ }
+ public Bitmap run(JobContext jc) {
+ return mItem == null
+ ? null
+ : mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc);
+ }
+ }
+}