diff options
Diffstat (limited to 'src/com/android/gallery3d/app/CropImage.java')
-rw-r--r-- | src/com/android/gallery3d/app/CropImage.java | 850 |
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); + } + } +} |