diff options
Diffstat (limited to 'src/com/android/gallery3d')
15 files changed, 847 insertions, 361 deletions
diff --git a/src/com/android/gallery3d/app/GalleryActionBar.java b/src/com/android/gallery3d/app/GalleryActionBar.java index 0fb5e51b4..588f5842a 100644 --- a/src/com/android/gallery3d/app/GalleryActionBar.java +++ b/src/com/android/gallery3d/app/GalleryActionBar.java @@ -422,7 +422,8 @@ public class GalleryActionBar implements OnNavigationListener { return mActionBarMenu; } - public void setShareIntents(Intent sharePanoramaIntent, Intent shareIntent) { + public void setShareIntents(Intent sharePanoramaIntent, Intent shareIntent, + ShareActionProvider.OnShareTargetSelectedListener onShareListener) { mSharePanoramaIntent = sharePanoramaIntent; if (mSharePanoramaActionProvider != null) { mSharePanoramaActionProvider.setShareIntent(sharePanoramaIntent); @@ -430,6 +431,8 @@ public class GalleryActionBar implements OnNavigationListener { mShareIntent = shareIntent; if (mShareActionProvider != null) { mShareActionProvider.setShareIntent(shareIntent); + mShareActionProvider.setOnShareTargetSelectedListener( + onShareListener); } } } diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index 9b6f2b961..613ac17eb 100644 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -36,6 +36,7 @@ import android.os.SystemClock; import android.view.Menu; import android.view.MenuItem; import android.widget.RelativeLayout; +import android.widget.ShareActionProvider; import android.widget.Toast; import com.android.camera.CameraActivity; @@ -59,6 +60,7 @@ import com.android.gallery3d.data.SnailAlbum; import com.android.gallery3d.data.SnailItem; import com.android.gallery3d.data.SnailSource; import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.crop.CropActivity; import com.android.gallery3d.picasasource.PicasaSource; import com.android.gallery3d.ui.DetailsHelper; import com.android.gallery3d.ui.DetailsHelper.CloseListener; @@ -72,7 +74,7 @@ import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.UsageStatistics; public abstract class PhotoPage extends ActivityState implements - PhotoView.Listener, AppBridge.Server, + PhotoView.Listener, AppBridge.Server, ShareActionProvider.OnShareTargetSelectedListener, PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener { private static final String TAG = "PhotoPage"; @@ -391,7 +393,7 @@ public abstract class PhotoPage extends ActivityState implements } Intent shareIntent = createShareIntent(mCurrentPhoto); - mActionBar.setShareIntents(panoramaIntent, shareIntent); + mActionBar.setShareIntents(panoramaIntent, shareIntent, PhotoPage.this); setNfcBeamPushUri(contentUri); } break; @@ -1081,8 +1083,8 @@ public abstract class PhotoPage extends ActivityState implements } case R.id.action_crop: { Activity activity = mActivity; - Intent intent = new Intent(FilterShowActivity.CROP_ACTION); - intent.setClass(activity, FilterShowActivity.class); + Intent intent = new Intent(CropActivity.CROP_ACTION); + intent.setClass(activity, CropActivity.class); intent.setDataAndType(manager.getContentUri(path), current.getMimeType()) .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current) @@ -1540,4 +1542,28 @@ public abstract class PhotoPage extends ActivityState implements public void onUndoBarVisibilityChanged(boolean visible) { refreshBottomControlsWhenReady(); } + + @Override + public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) { + final long timestampMillis = mCurrentPhoto.getDateInMs(); + final String mediaType = getMediaTypeString(mCurrentPhoto); + UsageStatistics.onEvent(UsageStatistics.COMPONENT_GALLERY, + UsageStatistics.ACTION_SHARE, + mediaType, + timestampMillis > 0 + ? System.currentTimeMillis() - timestampMillis + : -1); + return false; + } + + private static String getMediaTypeString(MediaItem item) { + if (item.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO) { + return "Video"; + } else if (item.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE) { + return "Photo"; + } else { + return "Unknown:" + item.getMediaType(); + } + } + } diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index 4133d8915..fb8984989 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -119,7 +119,6 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL private static final int SELECT_PICTURE = 1; private static final String LOGTAG = "FilterShowActivity"; protected static final boolean ANIMATE_PANELS = true; - private static int mImageBorderSize = 4; // in percent private boolean mShowingTinyPlanet = false; private boolean mShowingImageStatePanel = false; @@ -398,27 +397,10 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL borders.add(new FilterImageBorderRepresentation(0)); // Google-build borders - FiltersManager.getManager().addBorders(borders); - - // Regular borders - borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_4x5)); - borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_brush)); - borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_grunge)); - borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_sumi_e)); - borders.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_tape)); - borders.add(new FilterColorBorderRepresentation(Color.BLACK, mImageBorderSize, 0)); - borders.add(new FilterColorBorderRepresentation(Color.BLACK, mImageBorderSize, - mImageBorderSize)); - borders.add(new FilterColorBorderRepresentation(Color.WHITE, mImageBorderSize, 0)); - borders.add(new FilterColorBorderRepresentation(Color.WHITE, mImageBorderSize, - mImageBorderSize)); - int creamColor = Color.argb(255, 237, 237, 227); - borders.add(new FilterColorBorderRepresentation(creamColor, mImageBorderSize, 0)); - borders.add(new FilterColorBorderRepresentation(creamColor, mImageBorderSize, - mImageBorderSize)); + FiltersManager.getManager().addBorders(this, borders); + for (int i = 0; i < borders.size(); i++) { FilterRepresentation filter = borders.elementAt(i); - filter.setName(getString(R.string.borders)); if (i == 0) { filter.setName(getString(R.string.none)); } @@ -738,41 +720,6 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } private void fillFx(LinearLayout listFilters, int buttonId) { - // TODO: use listview - // TODO: load the filters straight from the filesystem - - FilterFxRepresentation[] fxArray = new FilterFxRepresentation[18]; - int p = 0; - - int[] drawid = { - R.drawable.filtershow_fx_0005_punch, - R.drawable.filtershow_fx_0000_vintage, - R.drawable.filtershow_fx_0004_bw_contrast, - R.drawable.filtershow_fx_0002_bleach, - R.drawable.filtershow_fx_0001_instant, - R.drawable.filtershow_fx_0007_washout, - R.drawable.filtershow_fx_0003_blue_crush, - R.drawable.filtershow_fx_0008_washout_color, - R.drawable.filtershow_fx_0006_x_process - }; - - int[] fxNameid = { - R.string.ffx_punch, - R.string.ffx_vintage, - R.string.ffx_bw_contrast, - R.string.ffx_bleach, - R.string.ffx_instant, - R.string.ffx_washout, - R.string.ffx_blue_crush, - R.string.ffx_washout_color, - R.string.ffx_x_process - }; - - for (int i = 0; i < drawid.length; i++) { - FilterFxRepresentation fx = new FilterFxRepresentation(getString(fxNameid[i]), drawid[i], fxNameid[i]); - fxArray[p++] = fx; - } - ImageButton button = (ImageButton) findViewById(buttonId); FilterFxRepresentation nullFx = new FilterFxRepresentation(getString(R.string.none), 0, R.string.none); @@ -780,14 +727,11 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL mNullFxFilter.setSelected(true); Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>(); - FiltersManager.getManager().addLooks(filtersRepresentations); + FiltersManager.getManager().addLooks(this, filtersRepresentations); for (FilterRepresentation representation : filtersRepresentations) { setupFilterRepresentationButton(representation, listFilters, button); } - for (int i = 0; i < p; i++) { - setupFilterRepresentationButton(fxArray[i], listFilters, button); - } } public void setDefaultPreset() { diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java index e9a913610..2cd70e3aa 100644 --- a/src/com/android/gallery3d/filtershow/PanelController.java +++ b/src/com/android/gallery3d/filtershow/PanelController.java @@ -598,6 +598,7 @@ public class PanelController implements OnClickListener { mCurrentEditor = null; FilterIconButton component = (FilterIconButton) view; FilterRepresentation representation = component.getFilterRepresentation(); + if (representation != null) { mUtilityPanel.setEffectName(representation.getName()); mUtilityPanel.setShowParameter(representation.showParameterValue()); diff --git a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java index e63323b92..8202d71bb 100644 --- a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java +++ b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java @@ -151,7 +151,6 @@ public class FilteringPipeline implements Handler.Callback { Log.e(LOGTAG, "setOriginal called after pipeline initialization!"); return; } - Log.v(LOGTAG,"setOriginal, size " + bitmap.getWidth() + " x " + bitmap.getHeight()); mAccessoryPipeline.setOriginal(bitmap); mPreviewPipeline.setOriginal(bitmap); mHighresPreviewPipeline.setOriginal(bitmap); diff --git a/src/com/android/gallery3d/filtershow/crop/CropActivity.java b/src/com/android/gallery3d/filtershow/crop/CropActivity.java index 26659a600..3429e0baf 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropActivity.java +++ b/src/com/android/gallery3d/filtershow/crop/CropActivity.java @@ -21,11 +21,11 @@ import android.app.Activity; import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; -import android.graphics.Point; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.net.Uri; @@ -34,8 +34,6 @@ import android.os.Bundle; import android.provider.MediaStore; import android.util.DisplayMetrics; import android.util.Log; -import android.util.TypedValue; -import android.view.Display; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; @@ -43,8 +41,11 @@ import android.widget.Toast; import com.android.gallery3d.R; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; /** @@ -52,25 +53,35 @@ import java.io.OutputStream; */ public class CropActivity extends Activity { private static final String LOGTAG = "CropActivity"; + public static final String CROP_ACTION = "com.android.camera.action.CROP"; private CropExtras mCropExtras = null; private LoadBitmapTask mLoadBitmapTask = null; - private SaveBitmapTask mSaveBitmapTask = null; - private SetWallpaperTask mSetWallpaperTask = null; + private Bitmap mOriginalBitmap = null; + private RectF mOriginalBounds = null; + private int mOriginalRotation = 0; + private Uri mSourceUri = null; private CropView mCropView = null; - private int mActiveBackgroundIO = 0; - private Intent mResultIntent = null; + private View mSaveButton = null; + private boolean finalIOGuard = false; + private static final int SELECT_PICTURE = 1; // request code for picker - private static final int DEFAULT_DENSITY = 133; + private static final int DEFAULT_COMPRESS_QUALITY = 90; public static final int MAX_BMAP_IN_INTENT = 990000; + // Flags + private static final int DO_SET_WALLPAPER = 1; + private static final int DO_RETURN_DATA = 1 << 1; + private static final int DO_EXTRA_OUTPUT = 1 << 2; + + private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); 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); @@ -79,22 +90,30 @@ public class CropActivity extends Activity { setContentView(R.layout.crop_activity); mCropView = (CropView) findViewById(R.id.cropView); - if (intent.getData() != null) { - startLoadBitmap(intent.getData()); - } else { - pickImage(); - } ActionBar actionBar = getActionBar(); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); actionBar.setCustomView(R.layout.filtershow_actionbar); - View saveButton = actionBar.getCustomView(); - saveButton.setOnClickListener(new OnClickListener() { + View mSaveButton = actionBar.getCustomView(); + mSaveButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { startFinishOutput(); } }); + + if (intent.getData() != null) { + mSourceUri = intent.getData(); + startLoadBitmap(mSourceUri); + } else { + pickImage(); + } + } + + private void enableSave(boolean enable) { + if (mSaveButton != null) { + mSaveButton.setEnabled(enable); + } } @Override @@ -109,7 +128,7 @@ public class CropActivity extends Activity { * Opens a selector in Gallery to chose an image for use when none was given * in the CROP intent. */ - public void pickImage() { + private void pickImage() { Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); @@ -123,74 +142,65 @@ public class CropActivity extends Activity { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) { - Uri selectedImageUri = data.getData(); - startLoadBitmap(selectedImageUri); - } - } - - /** - * Gets the crop extras from the intent, or null if none exist. - */ - public static CropExtras getExtrasFromIntent(Intent intent) { - Bundle extras = intent.getExtras(); - if (extras != null) { - return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0), - extras.getInt(CropExtras.KEY_OUTPUT_Y, 0), - extras.getBoolean(CropExtras.KEY_SCALE, true) && - extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false), - extras.getInt(CropExtras.KEY_ASPECT_X, 0), - extras.getInt(CropExtras.KEY_ASPECT_Y, 0), - extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false), - extras.getBoolean(CropExtras.KEY_RETURN_DATA, false), - (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT), - extras.getString(CropExtras.KEY_OUTPUT_FORMAT), - extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false), - extras.getFloat(CropExtras.KEY_SPOTLIGHT_X), - extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y)); + mSourceUri = data.getData(); + startLoadBitmap(mSourceUri); } - return null; } /** * Gets screen size metric. */ private int getScreenImageSize() { - DisplayMetrics metrics = new DisplayMetrics(); - Display display = getWindowManager().getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - display.getMetrics(metrics); - int msize = Math.min(size.x, size.y); - // TODO: WTF - return (DEFAULT_DENSITY * msize) / metrics.densityDpi + 512; + DisplayMetrics outMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(outMetrics); + return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels); } /** * Method that loads a bitmap in an async task. */ private void startLoadBitmap(Uri uri) { - mActiveBackgroundIO++; - 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) { - mActiveBackgroundIO--; + private void doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation) { final View loading = findViewById(R.id.loading); loading.setVisibility(View.GONE); mOriginalBitmap = bitmap; - // TODO: move these to dimens folder - if (bitmap != null) { - mCropView.setup(bitmap, (int) getPixelsFromDip(55), (int) getPixelsFromDip(25)); + 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, orientation); + if (mCropExtras != null) { + int aspectX = mCropExtras.getAspectX(); + int aspectY = mCropExtras.getAspectY(); + int outputX = mCropExtras.getOutputX(); + int outputY = mCropExtras.getOutputY(); + if (outputX > 0 && outputY > 0) { + mCropView.applyAspect(outputX, outputY); + } + if (aspectX > 0 && aspectY > 0) { + mCropView.applyAspect(aspectX, aspectY); + } + } + enableSave(true); } else { Log.w(LOGTAG, "could not load image for cropping"); cannotLoadImage(); - setResult(RESULT_CANCELED, mResultIntent); + setResult(RESULT_CANCELED, new Intent()); done(); } } @@ -214,172 +224,328 @@ public class CropActivity extends Activity { int mBitmapSize; Context mContext; Rect mOriginalBounds; + int mOrientation; public LoadBitmapTask() { mBitmapSize = getScreenImageSize(); - Log.v(LOGTAG, "bitmap size: " + mBitmapSize); mContext = getApplicationContext(); mOriginalBounds = new Rect(); + mOrientation = 0; } @Override protected Bitmap doInBackground(Uri... params) { - Bitmap bmap = CropLoader.getConstrainedBitmap(params[0], mContext, mBitmapSize, + Uri uri = params[0]; + Bitmap bmap = CropLoader.getConstrainedBitmap(uri, mContext, mBitmapSize, mOriginalBounds); + mOrientation = CropLoader.getMetadataRotation(uri, mContext); return bmap; } @Override protected void onPostExecute(Bitmap result) { - doneLoadBitmap(result); - // super.onPostExecute(result); + doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation); } } - private void startSaveBitmap(Bitmap bmap, Uri uri, String format) { - if (bmap == null || uri == null) { - throw new IllegalArgumentException("bad argument to startSaveBitmap"); + private void startFinishOutput() { + if (finalIOGuard) { + return; + } else { + finalIOGuard = true; + } + enableSave(false); + Uri destinationUri = null; + int flags = 0; + if (mOriginalBitmap != null && mCropExtras != null) { + if (mCropExtras.getExtraOutput() != null) { + destinationUri = mCropExtras.getExtraOutput(); + flags |= DO_EXTRA_OUTPUT; + } + if (mCropExtras.getSetAsWallpaper()) { + flags |= DO_SET_WALLPAPER; + } + if (mCropExtras.getReturnData()) { + flags |= DO_RETURN_DATA; + } } - mActiveBackgroundIO++; + if (flags == 0) { + destinationUri = CropLoader.makeAndInsertUri(this, mSourceUri); + if (destinationUri != null) { + flags |= DO_EXTRA_OUTPUT; + } + } + if ((flags & FLAG_CHECK) != 0) { + 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(), mOriginalRotation); + return; + } + 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, + int rotation) { + if (cropBounds == null || photoBounds == null || currentBitmap == null + || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0 + || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0 + || photoBounds.height() == 0) { + return; // fail fast + } + if ((flags & FLAG_CHECK) == 0) { + return; // no output options + } + if ((flags & DO_SET_WALLPAPER) != 0) { + Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show(); + } + final View loading = findViewById(R.id.loading); loading.setVisibility(View.VISIBLE); - mSaveBitmapTask = new SaveBitmapTask(uri, format); - mSaveBitmapTask.execute(bmap); + BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds, + photoBounds, currentBitmapBounds, rotation); + ioTask.execute(currentBitmap); } - private void doneSaveBitmap(Uri uri) { - mActiveBackgroundIO--; + private void doneBitmapIO(boolean success, Intent intent) { final View loading = findViewById(R.id.loading); loading.setVisibility(View.GONE); - if (uri == null) { - Log.w(LOGTAG, "failed to save bitmap"); - setResult(RESULT_CANCELED, mResultIntent); - done(); - return; + if (success) { + setResult(RESULT_OK, intent); + } else { + setResult(RESULT_CANCELED, intent); } done(); } - private class SaveBitmapTask extends AsyncTask<Bitmap, Void, Boolean> { + private class BitmapIOTask extends AsyncTask<Bitmap, Void, Boolean> { + private final WallpaperManager mWPManager; + InputStream mInStream = null; OutputStream mOutStream = null; String mOutputFormat = null; Uri mOutUri = null; - - public SaveBitmapTask(Uri uri, String outputFormat) { + Uri mInUri = null; + int mFlags = 0; + RectF mCrop = null; + RectF mPhoto = null; + RectF mOrig = null; + Intent mResultIntent = null; + int mRotation = 0; + + public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags, + RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation) { mOutputFormat = outputFormat; mOutStream = null; - mOutUri = uri; - try { - mOutStream = getContentResolver().openOutputStream(uri); - } catch (FileNotFoundException e) { - Log.w(LOGTAG, "cannot write output: " + mOutUri.toString(), e); + mOutUri = destUri; + mInUri = sourceUri; + mFlags = flags; + mCrop = cropBounds; + mPhoto = photoBounds; + 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 + + if ((flags & DO_EXTRA_OUTPUT) != 0) { + if (mOutUri == null) { + Log.w(LOGTAG, "cannot write file, no output URI given"); + } else { + try { + mOutStream = getContentResolver().openOutputStream(mOutUri); + } catch (FileNotFoundException e) { + Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e); + } + } } - } - @Override - protected Boolean doInBackground(Bitmap... params) { - if (mOutStream == null) { - return false; + 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); + } + } } - CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); - return params[0].compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream); } @Override - protected void onPostExecute(Boolean result) { - if (result.booleanValue() == false) { - Log.w(LOGTAG, "could not compress to output: " + mOutUri.toString()); - doneSaveBitmap(null); + protected Boolean doInBackground(Bitmap... params) { + boolean failure = false; + Bitmap img = params[0]; + + // 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); + mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded); + } } - doneSaveBitmap(mOutUri); - } - } - - private void startSetWallpaper(Bitmap bmap) { - if (bmap == null) { - throw new IllegalArgumentException("bad argument to startSetWallpaper"); - } - mActiveBackgroundIO++; - Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show(); - mSetWallpaperTask = new SetWallpaperTask(); - mSetWallpaperTask.execute(bmap); - - } - - private void doneSetWallpaper() { - mActiveBackgroundIO--; - done(); - } - - private class SetWallpaperTask extends AsyncTask<Bitmap, Void, Boolean> { - private final WallpaperManager mWPManager; - - public SetWallpaperTask() { - mWPManager = WallpaperManager.getInstance(getApplicationContext()); - } - @Override - protected Boolean doInBackground(Bitmap... params) { - try { - mWPManager.setBitmap(params[0]); - } catch (IOException e) { - Log.w(LOGTAG, "fail to set wall paper", e); + // Find the small cropped bitmap that is returned in the intent + if ((mFlags & DO_RETURN_DATA) != 0) { + assert (img != null); + Bitmap ret = getCroppedImage(img, mCrop, mPhoto); + if (ret != null) { + ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT); + } + if (ret == null) { + 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); + } } - return true; - } - @Override - protected void onPostExecute(Boolean result) { - doneSetWallpaper(); - } - } + // 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; + } - private void startFinishOutput() { - if (mOriginalBitmap != null && mCropExtras != null) { - Bitmap cropped = null; - if (mCropExtras.getExtraOutput() != null) { - if (cropped == null) { - cropped = getCroppedImage(mOriginalBitmap); + // Find crop bounds (scaled to original image size) + RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig); + if (trueCrop == null) { + Log.w(LOGTAG, "cannot find crop for full size image"); + failure = true; + return false; } - startSaveBitmap(cropped, mCropExtras.getExtraOutput(), - mCropExtras.getOutputFormat()); - } - if (mCropExtras.getSetAsWallpaper()) { - if (cropped == null) { - cropped = getCroppedImage(mOriginalBitmap); + Rect roundedTrueCrop = new Rect(); + trueCrop.roundOut(roundedTrueCrop); + + if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { + Log.w(LOGTAG, "crop has bad values for full size image"); + failure = true; + return false; } - startSetWallpaper(cropped); - } - if (mCropExtras.getReturnData()) { - if (cropped == null) { - cropped = getCroppedImage(mOriginalBitmap); + // Do region decoding to get crop bitmap + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inMutable = true; + Bitmap crop = decoder.decodeRegion(roundedTrueCrop, options); + decoder.recycle(); + + if (crop == null) { + Log.w(LOGTAG, "cannot region decode file: " + mInUri.toString()); + failure = true; + return false; + } + 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; + } } - int bmapSize = cropped.getRowBytes() * cropped.getHeight(); - if (bmapSize > MAX_BMAP_IN_INTENT) { - Log.w(LOGTAG, "Bitmap too large to be returned via intent"); + // Get output compression format + CompressFormat cf = + convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); + + // If we only need to output to a URI, compress straight to file + if (mFlags == DO_EXTRA_OUTPUT) { + if (mOutStream == null + || !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 { - mResultIntent.putExtra(CropExtras.KEY_DATA, cropped); + // Compress to byte array + ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); + if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { + + // If we need to output to a Uri, write compressed + // bitmap out + if ((mFlags & DO_EXTRA_OUTPUT) != 0) { + if (mOutStream == null) { + Log.w(LOGTAG, + "failed to compress bitmap to file: " + mOutUri.toString()); + failure = true; + } else { + try { + mOutStream.write(tmpOut.toByteArray()); + mResultIntent.setData(mOutUri); + } catch (IOException e) { + Log.w(LOGTAG, + "failed to compress bitmap to file: " + + mOutUri.toString(), e); + failure = true; + } + } + } + + // If we need to set to the wallpaper, set it + if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) { + if (mWPManager == null) { + Log.w(LOGTAG, "no wallpaper manager"); + failure = true; + } else { + try { + mWPManager.setStream(new ByteArrayInputStream(tmpOut + .toByteArray())); + } catch (IOException e) { + Log.w(LOGTAG, "cannot write stream to wallpaper", e); + failure = true; + } + } + } + } else { + Log.w(LOGTAG, "cannot compress bitmap"); + failure = true; + } } } - setResult(RESULT_OK, mResultIntent); - } else { - setResult(RESULT_CANCELED, mResultIntent); + return !failure; // True if any of the operations failed } - done(); + + @Override + protected void onPostExecute(Boolean result) { + doneBitmapIO(result.booleanValue(), mResultIntent); + } + } private void done() { - if (mActiveBackgroundIO == 0) { - finish(); - } + finish(); } - private Bitmap getCroppedImage(Bitmap image) { + protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) { RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight()); - RectF crop = getBitmapCrop(imageBounds); + RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds); if (crop == null) { - return image; + return null; } Rect intCrop = new Rect(); crop.roundOut(intCrop); @@ -387,29 +553,56 @@ public class CropActivity extends Activity { intCrop.height()); } - private RectF getBitmapCrop(RectF imageBounds) { - RectF crop = new RectF(); - if (!mCropView.getCropBounds(crop, imageBounds)) { - Log.w(LOGTAG, "could not get crop"); + protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) { + if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) { + throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()"); + } + int shifts = 0; + int size = CropMath.getBitmapSize(image); + while (size > max_size) { + shifts++; + size /= 4; + } + Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts, + image.getHeight() >> shifts, true); + if (ret == null) { return null; } - return crop; + // Handle edge case for rounding. + if (CropMath.getBitmapSize(ret) > max_size) { + return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true); + } + return ret; } /** - * Helper method for unit conversions. + * Gets the crop extras from the intent, or null if none exist. */ - public float getPixelsFromDip(float value) { - Resources r = getResources(); - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, - r.getDisplayMetrics()); + protected static CropExtras getExtrasFromIntent(Intent intent) { + Bundle extras = intent.getExtras(); + if (extras != null) { + return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0), + extras.getInt(CropExtras.KEY_OUTPUT_Y, 0), + extras.getBoolean(CropExtras.KEY_SCALE, true) && + extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false), + extras.getInt(CropExtras.KEY_ASPECT_X, 0), + extras.getInt(CropExtras.KEY_ASPECT_Y, 0), + extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false), + extras.getBoolean(CropExtras.KEY_RETURN_DATA, false), + (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT), + extras.getString(CropExtras.KEY_OUTPUT_FORMAT), + extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false), + extras.getFloat(CropExtras.KEY_SPOTLIGHT_X), + extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y)); + } + return null; } - private static CompressFormat convertExtensionToCompressFormat(String extension) { + protected static CompressFormat convertExtensionToCompressFormat(String extension) { return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; } - private static String getFileExtension(String requestFormat) { + protected static String getFileExtension(String requestFormat) { String outputFormat = (requestFormat == null) ? "jpg" : requestFormat; @@ -419,4 +612,14 @@ public class CropActivity extends Activity { : "jpg"; } + private RectF getBitmapCrop(RectF imageBounds) { + RectF crop = mCropView.getCrop(); + RectF photo = mCropView.getPhoto(); + if (crop == null || photo == null) { + Log.w(LOGTAG, "could not get crop"); + return null; + } + RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds); + return scaledCrop; + } } diff --git a/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java b/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java index 483cb6372..a3664d764 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java +++ b/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java @@ -113,4 +113,17 @@ public abstract class CropDrawingUtils { return m.setRectToRect(imageBounds, displayBounds, Matrix.ScaleToFit.CENTER); } + public static boolean setImageToScreenMatrix(Matrix dst, RectF image, + RectF screen, int rotation) { + RectF rotatedImage = new RectF(); + dst.setRotate(rotation, image.centerX(), image.centerY()); + if (!dst.mapRect(rotatedImage, image)) { + return false; // fails for rotations that are not multiples of 90 + // degrees + } + boolean rToR = dst.setRectToRect(rotatedImage, screen, Matrix.ScaleToFit.CENTER); + boolean rot = dst.preRotate(rotation, image.centerX(), image.centerY()); + return rToR && rot; + } + } diff --git a/src/com/android/gallery3d/filtershow/crop/CropLoader.java b/src/com/android/gallery3d/filtershow/crop/CropLoader.java index 132d6c1dc..fc461f5d0 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropLoader.java +++ b/src/com/android/gallery3d/filtershow/crop/CropLoader.java @@ -17,6 +17,7 @@ package com.android.gallery3d.filtershow.crop; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteException; @@ -24,27 +25,32 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Rect; import android.net.Uri; +import android.os.Environment; import android.provider.MediaStore; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.Images.ImageColumns; import android.util.Log; import com.android.gallery3d.common.Utils; import com.android.gallery3d.exif.ExifInterface; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.sql.Date; +import java.text.SimpleDateFormat; /** * This class contains static methods for loading a bitmap and - * mantains no instance state. + * maintains no instance state. */ public abstract class CropLoader { public static final String LOGTAG = "CropLoader"; public static final String JPEG_MIME_TYPE = "image/jpeg"; - public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT; - public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP; - public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT; - public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM; + + private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss"; + public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; /** * Returns the orientation of image at the given URI as one of 0, 90, 180, @@ -54,7 +60,7 @@ public abstract class CropLoader { * @param context context whose ContentResolver to use. * @return the orientation of the image. Defaults to 0. */ - public static int getMetadataOrientation(Uri uri, Context context) { + public static int getMetadataRotation(Uri uri, Context context) { if (uri == null || context == null) { throw new IllegalArgumentException("bad argument to getScaledBitmap"); } @@ -78,25 +84,11 @@ public abstract class CropLoader { Cursor cursor = null; try { cursor = context.getContentResolver().query(uri, - new String[] { - MediaStore.Images.ImageColumns.ORIENTATION - }, + new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); if (cursor.moveToNext()) { int ori = cursor.getInt(0); - - switch (ori) { - case 0: - return ORI_NORMAL; - case 90: - return ORI_ROTATE_90; - case 270: - return ORI_ROTATE_270; - case 180: - return ORI_ROTATE_180; - default: - return 0; - } + return (ori < 0) ? 0 : ori; } } catch (SQLiteException e) { return 0; @@ -199,4 +191,112 @@ public abstract class CropLoader { return null; } + // TODO: Super gnarly (copied from SaveCopyTask.java), do cleanup. + + public static File getFinalSaveDirectory(Context context, Uri sourceUri) { + File saveDirectory = getSaveDirectory(context, sourceUri); + if ((saveDirectory == null) || !saveDirectory.canWrite()) { + saveDirectory = new File(Environment.getExternalStorageDirectory(), + DEFAULT_SAVE_DIRECTORY); + } + // Create the directory if it doesn't exist + if (!saveDirectory.exists()) + saveDirectory.mkdirs(); + return saveDirectory; + } + + + + public static String getNewFileName(long time) { + return new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(time)); + } + + public static File getNewFile(Context context, Uri sourceUri, String filename) { + File saveDirectory = getFinalSaveDirectory(context, sourceUri); + return new File(saveDirectory, filename + ".JPG"); + } + + private interface ContentResolverQueryCallback { + + void onCursorResult(Cursor cursor); + } + + private static void querySource(Context context, Uri sourceUri, String[] projection, + ContentResolverQueryCallback callback) { + ContentResolver contentResolver = context.getContentResolver(); + Cursor cursor = null; + try { + cursor = contentResolver.query(sourceUri, projection, null, null, + null); + if ((cursor != null) && cursor.moveToNext()) { + callback.onCursorResult(cursor); + } + } catch (Exception e) { + // Ignore error for lacking the data column from the source. + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + private static File getSaveDirectory(Context context, Uri sourceUri) { + final File[] dir = new File[1]; + querySource(context, sourceUri, new String[] { + ImageColumns.DATA }, new ContentResolverQueryCallback() { + @Override + public void onCursorResult(Cursor cursor) { + dir[0] = new File(cursor.getString(0)).getParentFile(); + } + }); + return dir[0]; + } + + public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName, + long time) { + time /= 1000; + + final ContentValues values = new ContentValues(); + values.put(Images.Media.TITLE, saveFileName); + values.put(Images.Media.DISPLAY_NAME, file.getName()); + values.put(Images.Media.MIME_TYPE, "image/jpeg"); + values.put(Images.Media.DATE_TAKEN, time); + values.put(Images.Media.DATE_MODIFIED, time); + values.put(Images.Media.DATE_ADDED, time); + values.put(Images.Media.ORIENTATION, 0); + values.put(Images.Media.DATA, file.getAbsolutePath()); + values.put(Images.Media.SIZE, file.length()); + + final String[] projection = new String[] { + ImageColumns.DATE_TAKEN, + ImageColumns.LATITUDE, ImageColumns.LONGITUDE, + }; + querySource(context, sourceUri, projection, + new ContentResolverQueryCallback() { + + @Override + public void onCursorResult(Cursor cursor) { + values.put(Images.Media.DATE_TAKEN, cursor.getLong(0)); + + double latitude = cursor.getDouble(1); + double longitude = cursor.getDouble(2); + // TODO: Change || to && after the default location + // issue is fixed. + if ((latitude != 0f) || (longitude != 0f)) { + values.put(Images.Media.LATITUDE, latitude); + values.put(Images.Media.LONGITUDE, longitude); + } + } + }); + + return context.getContentResolver().insert( + Images.Media.EXTERNAL_CONTENT_URI, values); + } + + public static Uri makeAndInsertUri(Context context, Uri sourceUri) { + long time = System.currentTimeMillis(); + String filename = getNewFileName(time); + File file = getNewFile(context, sourceUri, filename); + return insertContent(context, sourceUri, file, filename, time); + } } diff --git a/src/com/android/gallery3d/filtershow/crop/CropMath.java b/src/com/android/gallery3d/filtershow/crop/CropMath.java index 52b11a56b..ed800c912 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropMath.java +++ b/src/com/android/gallery3d/filtershow/crop/CropMath.java @@ -16,6 +16,7 @@ package com.android.gallery3d.filtershow.crop; +import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.RectF; @@ -205,6 +206,35 @@ public class CropMath { r.set(centX - hw, centY - hh, centX + hw, centY + hh); } + /** + * Stretches/Scales/Translates photoBounds to match displayBounds, and + * and returns an equivalent stretched/scaled/translated cropBounds or null + * if the mapping is invalid. + * @param cropBounds cropBounds to transform + * @param photoBounds original bounds containing crop bounds + * @param displayBounds final bounds for crop + * @return the stretched/scaled/translated crop bounds that fit within displayBounds + */ + public static RectF getScaledCropBounds(RectF cropBounds, RectF photoBounds, + RectF displayBounds) { + Matrix m = new Matrix(); + m.setRectToRect(photoBounds, displayBounds, Matrix.ScaleToFit.FILL); + RectF trueCrop = new RectF(cropBounds); + if (!m.mapRect(trueCrop)) { + return null; + } + return trueCrop; + } + + /** + * Returns the size of a bitmap in bytes. + * @param bmap bitmap whose size to check + * @return bitmap size in bytes + */ + public static int getBitmapSize(Bitmap bmap) { + return bmap.getRowBytes() * bmap.getHeight(); + } + private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) { float dy = rotatedRect[1] - rotatedRect[3]; float dx = rotatedRect[0] - rotatedRect[2]; diff --git a/src/com/android/gallery3d/filtershow/crop/CropObject.java b/src/com/android/gallery3d/filtershow/crop/CropObject.java index 7999b4878..bea3ffabd 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropObject.java +++ b/src/com/android/gallery3d/filtershow/crop/CropObject.java @@ -89,7 +89,7 @@ public class CropObject { clearSelectState(); } - public boolean setInnerAspectRatio(int width, int height) { + public boolean setInnerAspectRatio(float width, float height) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("Width and Height must be greater than zero"); } diff --git a/src/com/android/gallery3d/filtershow/crop/CropView.java b/src/com/android/gallery3d/filtershow/crop/CropView.java index d762ad459..dca752146 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropView.java +++ b/src/com/android/gallery3d/filtershow/crop/CropView.java @@ -25,30 +25,46 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.crop.CropDrawingUtils; + public class CropView extends View { private static final String LOGTAG = "CropView"; - Bitmap mImage = null; - CropObject mCropObj = null; + private RectF mImageBounds = new RectF(); + private RectF mScreenBounds = new RectF(); + private RectF mScreenImageBounds = new RectF(); + private RectF mScreenCropBounds = new RectF(); + private Rect mShadowBounds = new Rect(); + + private Bitmap mBitmap; + private Paint mPaint = new Paint(); + + private NinePatchDrawable mShadow; + private CropObject mCropObj = null; private final Drawable mCropIndicator; private final int mIndicatorSize; + private int mRotation = 0; + private boolean mMovingBlock = false; + private Matrix mDisplayMatrix = null; + private Matrix mDisplayMatrixInverse = null; + private boolean mDirty = false; private float mPrevX = 0; private float mPrevY = 0; - private int mMinSideSize = 45; - private int mTouchTolerance = 20; - private boolean mMovingBlock = false; - - private Matrix mDisplayMatrix = null; - private Matrix mDisplayMatrixInverse = null; + private int mShadowMargin = 15; + private int mMargin = 32; + private int mOverlayShadowColor = 0xCF000000; + private int mMinSideSize = 90; + private int mTouchTolerance = 40; private enum Mode { NONE, MOVE @@ -58,57 +74,41 @@ public class CropView extends View { public CropView(Context context, AttributeSet attrs) { super(context, attrs); - Resources resources = context.getResources(); - mCropIndicator = resources.getDrawable(R.drawable.camera_crop); - mIndicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); - } - - // For unchanging parameters - public void setup(Bitmap image, int minSideSize, int touchTolerance) { - mImage = image; - mMinSideSize = minSideSize; - mTouchTolerance = touchTolerance; - reset(); + Resources rsc = context.getResources(); + mShadow = (NinePatchDrawable) rsc.getDrawable(R.drawable.geometry_shadow); + mCropIndicator = rsc.getDrawable(R.drawable.camera_crop); + mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size); + mShadowMargin = (int) rsc.getDimension(R.dimen.shadow_margin); + mMargin = (int) rsc.getDimension(R.dimen.preview_margin); + mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side); + mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance); + mOverlayShadowColor = (int) rsc.getColor(R.color.crop_shadow_color); } - @Override - public void onDraw(Canvas canvas) { - if (mImage == null) { - return; - } - int displayWidth = getWidth(); - int displayHeight = getHeight(); - Rect imageBoundsOriginal = new Rect(0, 0, mImage.getWidth(), mImage.getHeight()); - Rect displayBoundsOriginal = new Rect(0, 0, displayWidth, displayHeight); - if (mCropObj == null) { - reset(); - mCropObj = new CropObject(imageBoundsOriginal, imageBoundsOriginal, 0); - } - - RectF imageBounds = mCropObj.getInnerBounds(); - RectF displayBounds = mCropObj.getOuterBounds(); - - // If display matrix doesn't exist, create it and its dependencies - if (mDisplayMatrix == null || mDisplayMatrixInverse == null) { - mDisplayMatrix = CropDrawingUtils.getBitmapToDisplayMatrix(displayBounds, new RectF( - displayBoundsOriginal)); - mDisplayMatrixInverse = new Matrix(); - mDisplayMatrixInverse.reset(); - if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) { - Log.w(LOGTAG, "could not invert display matrix"); + public void initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation) { + mBitmap = image; + if (mCropObj != null) { + RectF crop = mCropObj.getInnerBounds(); + RectF containing = mCropObj.getOuterBounds(); + if (crop != newCropBounds || containing != newPhotoBounds + || mRotation != rotation) { + mRotation = rotation; + mCropObj.resetBoundsTo(newCropBounds, newPhotoBounds); + clearDisplay(); } - // Scale min side and tolerance by display matrix scale factor - mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize)); - mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance)); + } else { + mRotation = rotation; + mCropObj = new CropObject(newPhotoBounds, newCropBounds, 0); + clearDisplay(); } - canvas.drawBitmap(mImage, mDisplayMatrix, new Paint()); + } - if (mDisplayMatrix.mapRect(imageBounds)) { - CropDrawingUtils.drawCropRect(canvas, imageBounds); - CropDrawingUtils.drawRuleOfThird(canvas, imageBounds); - CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, imageBounds, - mCropObj.isFixedAspect(), mCropObj.getSelectState()); - } + public RectF getCrop() { + return mCropObj.getInnerBounds(); + } + + public RectF getPhoto() { + return mCropObj.getOuterBounds(); } @Override @@ -133,8 +133,6 @@ public class CropView extends View { mPrevX = x; mPrevY = y; mState = Mode.MOVE; - } else { - reset(); } break; case (MotionEvent.ACTION_UP): @@ -144,8 +142,6 @@ public class CropView extends View { mPrevX = x; mPrevY = y; mState = Mode.NONE; - } else { - reset(); } break; case (MotionEvent.ACTION_MOVE): @@ -155,41 +151,139 @@ public class CropView extends View { mCropObj.moveCurrentSelection(dx, dy); mPrevX = x; mPrevY = y; - } else { - reset(); } break; default: - reset(); break; } invalidate(); return true; } - public void reset() { - Log.w(LOGTAG, "reset called"); + private void reset() { + Log.w(LOGTAG, "crop reset called"); mState = Mode.NONE; mCropObj = null; + mRotation = 0; + mMovingBlock = false; + clearDisplay(); + } + + private void clearDisplay() { mDisplayMatrix = null; mDisplayMatrixInverse = null; - mMovingBlock = false; invalidate(); } - public boolean getCropBounds(RectF out_crop, RectF in_newContaining) { - Matrix m = new Matrix(); - RectF inner = mCropObj.getInnerBounds(); + protected void configChanged() { + mDirty = true; + } + + public void applyFreeAspect() { + mCropObj.unsetAspectRatio(); + invalidate(); + } + + public void applyOriginalAspect() { RectF outer = mCropObj.getOuterBounds(); - if (!m.setRectToRect(outer, in_newContaining, Matrix.ScaleToFit.FILL)) { - Log.w(LOGTAG, "failed to make transform matrix"); - return false; + float w = outer.width(); + float h = outer.height(); + if (w > 0 && h > 0) { + applyAspect(w, h); + mCropObj.resetBoundsTo(outer, outer); + } else { + Log.w(LOGTAG, "failed to set aspect ratio original"); } - if (!m.mapRect(inner)) { - Log.w(LOGTAG, "failed to transform crop bounds"); - return false; + } + + public void applySquareAspect() { + applyAspect(1, 1); + } + + public void applyAspect(float x, float y) { + if (x <= 0 || y <= 0) { + throw new IllegalArgumentException("Bad arguments to applyAspect"); } - out_crop.set(inner); - return true; + if (!mCropObj.setInnerAspectRatio(x, y)) { + Log.w(LOGTAG, "failed to set aspect ratio"); + } + invalidate(); + } + + @Override + public void onDraw(Canvas canvas) { + if (mBitmap == null) { + return; + } + if (mDirty) { + mDirty = false; + clearDisplay(); + } + + mImageBounds = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); + mScreenBounds = new RectF(0, 0, canvas.getWidth(), canvas.getHeight()); + mScreenBounds.inset(mMargin, mMargin); + + // If crop object doesn't exist, create it and update it from master + // state + if (mCropObj == null) { + reset(); + mCropObj = new CropObject(mImageBounds, mImageBounds, 0); + } + + // If display matrix doesn't exist, create it and its dependencies + if (mDisplayMatrix == null || mDisplayMatrixInverse == null) { + mDisplayMatrix = new Matrix(); + mDisplayMatrix.reset(); + if (!CropDrawingUtils.setImageToScreenMatrix(mDisplayMatrix, mImageBounds, mScreenBounds, + mRotation)) { + Log.w(LOGTAG, "failed to get screen matrix"); + mDisplayMatrix = null; + return; + } + mDisplayMatrixInverse = new Matrix(); + mDisplayMatrixInverse.reset(); + if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) { + Log.w(LOGTAG, "could not invert display matrix"); + mDisplayMatrixInverse = null; + return; + } + // Scale min side and tolerance by display matrix scale factor + mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize)); + mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance)); + } + + mScreenImageBounds.set(mImageBounds); + + // Draw background shadow + if (mDisplayMatrix.mapRect(mScreenImageBounds)) { + int margin = (int) mDisplayMatrix.mapRadius(mShadowMargin); + mScreenImageBounds.roundOut(mShadowBounds); + mShadowBounds.set(mShadowBounds.left - margin, mShadowBounds.top - + margin, mShadowBounds.right + margin, mShadowBounds.bottom + margin); + mShadow.setBounds(mShadowBounds); + mShadow.draw(canvas); + } + + // Draw actual bitmap + canvas.drawBitmap(mBitmap, mDisplayMatrix, mPaint); + + mCropObj.getInnerBounds(mScreenCropBounds); + + if (mDisplayMatrix.mapRect(mScreenCropBounds)) { + + // Draw overlay shadows + Paint p = new Paint(); + p.setColor(mOverlayShadowColor); + p.setStyle(Paint.Style.FILL); + CropDrawingUtils.drawShadows(canvas, p, mScreenCropBounds, mScreenImageBounds); + + // Draw crop rect and markers + CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds); + CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds); + CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, + mScreenCropBounds, mCropObj.isFixedAspect(), mCropObj.getSelectState()); + } + } } diff --git a/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java b/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java index a3fc5aab4..d4e66edf8 100644 --- a/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java +++ b/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java @@ -26,7 +26,7 @@ import com.android.gallery3d.filtershow.imageshow.ImageShow; * The editor with no slider for filters without UI */ public class ImageOnlyEditor extends Editor { - public static int ID = R.id.imageOnlyEditor; + public final static int ID = R.id.imageOnlyEditor; private final String LOGTAG = "ImageOnlyEditor"; public ImageOnlyEditor() { @@ -37,6 +37,10 @@ public class ImageOnlyEditor extends Editor { super(id); } + public boolean useUtilityPanel() { + return false; + } + @Override public void createEditor(Context context, FrameLayout frameLayout) { super.createEditor(context, frameLayout); diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java index 66ad10640..ec9a4368c 100644 --- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java +++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java @@ -15,7 +15,10 @@ */ package com.android.gallery3d.filtershow.filters; +import android.content.Context; import android.content.res.Resources; + +import com.android.gallery3d.R; import com.android.gallery3d.filtershow.presets.ImagePreset; import java.util.HashMap; @@ -86,7 +89,7 @@ public abstract class BaseFiltersManager { protected void addFilterClasses(Vector<Class> filters) { filters.add(ImageFilterTinyPlanet.class); - filters.add(ImageFilterRedEye.class); + //filters.add(ImageFilterRedEye.class); filters.add(ImageFilterWBalance.class); filters.add(ImageFilterExposure.class); filters.add(ImageFilterVignette.class); @@ -109,12 +112,40 @@ public abstract class BaseFiltersManager { filters.add(ImageFilterGeometry.class); } - public void addBorders(Vector<FilterRepresentation> representations) { - // Override + public void addBorders(Context context, Vector<FilterRepresentation> representations) { + } - public void addLooks(Vector<FilterRepresentation> representations) { - // Override + public void addLooks(Context context, Vector<FilterRepresentation> representations) { + int[] drawid = { + R.drawable.filtershow_fx_0005_punch, + R.drawable.filtershow_fx_0000_vintage, + R.drawable.filtershow_fx_0004_bw_contrast, + R.drawable.filtershow_fx_0002_bleach, + R.drawable.filtershow_fx_0001_instant, + R.drawable.filtershow_fx_0007_washout, + R.drawable.filtershow_fx_0003_blue_crush, + R.drawable.filtershow_fx_0008_washout_color, + R.drawable.filtershow_fx_0006_x_process + }; + + int[] fxNameid = { + R.string.ffx_punch, + R.string.ffx_vintage, + R.string.ffx_bw_contrast, + R.string.ffx_bleach, + R.string.ffx_instant, + R.string.ffx_washout, + R.string.ffx_blue_crush, + R.string.ffx_washout_color, + R.string.ffx_x_process + }; + + for (int i = 0; i < drawid.length; i++) { + FilterFxRepresentation fx = new FilterFxRepresentation( + context.getString(fxNameid[i]), drawid[i], fxNameid[i]); + representations.add(fx); + } } public void addEffects(Vector<FilterRepresentation> representations) { @@ -137,7 +168,7 @@ public abstract class BaseFiltersManager { } public void addTools(Vector<FilterRepresentation> representations) { - representations.add(getRepresentation(ImageFilterRedEye.class)); + //representations.add(getRepresentation(ImageFilterRedEye.class)); representations.add(getRepresentation(ImageFilterDraw.class)); } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index 65fe523c0..045c1a538 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java @@ -28,6 +28,7 @@ import android.graphics.RectF; import android.net.Uri; import android.os.Handler; import android.util.AttributeSet; +import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.OnDoubleTapListener; import android.view.GestureDetector.OnGestureListener; @@ -83,14 +84,22 @@ public class ImageShow extends View implements OnGestureListener, private static int mOriginalTextSize = 26; private static String mOriginalText = "Original"; private boolean mZoomIn = false; - - protected GeometryMetadata getGeometry() { - return new GeometryMetadata(getImagePreset().mGeoData); + Point mOriginalTranslation = new Point(); + float mOriginalScale; + float mStartFocusX, mStartFocusY; + private enum InteractionMode { + NONE, + SCALE, + MOVE } - private String mToast = null; private boolean mShowToast = false; private boolean mImportantToast = false; + InteractionMode mInteractionMode = InteractionMode.NONE; + + protected GeometryMetadata getGeometry() { + return new GeometryMetadata(getImagePreset().mGeoData); + } private PanelController mController = null; @@ -559,9 +568,15 @@ public class ImageShow extends View implements OnGestureListener, @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); + int action = event.getAction(); + action = action & MotionEvent.ACTION_MASK; + mGestureDetector.onTouchEvent(event); boolean scaleInProgress = scaleInProgress(); mScaleGestureDetector.onTouchEvent(event); + if (mInteractionMode == InteractionMode.SCALE) { + return true; + } if (!scaleInProgress() && scaleInProgress) { // If we were scaling, the scale will stop but we will // still issue an ACTION_UP. Let the subclasses know. @@ -570,7 +585,8 @@ public class ImageShow extends View implements OnGestureListener, int ex = (int) event.getX(); int ey = (int) event.getY(); - if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (action == MotionEvent.ACTION_DOWN) { + mInteractionMode = InteractionMode.MOVE; mTouchDown.x = ex; mTouchDown.y = ey; mTouchShowOriginalDate = System.currentTimeMillis(); @@ -578,7 +594,7 @@ public class ImageShow extends View implements OnGestureListener, MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation()); } - if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) { mTouch.x = ex; mTouch.y = ey; @@ -600,7 +616,8 @@ public class ImageShow extends View implements OnGestureListener, } } - if (event.getAction() == MotionEvent.ACTION_UP) { + if (action == MotionEvent.ACTION_UP) { + mInteractionMode = InteractionMode.NONE; mTouchShowOriginal = false; mTouchDown.x = 0; mTouchDown.y = 0; @@ -699,7 +716,10 @@ public class ImageShow extends View implements OnGestureListener, @Override public boolean onScale(ScaleGestureDetector detector) { - float scaleFactor = MasterImage.getImage().getScaleFactor(); + MasterImage img = MasterImage.getImage(); + float scaleFactor = img.getScaleFactor(); + Point pos = img.getTranslation(); + scaleFactor = scaleFactor * detector.getScaleFactor(); if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) { scaleFactor = MasterImage.getImage().getMaxScaleFactor(); @@ -708,16 +728,36 @@ public class ImageShow extends View implements OnGestureListener, scaleFactor = 0.5f; } MasterImage.getImage().setScaleFactor(scaleFactor); + scaleFactor = img.getScaleFactor(); + pos = img.getTranslation(); + float focusx = detector.getFocusX(); + float focusy = detector.getFocusY(); + float translateX = (focusx - mStartFocusX) / scaleFactor; + float translateY = (focusy - mStartFocusY) / scaleFactor; + Point translation = MasterImage.getImage().getTranslation(); + translation.x = (int) (mOriginalTranslation.x + translateX); + translation.y = (int) (mOriginalTranslation.y + translateY); + MasterImage.getImage().setTranslation(translation); + + invalidate(); return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { + Point pos = MasterImage.getImage().getTranslation(); + mOriginalTranslation.x = pos.x; + mOriginalTranslation.y = pos.y; + mOriginalScale = MasterImage.getImage().getScaleFactor(); + mStartFocusX = detector.getFocusX(); + mStartFocusY = detector.getFocusY(); + mInteractionMode = InteractionMode.SCALE; return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { + mInteractionMode = InteractionMode.NONE; if (MasterImage.getImage().getScaleFactor() < 1) { MasterImage.getImage().setScaleFactor(1); invalidate(); diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java index 7b881c7f5..528746db1 100644 --- a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java +++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java @@ -250,8 +250,6 @@ public class ImageCurves extends ImageShow { @Override public synchronized boolean onTouchEvent(MotionEvent e) { - super.onTouchEvent(e); - if (e.getPointerCount() != 1) { return true; } |