X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=src%2Fcom%2Fandroid%2Fgallery3d%2Ffiltershow%2Fcrop%2FCropActivity.java;h=9c013085475a2c41c0ba5c73514a6596853b62ec;hb=8dd1afedc5899c7539978b706fb165fba8d1e2f2;hp=878b82fc62b12cbacf9125da24ebbe81e2e33b96;hpb=c3178ca4daf7ed570f3432f3433959c208ecc105;p=android-x86%2Fpackages-apps-Gallery2.git diff --git a/src/com/android/gallery3d/filtershow/crop/CropActivity.java b/src/com/android/gallery3d/filtershow/crop/CropActivity.java index 878b82fc6..9c0130854 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropActivity.java +++ b/src/com/android/gallery3d/filtershow/crop/CropActivity.java @@ -21,10 +21,14 @@ import android.app.Activity; import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.net.Uri; @@ -33,12 +37,21 @@ import android.os.Bundle; import android.provider.MediaStore; import android.util.DisplayMetrics; import android.util.Log; +import android.view.InputDevice; +import android.view.InputDevice.MotionRange; +import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.Toast; import com.android.gallery3d.R; +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.filtershow.cache.ImageLoader; +import com.android.gallery3d.filtershow.tools.SaveImage; +import com.google.android.pano.util.TouchNavGestureDetector; +import com.google.android.pano.util.TouchNavGestureDetector.OnGestureListener; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -50,24 +63,34 @@ import java.io.OutputStream; /** * Activity for cropping an image. */ -public class CropActivity extends Activity { +public class CropActivity extends Activity implements OnGestureListener { private static final String LOGTAG = "CropActivity"; public static final String CROP_ACTION = "com.android.camera.action.CROP"; + private static final int MIN_SCROLL_LENGTH = 5; private CropExtras mCropExtras = null; private LoadBitmapTask mLoadBitmapTask = null; + private int mOutputX = 0; + private int mOutputY = 0; private Bitmap mOriginalBitmap = null; private RectF mOriginalBounds = null; + private int mOriginalRotation = 0; private Uri mSourceUri = null; private CropView mCropView = null; private View mSaveButton = null; private boolean finalIOGuard = false; - private Intent mResultIntent = null; private static final int SELECT_PICTURE = 1; // request code for picker private static final int DEFAULT_COMPRESS_QUALITY = 90; - public static final int MAX_BMAP_IN_INTENT = 990000; + /** + * The maximum bitmap size we allow to be returned through the intent. + * Intents have a maximum of 1MB in total size. However, the Bitmap seems to + * have some overhead to hit so that we go way below the limit here to make + * sure the intent stays below 1MB.We should consider just returning a byte + * array instead of a Bitmap instance to avoid overhead. + */ + public static final int MAX_BMAP_IN_INTENT = 750000; // Flags private static final int DO_SET_WALLPAPER = 1; @@ -76,12 +99,27 @@ public class CropActivity extends Activity { private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT; + private TouchNavGestureDetector mGestureDetector; + private float mTouchPadToViewRatioX; + private float mTouchPadToViewRatioY; + private float mResolutionX; + private float mResolutionY; + + /** + * Used for click filtering. Any scroll less than MIN_SCROLL_LENGTH will not + * be registered. This will make clicking motions not trigger a scroll. Once + * you get past MIN_SCROLL_LENGTH, you can go below it again and register + * scrolls. This is to allow for starting a scroll and then moving back to + * the origin of the scroll. + */ + private boolean mDidStartDrag; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mGestureDetector = new TouchNavGestureDetector(this, this); Intent intent = getIntent(); - mResultIntent = new Intent(); - setResult(RESULT_CANCELED, mResultIntent); + setResult(RESULT_CANCELED, new Intent()); mCropExtras = getExtrasFromIntent(intent); if (mCropExtras != null && mCropExtras.getShowWhenLocked()) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); @@ -91,16 +129,18 @@ public class CropActivity extends Activity { mCropView = (CropView) findViewById(R.id.cropView); ActionBar actionBar = getActionBar(); - actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); - actionBar.setCustomView(R.layout.filtershow_actionbar); - - View mSaveButton = actionBar.getCustomView(); - mSaveButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - startFinishOutput(); - } - }); + if (actionBar != null) { + actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); + actionBar.setCustomView(R.layout.filtershow_actionbar); + + mSaveButton = actionBar.getCustomView(); + mSaveButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + startFinishOutput(); + } + }); + } if (intent.getData() != null) { mSourceUri = intent.getData(); @@ -124,6 +164,12 @@ public class CropActivity extends Activity { super.onDestroy(); } + @Override + public void onConfigurationChanged (Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mCropView.configChanged(); + } + /** * Opens a selector in Gallery to chose an image for use when none was given * in the CROP intent. @@ -160,24 +206,48 @@ public class CropActivity extends Activity { * Method that loads a bitmap in an async task. */ private void startLoadBitmap(Uri uri) { - enableSave(false); - final View loading = findViewById(R.id.loading); - loading.setVisibility(View.VISIBLE); - mLoadBitmapTask = new LoadBitmapTask(); - mLoadBitmapTask.execute(uri); + if (uri != null) { + enableSave(false); + final View loading = findViewById(R.id.loading); + loading.setVisibility(View.VISIBLE); + mLoadBitmapTask = new LoadBitmapTask(); + mLoadBitmapTask.execute(uri); + } else { + cannotLoadImage(); + done(); + } } /** * Method called on UI thread with loaded bitmap. */ - private void doneLoadBitmap(Bitmap bitmap, RectF bounds) { + private void doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation) { final View loading = findViewById(R.id.loading); loading.setVisibility(View.GONE); mOriginalBitmap = bitmap; mOriginalBounds = bounds; + mOriginalRotation = orientation; if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) { RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()); - mCropView.initialize(bitmap, imgBounds, imgBounds, 0); + mCropView.initialize(bitmap, imgBounds, imgBounds, orientation); + if (mCropExtras != null) { + int aspectX = mCropExtras.getAspectX(); + int aspectY = mCropExtras.getAspectY(); + mOutputX = mCropExtras.getOutputX(); + mOutputY = mCropExtras.getOutputY(); + if (mOutputX > 0 && mOutputY > 0) { + mCropView.applyAspect(mOutputX, mOutputY); + + } + float spotX = mCropExtras.getSpotlightX(); + float spotY = mCropExtras.getSpotlightY(); + if (spotX > 0 && spotY > 0) { + mCropView.setWallpaperSpotlight(spotX, spotY); + } + if (aspectX > 0 && aspectY > 0) { + mCropView.applyAspect(aspectX, aspectY); + } + } enableSave(true); } else { Log.w(LOGTAG, "could not load image for cropping"); @@ -206,23 +276,27 @@ public class CropActivity extends Activity { int mBitmapSize; Context mContext; Rect mOriginalBounds; + int mOrientation; public LoadBitmapTask() { mBitmapSize = getScreenImageSize(); mContext = getApplicationContext(); mOriginalBounds = new Rect(); + mOrientation = 0; } @Override protected Bitmap doInBackground(Uri... params) { - Bitmap bmap = CropLoader.getConstrainedBitmap(params[0], mContext, mBitmapSize, - mOriginalBounds); + Uri uri = params[0]; + Bitmap bmap = ImageLoader.loadConstrainedBitmap(uri, mContext, mBitmapSize, + mOriginalBounds, false); + mOrientation = ImageLoader.getMetadataRotation(mContext, uri); return bmap; } @Override protected void onPostExecute(Bitmap result) { - doneLoadBitmap(result, new RectF(mOriginalBounds)); + doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation); } } @@ -238,7 +312,9 @@ public class CropActivity extends Activity { if (mOriginalBitmap != null && mCropExtras != null) { if (mCropExtras.getExtraOutput() != null) { destinationUri = mCropExtras.getExtraOutput(); - flags |= DO_EXTRA_OUTPUT; + if (destinationUri != null) { + flags |= DO_EXTRA_OUTPUT; + } } if (mCropExtras.getSetAsWallpaper()) { flags |= DO_SET_WALLPAPER; @@ -248,26 +324,27 @@ public class CropActivity extends Activity { } } if (flags == 0) { - destinationUri = CropLoader.makeAndInsertUri(this, mSourceUri); + destinationUri = SaveImage.makeAndInsertUri(this, mSourceUri); if (destinationUri != null) { flags |= DO_EXTRA_OUTPUT; } } - if ((flags & FLAG_CHECK) != 0) { + if ((flags & FLAG_CHECK) != 0 && mOriginalBitmap != null) { RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight()); RectF crop = getBitmapCrop(photo); startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop, photo, mOriginalBounds, - (mCropExtras == null) ? null : mCropExtras.getOutputFormat()); + (mCropExtras == null) ? null : mCropExtras.getOutputFormat(), mOriginalRotation); return; } - setResult(RESULT_CANCELED, mResultIntent); + setResult(RESULT_CANCELED, new Intent()); done(); return; } private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri, - RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format) { + RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format, + int rotation) { if (cropBounds == null || photoBounds == null || currentBitmap == null || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0 || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0 @@ -284,7 +361,7 @@ public class CropActivity extends Activity { final View loading = findViewById(R.id.loading); loading.setVisibility(View.VISIBLE); BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds, - photoBounds, currentBitmapBounds); + photoBounds, currentBitmapBounds, rotation, mOutputX, mOutputY); ioTask.execute(currentBitmap); } @@ -312,9 +389,25 @@ public class CropActivity extends Activity { RectF mPhoto = null; RectF mOrig = null; Intent mResultIntent = null; + int mRotation = 0; + + // Helper to setup input stream + private void regenerateInputStream() { + if (mInUri == null) { + Log.w(LOGTAG, "cannot read original file, no input URI given"); + } else { + Utils.closeSilently(mInStream); + try { + mInStream = getContentResolver().openInputStream(mInUri); + } catch (FileNotFoundException e) { + Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e); + } + } + } public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags, - RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds) { + RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation, + int outputX, int outputY) { mOutputFormat = outputFormat; mOutStream = null; mOutUri = destUri; @@ -325,6 +418,11 @@ public class CropActivity extends Activity { mOrig = originalBitmapBounds; mWPManager = WallpaperManager.getInstance(getApplicationContext()); mResultIntent = new Intent(); + mRotation = (rotation < 0) ? -rotation : rotation; + mRotation %= 360; + mRotation = 90 * (int) (mRotation / 90); // now mRotation is a multiple of 90 + mOutputX = outputX; + mOutputY = outputY; if ((flags & DO_EXTRA_OUTPUT) != 0) { if (mOutUri == null) { @@ -339,15 +437,7 @@ public class CropActivity extends Activity { } if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) { - if (mInUri == null) { - Log.w(LOGTAG, "cannot read original file, no input URI given"); - } else { - try { - mInStream = getContentResolver().openInputStream(mInUri); - } catch (FileNotFoundException e) { - Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e); - } - } + regenerateInputStream(); } } @@ -359,6 +449,9 @@ public class CropActivity extends Activity { // Set extra for crop bounds if (mCrop != null && mPhoto != null && mOrig != null) { RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig); + Matrix m = new Matrix(); + m.setRotate(mRotation); + m.mapRect(trueCrop); if (trueCrop != null) { Rect rounded = new Rect(); trueCrop.roundOut(rounded); @@ -377,23 +470,21 @@ public class CropActivity extends Activity { Log.w(LOGTAG, "could not downsample bitmap to return in data"); failure = true; } else { + if (mRotation > 0) { + Matrix m = new Matrix(); + m.setRotate(mRotation); + Bitmap tmp = Bitmap.createBitmap(ret, 0, 0, ret.getWidth(), + ret.getHeight(), m, true); + if (tmp != null) { + ret = tmp; + } + } mResultIntent.putExtra(CropExtras.KEY_DATA, ret); } } // Do the large cropped bitmap and/or set the wallpaper if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) { - BitmapRegionDecoder decoder = null; - try { - decoder = BitmapRegionDecoder.newInstance(mInStream, true); - } catch (IOException e) { - Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); - } - if (decoder == null) { - failure = true; - return false; - } - // Find crop bounds (scaled to original image size) RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig); if (trueCrop == null) { @@ -409,17 +500,69 @@ public class CropActivity extends Activity { failure = true; return false; } - // Do region decoding to get crop bitmap - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inMutable = true; - Bitmap crop = decoder.decodeRegion(roundedTrueCrop, options); - decoder.recycle(); + + // Attempt to open a region decoder + BitmapRegionDecoder decoder = null; + try { + decoder = BitmapRegionDecoder.newInstance(mInStream, true); + } catch (IOException e) { + Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); + } + + Bitmap crop = null; + if (decoder != null) { + // Do region decoding to get crop bitmap + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inMutable = true; + crop = decoder.decodeRegion(roundedTrueCrop, options); + decoder.recycle(); + } if (crop == null) { - Log.w(LOGTAG, "cannot region decode file: " + mInUri.toString()); + // BitmapRegionDecoder has failed, try to crop in-memory + regenerateInputStream(); + Bitmap fullSize = null; + if (mInStream != null) { + fullSize = BitmapFactory.decodeStream(mInStream); + } + if (fullSize != null) { + crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, + roundedTrueCrop.top, roundedTrueCrop.width(), + roundedTrueCrop.height()); + } + } + + if (crop == null) { + Log.w(LOGTAG, "cannot decode file: " + mInUri.toString()); failure = true; return false; } + if (mOutputX > 0 && mOutputY > 0) { + Matrix m = new Matrix(); + RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight()); + if (mRotation > 0) { + m.setRotate(mRotation); + m.mapRect(cropRect); + } + RectF returnRect = new RectF(0, 0, mOutputX, mOutputY); + m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); + m.preRotate(mRotation); + Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), + (int) returnRect.height(), Bitmap.Config.ARGB_8888); + if (tmp != null) { + Canvas c = new Canvas(tmp); + c.drawBitmap(crop, m, new Paint()); + crop = tmp; + } + } else if (mRotation > 0) { + Matrix m = new Matrix(); + m.setRotate(mRotation); + Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(), + crop.getHeight(), m, true); + if (tmp != null) { + crop = tmp; + } + } // Get output compression format CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); @@ -430,6 +573,8 @@ public class CropActivity extends Activity { || !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) { Log.w(LOGTAG, "failed to compress bitmap to file: " + mOutUri.toString()); failure = true; + } else { + mResultIntent.setData(mOutUri); } } else { // Compress to byte array @@ -446,6 +591,7 @@ public class CropActivity extends Activity { } else { try { mOutStream.write(tmpOut.toByteArray()); + mResultIntent.setData(mOutUri); } catch (IOException e) { Log.w(LOGTAG, "failed to compress bitmap to file: " @@ -481,6 +627,8 @@ public class CropActivity extends Activity { @Override protected void onPostExecute(Boolean result) { + Utils.closeSilently(mOutStream); + Utils.closeSilently(mInStream); doneBitmapIO(result.booleanValue(), mResultIntent); } @@ -571,4 +719,106 @@ public class CropActivity extends Activity { RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds); return scaledCrop; } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + boolean handled = super.onGenericMotionEvent(event); + if (!handled) { + handled = mGestureDetector.onGenericMotionEvent(event); + if (!handled + && (event.getSource() & TouchNavGestureDetector.SOURCE_TOUCH_NAVIGATION) + == TouchNavGestureDetector.SOURCE_TOUCH_NAVIGATION) { + int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + sendScaledMotionEvent(event); + handled = true; + break; + } + } + } + return handled; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + boolean handled = super.onKeyDown(keyCode, event); + if (!handled) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) { + startFinishOutput(); + handled = true; + } + } + return handled; + } + + /** + * Returns whether a scroll happened. + */ + @Override + public boolean onScroll(MotionEvent downEvent, MotionEvent currentEvent, float deltaX, + float deltaY) { + // If <5mm from the start, consider it a click. Used for smoother + // clicks. + if (!mDidStartDrag) { + float distanceX = Math.abs((currentEvent.getX() - downEvent.getX()) / mResolutionX); + float distanceY = Math.abs((currentEvent.getY() - downEvent.getY()) / mResolutionY); + if (distanceX < MIN_SCROLL_LENGTH && distanceY < MIN_SCROLL_LENGTH) { + // Not moved enough to scroll. + return false; + } + } + // Moved enough to scroll. + mDidStartDrag = true; + sendScaledMotionEvent(currentEvent); + return true; + } + + @Override + public boolean onFling(MotionEvent downEvent, MotionEvent currentEvent, float velX, + float velY) { + sendScaledMotionEvent(currentEvent); + return true; + } + + @Override + public boolean onDown(MotionEvent downEvent) { + mDidStartDrag = false; + InputDevice device = downEvent.getDevice(); + MotionRange motionRangeX = device.getMotionRange(MotionEvent.AXIS_X); + MotionRange motionRangeY = device.getMotionRange(MotionEvent.AXIS_Y); + + if (motionRangeX == null || motionRangeY == null) { + return false; + } + + mResolutionX = motionRangeX.getResolution(); + mResolutionY = motionRangeY.getResolution(); + + if (mResolutionX == 0) { + mResolutionX = TouchNavGestureDetector.DEFAULT_TOUCH_RESOLUTION; + } + if (mResolutionY == 0) { + mResolutionY = TouchNavGestureDetector.DEFAULT_TOUCH_RESOLUTION; + } + + // This is the conversion between a drag on the device vs. a drag on + // the screen. + mTouchPadToViewRatioX = mCropView.getWidth() / motionRangeX.getRange(); + mTouchPadToViewRatioY = mCropView.getHeight() / motionRangeY.getRange(); + + sendScaledMotionEvent(downEvent); + return true; + } + + private void sendScaledMotionEvent(MotionEvent event) { + final int scaledX = (int) (mTouchPadToViewRatioX * event.getX()); + final int scaledY = (int) (mTouchPadToViewRatioY * event.getY()); + final MotionEvent ev = MotionEvent.obtain(event.getEventTime(), event.getEventTime(), + event.getActionMasked(), scaledX, scaledY, 0); + mCropView.onTouchEvent(ev); + ev.recycle(); + } + }