diff options
Diffstat (limited to 'src/com/android')
89 files changed, 4622 insertions, 3500 deletions
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java index 88ac028e1..081b87aee 100644 --- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java +++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java @@ -17,22 +17,25 @@ package com.android.gallery3d.app; import android.annotation.TargetApi; -import android.app.Activity; import android.app.AlertDialog; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.res.Configuration; import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; +import android.os.IBinder; import android.view.Window; import android.view.WindowManager; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.data.BitmapPool; @@ -40,10 +43,10 @@ import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLRootView; -import com.android.gallery3d.util.ThreadPool; import com.android.gallery3d.util.LightCycleHelper.PanoramaViewHelper; +import com.android.gallery3d.util.ThreadPool; -public class AbstractGalleryActivity extends Activity implements GalleryContext { +public class AbstractGalleryActivity extends SherlockActivity implements GalleryContext { @SuppressWarnings("unused") private static final String TAG = "AbstractGalleryActivity"; private GLRootView mGLRootView; @@ -71,6 +74,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext getWindow().setBackgroundDrawable(null); mPanoramaViewHelper = new PanoramaViewHelper(this); mPanoramaViewHelper.onCreate(); + doBindBatchService(); } @Override @@ -237,6 +241,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext } finally { mGLRootView.unlockRenderThread(); } + doUnbindBatchService(); } @Override @@ -308,4 +313,39 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext return (getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; } + + private BatchService mBatchService; + private boolean mBatchServiceIsBound = false; + private ServiceConnection mBatchServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + mBatchService = ((BatchService.LocalBinder)service).getService(); + } + + public void onServiceDisconnected(ComponentName className) { + mBatchService = null; + } + }; + + private void doBindBatchService() { + bindService(new Intent(this, BatchService.class), mBatchServiceConnection, Context.BIND_AUTO_CREATE); + mBatchServiceIsBound = true; + } + + private void doUnbindBatchService() { + if (mBatchServiceIsBound) { + // Detach our existing connection. + unbindService(mBatchServiceConnection); + mBatchServiceIsBound = false; + } + } + + public ThreadPool getBatchServiceThreadPoolIfAvailable() { + if (mBatchServiceIsBound && mBatchService != null) { + return mBatchService.getThreadPool(); + } else { + // Fall back on the old behavior if for some reason the + // service is not available. + return getThreadPool(); + } + } } diff --git a/src/com/android/gallery3d/app/ActivityState.java b/src/com/android/gallery3d/app/ActivityState.java index cdd91ff4d..b2e39b1cb 100644 --- a/src/com/android/gallery3d/app/ActivityState.java +++ b/src/com/android/gallery3d/app/ActivityState.java @@ -16,24 +16,23 @@ package com.android.gallery3d.app; -import android.app.ActionBar; import android.app.Activity; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.os.BatteryManager; import android.os.Bundle; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; +import android.view.HapticFeedbackConstants; import android.view.Window; import android.view.WindowManager; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.anim.StateTransitionAnimation; import com.android.gallery3d.ui.GLView; @@ -62,9 +61,6 @@ abstract public class ActivityState { public Intent resultData; } - protected boolean mHapticsEnabled; - private ContentResolver mContentResolver; - private boolean mDestroyed = false; private boolean mPlugged = false; boolean mIsFinishing = false; @@ -92,7 +88,6 @@ abstract public class ActivityState { void initialize(AbstractGalleryActivity activity, Bundle data) { mActivity = activity; mData = data; - mContentResolver = activity.getAndroidContext().getContentResolver(); } public Bundle getData() { @@ -175,15 +170,20 @@ abstract public class ActivityState { protected void transitionOnNextPause(Class<? extends ActivityState> outgoing, Class<? extends ActivityState> incoming, StateTransitionAnimation.Transition hint) { - if (outgoing == PhotoPage.class && incoming == AlbumPage.class) { + if (outgoing == SinglePhotoPage.class && incoming == AlbumPage.class) { mNextTransition = StateTransitionAnimation.Transition.Outgoing; - } else if (outgoing == AlbumPage.class && incoming == PhotoPage.class) { + } else if (outgoing == AlbumPage.class && incoming == SinglePhotoPage.class) { mNextTransition = StateTransitionAnimation.Transition.PhotoIncoming; } else { mNextTransition = hint; } } + protected void performHapticFeedback(int feedbackConstant) { + mActivity.getWindow().getDecorView().performHapticFeedback(feedbackConstant, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } + protected void onPause() { if (0 != (mFlags & FLAG_SCREEN_ON_WHEN_PLUGGED)) { ((Activity) mActivity).unregisterReceiver(mPowerIntentReceiver); @@ -198,7 +198,7 @@ abstract public class ActivityState { // should only be called by StateManager void resume() { AbstractGalleryActivity activity = mActivity; - ActionBar actionBar = activity.getActionBar(); + ActionBar actionBar = ((SherlockActivity) activity).getSupportActionBar(); if (actionBar != null) { if ((mFlags & FLAG_HIDE_ACTION_BAR) != 0) { actionBar.hide(); @@ -231,13 +231,6 @@ abstract public class ActivityState { activity.registerReceiver(mPowerIntentReceiver, filter); } - try { - mHapticsEnabled = Settings.System.getInt(mContentResolver, - Settings.System.HAPTIC_FEEDBACK_ENABLED) != 0; - } catch (SettingNotFoundException e) { - mHapticsEnabled = false; - } - onResume(); // the transition store should be cleared after resume; @@ -279,6 +272,6 @@ abstract public class ActivityState { } protected MenuInflater getSupportMenuInflater() { - return mActivity.getMenuInflater(); + return ((SherlockActivity) mActivity).getSupportMenuInflater(); } } diff --git a/src/com/android/gallery3d/app/AlbumDataLoader.java b/src/com/android/gallery3d/app/AlbumDataLoader.java index 0ee1b03af..28a822830 100644 --- a/src/com/android/gallery3d/app/AlbumDataLoader.java +++ b/src/com/android/gallery3d/app/AlbumDataLoader.java @@ -120,8 +120,7 @@ public class AlbumDataLoader { public MediaItem get(int index) { if (!isActive(index)) { - throw new IllegalArgumentException(String.format( - "%s not in (%s, %s)", index, mActiveStart, mActiveEnd)); + return mSource.getMediaItem(index, 1).get(0); } return mData[index % mData.length]; } diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java index ee7a107fd..60000903d 100644 --- a/src/com/android/gallery3d/app/AlbumPage.java +++ b/src/com/android/gallery3d/app/AlbumPage.java @@ -24,13 +24,13 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.os.Vibrator; import android.provider.MediaStore; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; +import android.view.HapticFeedbackConstants; import android.widget.Toast; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; @@ -57,6 +57,9 @@ import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.MediaSetUtils; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.CropExtras; + public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner, SelectionManager.SelectionListener, MediaSet.SyncListener, GalleryActionBar.OnAlbumModeSelectedListener { @@ -89,7 +92,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster private AlbumDataLoader mAlbumDataAdapter; protected SelectionManager mSelectionManager; - private Vibrator mVibrator; private boolean mGetContent; private boolean mShowClusterMenu; @@ -304,10 +306,10 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster startInFilmstrip); data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, mMediaSet.isCameraRoll()); if (startInFilmstrip) { - mActivity.getStateManager().switchState(this, PhotoPage.class, data); + mActivity.getStateManager().switchState(this, FilmstripPage.class, data); } else { mActivity.getStateManager().startStateForResult( - PhotoPage.class, REQUEST_PHOTO, data); + SinglePhotoPage.class, REQUEST_PHOTO, data); } } } @@ -318,11 +320,11 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster if (mData.getString(Gallery.EXTRA_CROP) != null) { // TODO: Handle MtpImagew Uri uri = dm.getContentUri(item.getPath()); - Intent intent = new Intent(CropImage.ACTION_CROP, uri) + Intent intent = new Intent(FilterShowActivity.CROP_ACTION, uri) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) .putExtras(getData()); if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) { - intent.putExtra(CropImage.KEY_RETURN_DATA, true); + intent.putExtra(CropExtras.KEY_RETURN_DATA, true); } activity.startActivity(intent); activity.finish(); @@ -371,7 +373,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false); mDetailsSource = new MyDetailsSource(); Context context = mActivity.getAndroidContext(); - mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); // Enable auto-select-all for mtp album if (data.getBoolean(KEY_AUTO_SELECT_ALL)) { @@ -379,7 +380,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster } mLaunchedFromPhotoPage = - mActivity.getStateManager().hasStateClass(PhotoPage.class); + mActivity.getStateManager().hasStateClass(FilmstripPage.class); mInCameraApp = data.getBoolean(PhotoPage.KEY_APP_BRIDGE, false); mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { @@ -443,7 +444,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mSelectionManager.leaveSelectionMode(); } mAlbumView.setSlotFilter(null); - + mActionModeHandler.pause(); mAlbumDataAdapter.pause(); mAlbumView.pause(); DetailsHelper.pause(); @@ -456,7 +457,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mSyncTask = null; clearLoadingBit(BIT_LOADING_SYNC); } - mActionModeHandler.pause(); } @Override @@ -465,6 +465,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster if (mAlbumDataAdapter != null) { mAlbumDataAdapter.setLoadingListener(null); } + mActionModeHandler.destroy(); } private void initializeViews() { @@ -662,7 +663,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster switch (mode) { case SelectionManager.ENTER_SELECTION_MODE: { mActionModeHandler.startActionMode(); - if (mHapticsEnabled) mVibrator.vibrate(100); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); break; } case SelectionManager.LEAVE_SELECTION_MODE: { diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java index cae606be1..df68ffbaa 100644 --- a/src/com/android/gallery3d/app/AlbumSetPage.java +++ b/src/com/android/gallery3d/app/AlbumSetPage.java @@ -23,16 +23,16 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.os.Vibrator; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; +import android.view.HapticFeedbackConstants; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.RelativeLayout; import android.widget.Toast; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; @@ -92,7 +92,6 @@ public class AlbumSetPage extends ActivityState implements private boolean mShowClusterMenu; private GalleryActionBar mActionBar; private int mSelectedAction; - private Vibrator mVibrator; protected SelectionManager mSelectionManager; private AlbumSetDataLoader mAlbumSetDataAdapter; @@ -275,7 +274,7 @@ public class AlbumSetPage extends ActivityState implements data.putBoolean(PhotoPage.KEY_START_IN_FILMSTRIP, true); data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, targetSet.isCameraRoll()); mActivity.getStateManager().startStateForResult( - PhotoPage.class, AlbumPage.REQUEST_PHOTO, data); + FilmstripPage.class, AlbumPage.REQUEST_PHOTO, data); return; } data.putString(AlbumPage.KEY_MEDIA_PATH, mediaPath); @@ -332,7 +331,6 @@ public class AlbumSetPage extends ActivityState implements mSubtitle = data.getString(AlbumSetPage.KEY_SET_SUBTITLE); mEyePosition = new EyePosition(context, this); mDetailsSource = new MyDetailsSource(); - mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mActionBar = mActivity.getGalleryActionBar(); mSelectedAction = data.getInt(AlbumSetPage.KEY_SELECTED_CLUSTER_TYPE, FilterUtils.CLUSTER_BY_ALBUM); @@ -353,8 +351,9 @@ public class AlbumSetPage extends ActivityState implements @Override public void onDestroy() { - cleanupCameraButton(); super.onDestroy(); + cleanupCameraButton(); + mActionModeHandler.destroy(); } private boolean setupCameraButton() { @@ -439,9 +438,9 @@ public class AlbumSetPage extends ActivityState implements public void onPause() { super.onPause(); mIsActive = false; - mActionModeHandler.pause(); mAlbumSetDataAdapter.pause(); mAlbumSetView.pause(); + mActionModeHandler.pause(); mEyePosition.pause(); DetailsHelper.pause(); // Call disableClusterMenu to avoid receiving callback after paused. @@ -655,7 +654,7 @@ public class AlbumSetPage extends ActivityState implements case SelectionManager.ENTER_SELECTION_MODE: { mActionBar.disableClusterMenu(true); mActionModeHandler.startActionMode(); - if (mHapticsEnabled) mVibrator.vibrate(100); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); break; } case SelectionManager.LEAVE_SELECTION_MODE: { diff --git a/src/com/android/gallery3d/app/BatchService.java b/src/com/android/gallery3d/app/BatchService.java new file mode 100644 index 000000000..98a1d8215 --- /dev/null +++ b/src/com/android/gallery3d/app/BatchService.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 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 android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; + +import com.android.gallery3d.util.ThreadPool; + +public class BatchService extends Service { + + public class LocalBinder extends Binder { + BatchService getService() { + return BatchService.this; + } + } + + private final IBinder mBinder = new LocalBinder(); + private ThreadPool mThreadPool = new ThreadPool(1, 1); + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + public ThreadPool getThreadPool() { + return mThreadPool; + } +} diff --git a/src/com/android/gallery3d/app/CommonControllerOverlay.java b/src/com/android/gallery3d/app/CommonControllerOverlay.java index ab43dada5..089872fa5 100644 --- a/src/com/android/gallery3d/app/CommonControllerOverlay.java +++ b/src/com/android/gallery3d/app/CommonControllerOverlay.java @@ -154,7 +154,7 @@ public abstract class CommonControllerOverlay extends FrameLayout implements @Override public void showEnded() { mState = State.ENDED; - showMainView(mPlayPauseReplayView); + if (mCanReplay) showMainView(mPlayPauseReplayView); } @Override diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java deleted file mode 100644 index 89ca63d44..000000000 --- a/src/com/android/gallery3d/app/CropImage.java +++ /dev/null @@ -1,1040 +0,0 @@ -/* - * 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 android.annotation.TargetApi; -import android.app.ActionBar; -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.Build; -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.util.FloatMath; -import android.view.Menu; -import android.view.MenuItem; -import android.view.Window; -import android.view.WindowManager; -import android.widget.Toast; - -import com.android.camera.Util; -import com.android.gallery3d.R; -import com.android.gallery3d.common.ApiHelper; -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.exif.ExifData; -import com.android.gallery3d.exif.ExifOutputStream; -import com.android.gallery3d.exif.ExifReader; -import com.android.gallery3d.exif.ExifTag; -import com.android.gallery3d.picasasource.PicasaSource; -import com.android.gallery3d.ui.BitmapScreenNail; -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.BucketNames; -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 java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteOrder; -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * 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 MSG_SHOW_SAVE_ERROR = 4; - private static final int MSG_CANCEL_DIALOG = 5; - - private static final int MAX_BACKUP_IMAGE_SIZE = 320; - private static final int DEFAULT_COMPRESS_QUALITY = 90; - private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss"; - - 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"; - public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked"; - - 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 File DOWNLOAD_BUCKET = new File( - Environment.getExternalStorageDirectory(), BucketNames.DOWNLOAD); - - 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 BitmapScreenNail mBitmapScreenNail; - - 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); - - ActionBar actionBar = getActionBar(); - int displayOptions = ActionBar.DISPLAY_HOME_AS_UP - | ActionBar.DISPLAY_SHOW_TITLE; - actionBar.setDisplayOptions(displayOptions, displayOptions); - - Bundle extra = getIntent().getExtras(); - if (extra != null) { - if (extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) { - actionBar.setTitle(getString(R.string.set_wallpaper)); - } - if (extra.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - } - } - - mMainHandler = new SynchronizedHandler(getGLRoot()) { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MSG_LARGE_BITMAP: { - dismissProgressDialogIfShown(); - onBitmapRegionDecoderAvailable((BitmapRegionDecoder) message.obj); - break; - } - case MSG_BITMAP: { - dismissProgressDialogIfShown(); - onBitmapAvailable((Bitmap) message.obj); - break; - } - case MSG_SHOW_SAVE_ERROR: { - dismissProgressDialogIfShown(); - setResult(RESULT_CANCELED); - Toast.makeText(CropImage.this, - CropImage.this.getString(R.string.save_error), - Toast.LENGTH_LONG).show(); - finish(); - } - case MSG_SAVE_COMPLETE: { - dismissProgressDialogIfShown(); - setResult(RESULT_OK, (Intent) message.obj); - finish(); - break; - } - case MSG_CANCEL_DIALOG: { - setResult(RESULT_CANCELED); - 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 android.R.id.home: { - finish(); - break; - } - case R.id.cancel: { - setResult(RESULT_CANCELED); - finish(); - break; - } - case R.id.save: { - onSaveClicked(); - break; - } - } - return true; - } - - @Override - public void onBackPressed() { - finish(); - } - - private class SaveOutput implements Job<Intent> { - private final RectF mCropRect; - - public SaveOutput(RectF cropRect) { - mCropRect = cropRect; - } - - @Override - 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, ExifData exifData) { - // 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 { - if (exifData != null) { - ExifOutputStream eos = new ExifOutputStream(fos); - eos.setExifData(exifData); - saveBitmapToOutputStream(jc, cropped, - convertExtensionToCompressFormat(fileExtension), eos); - } else { - 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 ExifData getExifData(String path) { - FileInputStream is = null; - try { - is = new FileInputStream(path); - ExifReader reader = new ExifReader(); - ExifData data = reader.read(is); - return data; - } catch (Throwable t) { - Log.w(TAG, "Cannot read EXIF data", t); - return null; - } finally { - Util.closeSilently(is); - } - } - - private static final String EXIF_SOFTWARE_VALUE = "Android Gallery"; - - private void changeExifData(ExifData data, int width, int height) { - data.addTag(ExifTag.TAG_IMAGE_WIDTH).setValue(width); - data.addTag(ExifTag.TAG_IMAGE_LENGTH).setValue(height); - data.addTag(ExifTag.TAG_SOFTWARE).setValue(EXIF_SOFTWARE_VALUE); - data.addTag(ExifTag.TAG_DATE_TIME).setTimeValue(System.currentTimeMillis()); - // Remove the original thumbnail - // TODO: generate a new thumbnail for the cropped image. - data.removeThumbnailData(); - } - - 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 { - return saveGenericImage(jc, cropped); - } - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private static void setImageSize(ContentValues values, int width, int height) { - // The two fields are available since ICS but got published in JB - if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) { - values.put(Images.Media.WIDTH, width); - values.put(Images.Media.HEIGHT, height); - } - } - - 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); - ExifData exifData = new ExifData(ByteOrder.BIG_ENDIAN); - PicasaSource.extractExifValues(mMediaItem, exifData); - changeExifData(exifData, cropped.getWidth(), cropped.getHeight()); - File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, exifData); - 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, getOutputMimeType()); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, output.getAbsolutePath()); - values.put(Images.Media.SIZE, output.length()); - setImageSize(values, cropped.getWidth(), cropped.getHeight()); - - 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 = null; - - ExifData exifData = null; - if (convertExtensionToCompressFormat(getFileExtension()) == CompressFormat.JPEG) { - exifData = getExifData(oldPath.getAbsolutePath()); - if (exifData != null) { - changeExifData(exifData, cropped.getWidth(), cropped.getHeight()); - } - } - output = saveMedia(jc, cropped, directory, filename, exifData); - 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, getOutputMimeType()); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, output.getAbsolutePath()); - values.put(Images.Media.SIZE, output.length()); - - setImageSize(values, cropped.getWidth(), cropped.getHeight()); - - 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 Uri saveGenericImage(JobContext jc, Bitmap cropped) { - if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) { - throw new RuntimeException("cannot create download folder"); - } - - long now = System.currentTimeMillis(); - String filename = new SimpleDateFormat(TIME_STAMP_NAME). - format(new Date(now)); - - File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, null); - if (output == null) return null; - - ContentValues values = new ContentValues(); - values.put(Images.Media.TITLE, filename); - values.put(Images.Media.DISPLAY_NAME, output.getName()); - values.put(Images.Media.DATE_TAKEN, now); - values.put(Images.Media.DATE_MODIFIED, now / 1000); - values.put(Images.Media.DATE_ADDED, now / 1000); - values.put(Images.Media.MIME_TYPE, getOutputMimeType()); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, output.getAbsolutePath()); - values.put(Images.Media.SIZE, output.length()); - - setImageSize(values, cropped.getWidth(), cropped.getHeight()); - - 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() { - @Override - public void onCancel() { - ios.interrupt(); - } - }); - try { - bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, ios); - return !jc.isCancelled(); - } finally { - jc.setCancelListener(null); - Utils.closeSilently(ios); - } - } - - private boolean saveBitmapToUri(JobContext jc, Bitmap bitmap, Uri uri) { - try { - OutputStream out = getContentResolver().openOutputStream(uri); - try { - return saveBitmapToOutputStream(jc, bitmap, - convertExtensionToCompressFormat(getFileExtension()), out); - } finally { - Utils.closeSilently(out); - } - } 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 getOutputMimeType() { - return getFileExtension().equals("png") ? "image/png" : "image/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>() { - @Override - public void onFutureDone(Future<Intent> future) { - mSaveTask = null; - if (future.isCancelled()) return; - Intent intent = future.get(); - if (intent != null) { - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_SAVE_COMPLETE, intent)); - } else { - mMainHandler.sendEmptyMessage(MSG_SHOW_SAVE_ERROR); - } - } - }); - } - - 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 = FloatMath.sqrt((float) 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; - } - - if (mUseRegionDecoder) { - int rotation = mMediaItem.getFullImageRotation(); - rotateRectangle(rect, mCropView.getImageWidth(), - mCropView.getImageHeight(), 360 - rotation); - rotateRectangle(dest, outputX, outputY, 360 - rotation); - - BitmapFactory.Options options = new BitmapFactory.Options(); - int sample = BitmapUtils.computeSampleSizeLarger( - Math.max(scaleX, scaleY)); - options.inSampleSize = sample; - - // The decoding result is what we want if - // 1. The size of the decoded bitmap match the destination's size - // 2. The destination covers the whole output bitmap - // 3. No rotation - if ((rect.width() / sample) == dest.width() - && (rect.height() / sample) == dest.height() - && (outputX == dest.width()) && (outputY == 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 { - int rotation = mMediaItem.getRotation(); - rotateRectangle(rect, mCropView.getImageWidth(), - mCropView.getImageHeight(), 360 - rotation); - rotateRectangle(dest, outputX, outputY, 360 - rotation); - 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, R.string.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); - - mBitmapScreenNail = new BitmapScreenNail(mBitmap); - - TileImageViewAdapter adapter = new TileImageViewAdapter(); - adapter.setScreenNail(mBitmapScreenNail, width, height); - adapter.setRegionDecoder(regionDecoder); - - mCropView.setDataModel(adapter, mMediaItem.getFullImageRotation()); - if (mDoFaceDetection) { - mCropView.detectFaces(mBitmap); - } else { - mCropView.initializeHighlightRectangle(); - } - } - - private void onBitmapAvailable(Bitmap bitmap) { - if (bitmap == null) { - Toast.makeText(this, R.string.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, true); - mProgressDialog.setCanceledOnTouchOutside(false); - mProgressDialog.setCancelMessage(mMainHandler.obtainMessage(MSG_CANCEL_DIALOG)); - - 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>() { - @Override - 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>() { - @Override - 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(); - dismissProgressDialogIfShown(); - - Future<BitmapRegionDecoder> loadTask = mLoadTask; - if (loadTask != null && !loadTask.isDone()) { - // load in progress, try to cancel it - loadTask.cancel(); - loadTask.waitDone(); - } - - Future<Bitmap> loadBitmapTask = mLoadBitmapTask; - if (loadBitmapTask != null && !loadBitmapTask.isDone()) { - // load in progress, try to cancel it - loadBitmapTask.cancel(); - loadBitmapTask.waitDone(); - } - - Future<Intent> saveTask = mSaveTask; - if (saveTask != null && !saveTask.isDone()) { - // save in progress, try to cancel it - saveTask.cancel(); - saveTask.waitDone(); - } - GLRoot root = getGLRoot(); - root.lockRenderThread(); - try { - mCropView.pause(); - } finally { - root.unlockRenderThread(); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mBitmapScreenNail != null) { - mBitmapScreenNail.recycle(); - mBitmapScreenNail = null; - } - } - - private void dismissProgressDialogIfShown() { - if (mProgressDialog != null) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } - } - - private MediaItem getMediaItemFromIntentData() { - Uri uri = getIntent().getData(); - DataManager manager = getDataManager(); - Path path = manager.findPathByUri(uri, getIntent().getType()); - if (path == null) { - Log.w(TAG, "cannot get path for: " + uri + ", or no data given"); - return null; - } - return (MediaItem) manager.getMediaObject(path); - } - - private class LoadDataTask implements Job<BitmapRegionDecoder> { - MediaItem mItem; - - public LoadDataTask(MediaItem item) { - mItem = item; - } - - @Override - 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; - } - @Override - public Bitmap run(JobContext jc) { - return mItem == null - ? null - : mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc); - } - } -} diff --git a/src/com/android/gallery3d/app/FilmstripPage.java b/src/com/android/gallery3d/app/FilmstripPage.java new file mode 100644 index 000000000..a9726cdc9 --- /dev/null +++ b/src/com/android/gallery3d/app/FilmstripPage.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2012 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; + +public class FilmstripPage extends PhotoPage { + +} diff --git a/src/com/android/gallery3d/app/Gallery.java b/src/com/android/gallery3d/app/Gallery.java index e28404fac..354e325d4 100644 --- a/src/com/android/gallery3d/app/Gallery.java +++ b/src/com/android/gallery3d/app/Gallery.java @@ -26,10 +26,9 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.OpenableColumns; -import android.view.Window; -import android.view.WindowManager; import android.widget.Toast; +import com.actionbarsherlock.view.Window; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; @@ -49,7 +48,6 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi public static final String KEY_GET_ALBUM = "get-album"; public static final String KEY_TYPE_BITS = "type-bits"; public static final String KEY_MEDIA_TYPES = "mediaTypes"; - public static final String KEY_DISMISS_KEYGUARD = "dismiss-keyguard"; private static final String TAG = "Gallery"; private Dialog mVersionCheckDialog; @@ -60,11 +58,6 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi requestWindowFeature(Window.FEATURE_ACTION_BAR); requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - if (getIntent().getBooleanExtra(KEY_DISMISS_KEYGUARD, false)) { - getWindow().addFlags( - WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); - } - setContentView(R.layout.main); if (savedInstanceState != null) { @@ -222,7 +215,7 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi } } - getStateManager().startState(PhotoPage.class, data); + getStateManager().startState(SinglePhotoPage.class, data); } } } diff --git a/src/com/android/gallery3d/app/GalleryActionBar.java b/src/com/android/gallery3d/app/GalleryActionBar.java index 0fb5e51b4..49f4186e8 100644 --- a/src/com/android/gallery3d/app/GalleryActionBar.java +++ b/src/com/android/gallery3d/app/GalleryActionBar.java @@ -17,9 +17,6 @@ package com.android.gallery3d.app; import android.annotation.TargetApi; -import android.app.ActionBar; -import android.app.ActionBar.OnMenuVisibilityListener; -import android.app.ActionBar.OnNavigationListener; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; @@ -27,15 +24,18 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; -import android.widget.ShareActionProvider; import android.widget.TextView; import android.widget.TwoLineListItem; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.ActionBar.OnMenuVisibilityListener; +import com.actionbarsherlock.app.ActionBar.OnNavigationListener; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.widget.ShareActionProvider; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; @@ -186,7 +186,7 @@ public class GalleryActionBar implements OnNavigationListener { } public GalleryActionBar(AbstractGalleryActivity activity) { - mActionBar = activity.getActionBar(); + mActionBar = activity.getSupportActionBar(); mContext = activity.getAndroidContext(); mActivity = activity; mInflater = ((Activity) mActivity).getLayoutInflater(); @@ -396,7 +396,7 @@ public class GalleryActionBar implements OnNavigationListener { private Intent mShareIntent; public void createActionBarMenu(int menuRes, Menu menu) { - mActivity.getMenuInflater().inflate(menuRes, menu); + mActivity.getSupportMenuInflater().inflate(menuRes, menu); mActionBarMenu = menu; MenuItem item = menu.findItem(R.id.action_share_panorama); diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java index 3123644c7..d725e6d12 100644 --- a/src/com/android/gallery3d/app/MovieActivity.java +++ b/src/com/android/gallery3d/app/MovieActivity.java @@ -17,8 +17,6 @@ package com.android.gallery3d.app; import android.annotation.TargetApi; -import android.app.ActionBar; -import android.app.Activity; import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.Intent; @@ -33,13 +31,15 @@ import android.os.Bundle; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.WindowManager; -import android.widget.ShareActionProvider; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.widget.ShareActionProvider; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.Utils; @@ -51,7 +51,7 @@ import com.android.gallery3d.common.Utils; * to set the action bar logo so the playback process looks more seamlessly integrated with * the original activity. */ -public class MovieActivity extends Activity { +public class MovieActivity extends SherlockActivity { @SuppressWarnings("unused") private static final String TAG = "MovieActivity"; public static final String KEY_LOGO_BITMAP = "logo-bitmap"; @@ -75,8 +75,8 @@ public class MovieActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_ACTION_BAR); - requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); + getSherlock().requestFeature(Window.FEATURE_ACTION_BAR); + getSherlock().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); setContentView(R.layout.movie_view); View rootView = findViewById(R.id.movie_view_root); @@ -119,14 +119,14 @@ public class MovieActivity extends Activity { private void setActionBarLogoFromIntent(Intent intent) { Bitmap logo = intent.getParcelableExtra(KEY_LOGO_BITMAP); if (logo != null) { - getActionBar().setLogo( + getSupportActionBar().setLogo( new BitmapDrawable(getResources(), logo)); } } private void initializeActionBar(Intent intent) { mUri = intent.getData(); - final ActionBar actionBar = getActionBar(); + final ActionBar actionBar = getSupportActionBar(); setActionBarLogoFromIntent(intent); actionBar.setDisplayOptions( ActionBar.DISPLAY_HOME_AS_UP, @@ -166,7 +166,7 @@ public class MovieActivity extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.movie, menu); + getSupportMenuInflater().inflate(R.menu.movie, menu); // Document says EXTRA_STREAM should be a content: Uri // So, we only share the video if it's "content:". diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java index 85dc4427e..00e4cd63b 100644 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -74,8 +74,8 @@ public class MoviePlayer implements private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins private Context mContext; - private final View mRootView; private final VideoView mVideoView; + private final View mRootView; private final Bookmarker mBookmarker; private final Uri mUri; private final Handler mHandler = new Handler(); @@ -191,7 +191,6 @@ public class MoviePlayer implements if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { mController.show(); - mRootView.setBackgroundColor(Color.BLACK); } } }); diff --git a/src/com/android/gallery3d/app/MuteVideo.java b/src/com/android/gallery3d/app/MuteVideo.java new file mode 100644 index 000000000..012b682ef --- /dev/null +++ b/src/com/android/gallery3d/app/MuteVideo.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 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 android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.provider.MediaStore; +import android.widget.Toast; + +import com.android.gallery3d.R; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.util.SaveVideoFileInfo; +import com.android.gallery3d.util.SaveVideoFileUtils; + +import java.io.IOException; + +public class MuteVideo { + + private ProgressDialog mMuteProgress; + + private MediaItem mCurrentItem = null; + private Uri mUri = null; + private SaveVideoFileInfo mDstFileInfo = null; + private Activity mActivity = null; + private final Handler mHandler = new Handler(); + + final String TIME_STAMP_NAME = "'MUTE'_yyyyMMdd_HHmmss"; + + public MuteVideo(MediaItem current, Uri uri, Activity activity) { + mUri = uri; + mCurrentItem = current; + mActivity = activity; + } + + public void muteInBackground() { + mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME, + mActivity.getContentResolver(), mUri, + mActivity.getString(R.string.folder_download)); + + showProgressDialog(); + new Thread(new Runnable() { + @Override + public void run() { + try { + VideoUtils.startMute(mCurrentItem.getFilePath(), mDstFileInfo); + SaveVideoFileUtils.insertContent( + mDstFileInfo, mActivity.getContentResolver(), mUri); + } catch (IOException e) { + Toast.makeText(mActivity, mActivity.getString(R.string.video_mute_err), + Toast.LENGTH_SHORT).show(); + } + // After muting is done, trigger the UI changed. + mHandler.post(new Runnable() { + @Override + public void run() { + Toast.makeText(mActivity.getApplicationContext(), + mActivity.getString(R.string.save_into, + mDstFileInfo.mFolderName), + Toast.LENGTH_SHORT) + .show(); + + if (mMuteProgress != null) { + mMuteProgress.dismiss(); + mMuteProgress = null; + + // Show the result only when the activity not + // stopped. + Intent intent = new Intent(android.content.Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*"); + intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false); + mActivity.startActivity(intent); + } + } + }); + } + }).start(); + } + + private void showProgressDialog() { + mMuteProgress = new ProgressDialog(mActivity); + mMuteProgress.setTitle(mActivity.getString(R.string.muting)); + mMuteProgress.setMessage(mActivity.getString(R.string.please_wait)); + mMuteProgress.setCancelable(false); + mMuteProgress.setCanceledOnTouchOutside(false); + mMuteProgress.show(); + } +} diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index 506d1ca6f..3c0c4330b 100644 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -17,7 +17,6 @@ package com.android.gallery3d.app; import android.annotation.TargetApi; -import android.app.ActionBar.OnMenuVisibilityListener; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; @@ -32,13 +31,15 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; -import android.view.Menu; -import android.view.MenuItem; import android.widget.RelativeLayout; import android.widget.Toast; import com.android.camera.CameraActivity; import com.android.camera.ProxyLauncher; + +import com.actionbarsherlock.app.ActionBar.OnMenuVisibilityListener; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.data.ComboAlbum; @@ -71,7 +72,7 @@ import com.android.gallery3d.ui.SelectionManager; import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.GalleryUtils; -public class PhotoPage extends ActivityState implements +public abstract class PhotoPage extends ActivityState implements PhotoView.Listener, AppBridge.Server, PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener { private static final String TAG = "PhotoPage"; @@ -1019,11 +1020,16 @@ public class PhotoPage extends ActivityState implements refreshHidingMessage(); MediaItem current = mModel.getMediaItem(0); + // This is a shield for monkey when it clicks the action bar + // menu when transitioning from filmstrip to camera + if (current instanceof SnailItem) return true; + // TODO: We should check the current photo against the MediaItem + // that the menu was initially created for. We need to fix this + // after PhotoPage being refactored. if (current == null) { // item is not ready, ignore return true; } - int currentIndex = mModel.getCurrentIndex(); Path path = current.getPath(); @@ -1064,6 +1070,12 @@ public class PhotoPage extends ActivityState implements mActivity.startActivityForResult(intent, REQUEST_TRIM); return true; } + case R.id.action_mute: { + MuteVideo muteVideo = new MuteVideo(current, + manager.getContentUri(path), mActivity); + muteVideo.muteInBackground(); + return true; + } case R.id.action_edit: { launchPhotoEditor(); return true; @@ -1156,9 +1168,7 @@ public class PhotoPage extends ActivityState implements } else if (goBack) { onBackPressed(); } else if (unlock) { - Intent intent = new Intent(mActivity, Gallery.class); - intent.putExtra(Gallery.KEY_DISMISS_KEYGUARD, true); - mActivity.startActivity(intent); + mActivity.getStateManager().finishState(this); } else if (launchCamera) { launchCamera(); } else { @@ -1242,7 +1252,7 @@ public class PhotoPage extends ActivityState implements Bundle data = new Bundle(getData()); data.putString(KEY_MEDIA_SET_PATH, albumPath.toString()); data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString()); - mActivity.getStateManager().startState(PhotoPage.class, data); + mActivity.getStateManager().startState(SinglePhotoPage.class, data); return; } mModel.setCurrentPhoto(path, mCurrentIndex); diff --git a/src/com/android/gallery3d/app/PickerActivity.java b/src/com/android/gallery3d/app/PickerActivity.java index d5bb218ea..1eb95d0c6 100644 --- a/src/com/android/gallery3d/app/PickerActivity.java +++ b/src/com/android/gallery3d/app/PickerActivity.java @@ -17,13 +17,13 @@ package com.android.gallery3d.app; import android.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.view.Window; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.Window; import com.android.gallery3d.R; import com.android.gallery3d.ui.GLRootView; @@ -62,7 +62,7 @@ public class PickerActivity extends AbstractGalleryActivity @Override public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); + MenuInflater inflater = getSupportMenuInflater(); inflater.inflate(R.menu.pickup, menu); return true; } diff --git a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java index 00f2fe78f..f0848ad22 100644 --- a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java +++ b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java @@ -34,6 +34,7 @@ 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.LightCycleHelper; import com.android.gallery3d.util.ThreadPool; public class SinglePhotoDataAdapter extends TileImageViewAdapter diff --git a/src/com/android/gallery3d/app/SinglePhotoPage.java b/src/com/android/gallery3d/app/SinglePhotoPage.java new file mode 100644 index 000000000..beb87d358 --- /dev/null +++ b/src/com/android/gallery3d/app/SinglePhotoPage.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2012 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; + +public class SinglePhotoPage extends PhotoPage { + +} diff --git a/src/com/android/gallery3d/app/StateManager.java b/src/com/android/gallery3d/app/StateManager.java index d77279f78..64daa6afe 100644 --- a/src/com/android/gallery3d/app/StateManager.java +++ b/src/com/android/gallery3d/app/StateManager.java @@ -21,10 +21,10 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.Parcelable; -import android.view.Menu; -import android.view.MenuItem; import com.android.gallery3d.anim.StateTransitionAnimation; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.common.Utils; import java.util.Stack; diff --git a/src/com/android/gallery3d/app/TrimControllerOverlay.java b/src/com/android/gallery3d/app/TrimControllerOverlay.java index 9127ad159..cae016626 100644 --- a/src/com/android/gallery3d/app/TrimControllerOverlay.java +++ b/src/com/android/gallery3d/app/TrimControllerOverlay.java @@ -23,6 +23,8 @@ import android.content.Context; import android.view.MotionEvent; import android.view.View; +import com.android.gallery3d.common.ApiHelper; + /** * The controller for the Trimming Video. */ @@ -41,36 +43,41 @@ public class TrimControllerOverlay extends CommonControllerOverlay { if (mState == State.PLAYING) { mPlayPauseReplayView.setVisibility(View.INVISIBLE); } - mPlayPauseReplayView.setAlpha(1f); + if (ApiHelper.HAS_OBJECT_ANIMATION) { + mPlayPauseReplayView.setAlpha(1f); + } } @Override public void showPlaying() { super.showPlaying(); + if (ApiHelper.HAS_OBJECT_ANIMATION) { + // Add animation to hide the play button while playing. + ObjectAnimator anim = ObjectAnimator.ofFloat(mPlayPauseReplayView, "alpha", 1f, 0f); + anim.setDuration(200); + anim.start(); + anim.addListener(new AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } - // Add animation to hide the play button while playing. - ObjectAnimator anim = ObjectAnimator.ofFloat(mPlayPauseReplayView, "alpha", 1f, 0f); - anim.setDuration(200); - anim.start(); - anim.addListener(new AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - hidePlayButtonIfPlaying(); - } + @Override + public void onAnimationEnd(Animator animation) { + hidePlayButtonIfPlaying(); + } - @Override - public void onAnimationCancel(Animator animation) { - hidePlayButtonIfPlaying(); - } + @Override + public void onAnimationCancel(Animator animation) { + hidePlayButtonIfPlaying(); + } - @Override - public void onAnimationRepeat(Animator animation) { - } - }); + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } else { + hidePlayButtonIfPlaying(); + } } @Override diff --git a/src/com/android/gallery3d/app/TrimVideo.java b/src/com/android/gallery3d/app/TrimVideo.java index 38b403b10..7a76be5dc 100644 --- a/src/com/android/gallery3d/app/TrimVideo.java +++ b/src/com/android/gallery3d/app/TrimVideo.java @@ -16,8 +16,6 @@ package com.android.gallery3d.app; -import android.app.ActionBar; -import android.app.Activity; import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.ContentValues; @@ -39,20 +37,25 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.VideoView; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockActivity; import com.android.gallery3d.R; import com.android.gallery3d.util.BucketNames; +import com.android.gallery3d.util.SaveVideoFileInfo; +import com.android.gallery3d.util.SaveVideoFileUtils; import java.io.File; import java.io.IOException; import java.sql.Date; import java.text.SimpleDateFormat; -public class TrimVideo extends Activity implements +public class TrimVideo extends SherlockActivity implements MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, ControllerOverlay.Listener { private VideoView mVideoView; + private TextView mSaveVideoTextView; private TrimControllerOverlay mController; private Context mContext; private Uri mUri; @@ -70,13 +73,8 @@ public class TrimVideo extends Activity implements private boolean mHasPaused = false; private String mSrcVideoPath = null; - private String mSaveFileName = null; private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss"; - private File mSrcFile = null; - private File mDstFile = null; - private File mSaveDirectory = null; - // For showing the result. - private String saveFolderName = null; + private SaveVideoFileInfo mDstFileInfo = null; @Override public void onCreate(Bundle savedInstanceState) { @@ -86,20 +84,21 @@ public class TrimVideo extends Activity implements requestWindowFeature(Window.FEATURE_ACTION_BAR); requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - ActionBar actionBar = getActionBar(); + ActionBar actionBar = getSupportActionBar(); int displayOptions = ActionBar.DISPLAY_SHOW_HOME; actionBar.setDisplayOptions(0, displayOptions); displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM; actionBar.setDisplayOptions(displayOptions, displayOptions); actionBar.setCustomView(R.layout.trim_menu); - TextView mSaveVideoTextView = (TextView) findViewById(R.id.start_trim); + mSaveVideoTextView = (TextView) findViewById(R.id.start_trim); mSaveVideoTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { trimVideo(); } }); + mSaveVideoTextView.setEnabled(false); Intent intent = getIntent(); mUri = intent.getData(); @@ -221,72 +220,24 @@ public class TrimVideo extends Activity implements mController.showPaused(); } - // Copy from SaveCopyTask.java in terms of how to handle the destination - // path and filename : querySource() and getSaveDirectory(). - private interface ContentResolverQueryCallback { - void onCursorResult(Cursor cursor); - } - - private void querySource(String[] projection, ContentResolverQueryCallback callback) { - ContentResolver contentResolver = getContentResolver(); - Cursor cursor = null; - try { - cursor = contentResolver.query(mUri, 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 File getSaveDirectory() { - final File[] dir = new File[1]; - querySource(new String[] { - VideoColumns.DATA }, new ContentResolverQueryCallback() { - @Override - public void onCursorResult(Cursor cursor) { - dir[0] = new File(cursor.getString(0)).getParentFile(); - } - }); - return dir[0]; - } - - private void trimVideo() { + private boolean isModified() { int delta = mTrimEndTime - mTrimStartTime; + // Considering that we only trim at sync frame, we don't want to trim // when the time interval is too short or too close to the origin. - if (delta < 100 ) { - Toast.makeText(getApplicationContext(), - getString(R.string.trim_too_short), - Toast.LENGTH_SHORT).show(); - return; - } - if (Math.abs(mVideoView.getDuration() - delta) < 100) { - // If no change has been made, go back - onBackPressed(); - return; - } - // Use the default save directory if the source directory cannot be - // saved. - mSaveDirectory = getSaveDirectory(); - if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) { - mSaveDirectory = new File(Environment.getExternalStorageDirectory(), - BucketNames.DOWNLOAD); - saveFolderName = getString(R.string.folder_download); + if (delta < 100 || Math.abs(mVideoView.getDuration() - delta) < 100) { + return false; } else { - saveFolderName = mSaveDirectory.getName(); + return true; } - mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format( - new Date(System.currentTimeMillis())); + } - mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4"); - mSrcFile = new File(mSrcVideoPath); + private void trimVideo() { + + mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME, + getContentResolver(), mUri, getString(R.string.folder_download)); + final File mSrcFile = new File(mSrcVideoPath); showProgressDialog(); @@ -294,9 +245,11 @@ public class TrimVideo extends Activity implements @Override public void run() { try { - TrimVideoUtils.startTrim(mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime); + VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile, + mTrimStartTime, mTrimEndTime, mVideoView.getDuration()); // Update the database for adding a new video file. - insertContent(mDstFile); + SaveVideoFileUtils.insertContent(mDstFileInfo, + getContentResolver(), mUri); } catch (IOException e) { e.printStackTrace(); } @@ -305,7 +258,7 @@ public class TrimVideo extends Activity implements @Override public void run() { Toast.makeText(getApplicationContext(), - getString(R.string.save_into) + " " + saveFolderName, + getString(R.string.save_into, mDstFileInfo.mFolderName), Toast.LENGTH_SHORT) .show(); // TODO: change trimming into a service to avoid @@ -315,7 +268,7 @@ public class TrimVideo extends Activity implements mProgress = null; // Show the result only when the activity not stopped. Intent intent = new Intent(android.content.Intent.ACTION_VIEW); - intent.setDataAndTypeAndNormalize(Uri.fromFile(mDstFile), "video/*"); + intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*"); intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false); startActivity(intent); finish(); @@ -338,53 +291,6 @@ public class TrimVideo extends Activity implements mProgress.show(); } - /** - * Insert the content (saved file) with proper video properties. - */ - private Uri insertContent(File file) { - long nowInMs = System.currentTimeMillis(); - long nowInSec = nowInMs / 1000; - final ContentValues values = new ContentValues(12); - values.put(Video.Media.TITLE, mSaveFileName); - values.put(Video.Media.DISPLAY_NAME, file.getName()); - values.put(Video.Media.MIME_TYPE, "video/mp4"); - values.put(Video.Media.DATE_TAKEN, nowInMs); - values.put(Video.Media.DATE_MODIFIED, nowInSec); - values.put(Video.Media.DATE_ADDED, nowInSec); - values.put(Video.Media.DATA, file.getAbsolutePath()); - values.put(Video.Media.SIZE, file.length()); - // Copy the data taken and location info from src. - String[] projection = new String[] { - VideoColumns.DATE_TAKEN, - VideoColumns.LATITUDE, - VideoColumns.LONGITUDE, - VideoColumns.RESOLUTION, - }; - - // Copy some info from the source file. - querySource(projection, new ContentResolverQueryCallback() { - @Override - public void onCursorResult(Cursor cursor) { - long timeTaken = cursor.getLong(0); - if (timeTaken > 0) { - values.put(Video.Media.DATE_TAKEN, timeTaken); - } - 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(Video.Media.LATITUDE, latitude); - values.put(Video.Media.LONGITUDE, longitude); - } - values.put(Video.Media.RESOLUTION, cursor.getString(3)); - - } - }); - - return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values); - } - @Override public void onPlayPause() { if (mVideoView.isPlaying()) { @@ -410,6 +316,8 @@ public class TrimVideo extends Activity implements mTrimStartTime = start; mTrimEndTime = end; setProgress(); + // Enable save if there's modifications + mSaveVideoTextView.setEnabled(isModified()); } @Override diff --git a/src/com/android/gallery3d/app/TrimVideoUtils.java b/src/com/android/gallery3d/app/VideoUtils.java index ae9b1e9ce..8ffc3d5eb 100644 --- a/src/com/android/gallery3d/app/TrimVideoUtils.java +++ b/src/com/android/gallery3d/app/VideoUtils.java @@ -19,6 +19,7 @@ package com.android.gallery3d.app; +import com.android.gallery3d.util.SaveVideoFileInfo; import com.coremedia.iso.IsoFile; import com.coremedia.iso.boxes.TimeToSampleBox; import com.googlecode.mp4parser.authoring.Movie; @@ -36,12 +37,46 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -/** - * Shortens/Crops a track - */ -public class TrimVideoUtils { +public class VideoUtils { - public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException { + public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo) throws IOException { + File dst = dstFileInfo.mFile; + File src = new File(filePath); + RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r"); + Movie movie = MovieCreator.build(randomAccessFile.getChannel()); + + // remove all tracks we will create new tracks from the old + List<Track> tracks = movie.getTracks(); + movie.setTracks(new LinkedList<Track>()); + + for (Track track : tracks) { + if (track.getHandler().equals("vide")) { + movie.addTrack(track); + } + } + writeMovieIntoFile(dst, movie); + randomAccessFile.close(); + } + + private static void writeMovieIntoFile(File dst, Movie movie) + throws IOException { + if (!dst.exists()) { + dst.createNewFile(); + } + + IsoFile out = new DefaultMp4Builder().build(movie); + FileOutputStream fos = new FileOutputStream(dst); + FileChannel fc = fos.getChannel(); + out.getBox(fc); // This one build up the memory. + + fc.close(); + fos.close(); + } + + /** + * Shortens/Crops a track + */ + public static void startTrim(File src, File dst, int startMs, int endMs, int totalMs) throws IOException { RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r"); Movie movie = MovieCreator.build(randomAccessFile.getChannel()); @@ -100,18 +135,7 @@ public class TrimVideoUtils { } movie.addTrack(new CroppedTrack(track, startSample, endSample)); } - IsoFile out = new DefaultMp4Builder().build(movie); - - if (!dst.exists()) { - dst.createNewFile(); - } - - FileOutputStream fos = new FileOutputStream(dst); - FileChannel fc = fos.getChannel(); - out.getBox(fc); // This one build up the memory. - - fc.close(); - fos.close(); + writeMovieIntoFile(dst, movie); randomAccessFile.close(); } diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java index 996d3f080..1bbe8d2c6 100644 --- a/src/com/android/gallery3d/app/Wallpaper.java +++ b/src/com/android/gallery3d/app/Wallpaper.java @@ -26,6 +26,8 @@ import android.os.Bundle; import android.view.Display; import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.CropExtras; /** * Wallpaper picker for the gallery application. This just redirects to the @@ -98,19 +100,18 @@ public class Wallpaper extends Activity { Point size = getDefaultDisplaySize(new Point()); float spotlightX = (float) size.x / width; float spotlightY = (float) size.y / height; - Intent request = new Intent(CropImage.ACTION_CROP) + Intent request = new Intent(FilterShowActivity.CROP_ACTION) .setDataAndType(mPickedItem, IMAGE_TYPE) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) - .putExtra(CropImage.KEY_OUTPUT_X, width) - .putExtra(CropImage.KEY_OUTPUT_Y, height) - .putExtra(CropImage.KEY_ASPECT_X, width) - .putExtra(CropImage.KEY_ASPECT_Y, height) - .putExtra(CropImage.KEY_SPOTLIGHT_X, spotlightX) - .putExtra(CropImage.KEY_SPOTLIGHT_Y, spotlightY) - .putExtra(CropImage.KEY_SCALE, true) - .putExtra(CropImage.KEY_SCALE_UP_IF_NEEDED, true) - .putExtra(CropImage.KEY_NO_FACE_DETECTION, true) - .putExtra(CropImage.KEY_SET_AS_WALLPAPER, true); + .putExtra(CropExtras.KEY_OUTPUT_X, width) + .putExtra(CropExtras.KEY_OUTPUT_Y, height) + .putExtra(CropExtras.KEY_ASPECT_X, width) + .putExtra(CropExtras.KEY_ASPECT_Y, height) + .putExtra(CropExtras.KEY_SPOTLIGHT_X, spotlightX) + .putExtra(CropExtras.KEY_SPOTLIGHT_Y, spotlightY) + .putExtra(CropExtras.KEY_SCALE, true) + .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true) + .putExtra(CropExtras.KEY_SET_AS_WALLPAPER, true); startActivity(request); finish(); } diff --git a/src/com/android/gallery3d/data/Exif.java b/src/com/android/gallery3d/data/Exif.java index ba5862a86..30aba7e97 100644 --- a/src/com/android/gallery3d/data/Exif.java +++ b/src/com/android/gallery3d/data/Exif.java @@ -18,144 +18,55 @@ package com.android.gallery3d.data; import android.util.Log; +import com.android.gallery3d.exif.ExifInvalidFormatException; +import com.android.gallery3d.exif.ExifParser; +import com.android.gallery3d.exif.ExifTag; + import java.io.IOException; import java.io.InputStream; public class Exif { - private static final String TAG = "CameraExif"; + private static final String TAG = "GalleryExif"; public static int getOrientation(InputStream is) { if (is == null) { return 0; } - byte[] buf = new byte[8]; - int length = 0; - - // ISO/IEC 10918-1:1993(E) - while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) { - int marker = buf[1] & 0xFF; - - // Check if the marker is a padding. - if (marker == 0xFF) { - continue; - } - - // Check if the marker is SOI or TEM. - if (marker == 0xD8 || marker == 0x01) { - continue; - } - // Check if the marker is EOI or SOS. - if (marker == 0xD9 || marker == 0xDA) { - return 0; - } - - // Get the length and check if it is reasonable. - if (!read(is, buf, 2)) { - return 0; - } - length = pack(buf, 0, 2, false); - if (length < 2) { - Log.e(TAG, "Invalid length"); - return 0; - } - length -= 2; - - // Break if the marker is EXIF in APP1. - if (marker == 0xE1 && length >= 6) { - if (!read(is, buf, 6)) return 0; - length -= 6; - if (pack(buf, 0, 4, false) == 0x45786966 && - pack(buf, 4, 2, false) == 0) { - break; - } - } - - // Skip other markers. - try { - is.skip(length); - } catch (IOException ex) { - return 0; - } - length = 0; - } - - // JEITA CP-3451 Exif Version 2.2 - if (length > 8) { - int offset = 0; - byte[] jpeg = new byte[length]; - if (!read(is, jpeg, length)) { - return 0; - } - - // Identify the byte order. - int tag = pack(jpeg, offset, 4, false); - if (tag != 0x49492A00 && tag != 0x4D4D002A) { - Log.e(TAG, "Invalid byte order"); - return 0; - } - boolean littleEndian = (tag == 0x49492A00); - - // Get the offset and check if it is reasonable. - int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; - if (count < 10 || count > length) { - Log.e(TAG, "Invalid offset"); - return 0; - } - offset += count; - length -= count; - - // Get the count and go through all the elements. - count = pack(jpeg, offset - 2, 2, littleEndian); - while (count-- > 0 && length >= 12) { - // Get the tag and check if it is orientation. - tag = pack(jpeg, offset, 2, littleEndian); - if (tag == 0x0112) { - // We do not really care about type and count, do we? - int orientation = pack(jpeg, offset + 8, 2, littleEndian); - switch (orientation) { - case 1: - return 0; - case 3: - return 180; - case 6: - return 90; - case 8: - return 270; + try { + ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0); + int event = parser.next(); + while (event != ExifParser.EVENT_END) { + if (event == ExifParser.EVENT_NEW_TAG) { + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifTag.TAG_ORIENTATION && + tag.hasValue()) { + int orient = (int) tag.getValueAt(0); + switch (orient) { + case ExifTag.Orientation.TOP_LEFT: + return 0; + case ExifTag.Orientation.BOTTOM_LEFT: + return 180; + case ExifTag.Orientation.RIGHT_TOP: + return 90; + case ExifTag.Orientation.RIGHT_BOTTOM: + return 270; + default: + Log.i(TAG, "Unsupported orientation"); + return 0; + } } - Log.i(TAG, "Unsupported orientation"); - return 0; } - offset += 12; - length -= 12; + event = parser.next(); } - } - - Log.i(TAG, "Orientation not found"); - return 0; - } - - private static int pack(byte[] bytes, int offset, int length, - boolean littleEndian) { - int step = 1; - if (littleEndian) { - offset += length - 1; - step = -1; - } - - int value = 0; - while (length-- > 0) { - value = (value << 8) | (bytes[offset] & 0xFF); - offset += step; - } - return value; - } - - private static boolean read(InputStream is, byte[] buf, int length) { - try { - return is.read(buf, 0, length) == length; - } catch (IOException ex) { - return false; + Log.i(TAG, "Orientation not found"); + return 0; + } catch (IOException e) { + Log.w(TAG, "Failed to read EXIF orientation", e); + return 0; + } catch (ExifInvalidFormatException e) { + Log.w(TAG, "Failed to read EXIF orientation", e); + return 0; } } } diff --git a/src/com/android/gallery3d/data/LocalAlbum.java b/src/com/android/gallery3d/data/LocalAlbum.java index e05aac01b..6c5feb5c8 100644 --- a/src/com/android/gallery3d/data/LocalAlbum.java +++ b/src/com/android/gallery3d/data/LocalAlbum.java @@ -61,7 +61,7 @@ public class LocalAlbum extends MediaSet { mApplication = application; mResolver = application.getContentResolver(); mBucketId = bucketId; - mName = getLocalizedName(application.getResources(), bucketId, name); + mName = name; mIsImage = isImage; if (isImage) { @@ -245,7 +245,7 @@ public class LocalAlbum extends MediaSet { @Override public String getName() { - return mName; + return getLocalizedName(mApplication.getResources(), mBucketId, mName); } @Override diff --git a/src/com/android/gallery3d/data/LocalMergeAlbum.java b/src/com/android/gallery3d/data/LocalMergeAlbum.java index cbaf82fff..f0b5e5726 100644 --- a/src/com/android/gallery3d/data/LocalMergeAlbum.java +++ b/src/com/android/gallery3d/data/LocalMergeAlbum.java @@ -41,7 +41,6 @@ public class LocalMergeAlbum extends MediaSet implements ContentListener { private final Comparator<MediaItem> mComparator; private final MediaSet[] mSources; - private String mName; private FetchCache[] mFetcher; private int mSupportedOperation; private int mBucketId; @@ -54,7 +53,6 @@ public class LocalMergeAlbum extends MediaSet implements ContentListener { super(path, INVALID_DATA_VERSION); mComparator = comparator; mSources = sources; - mName = sources.length == 0 ? "" : sources[0].getName(); mBucketId = bucketId; for (MediaSet set : mSources) { set.addContentListener(this); @@ -82,7 +80,6 @@ public class LocalMergeAlbum extends MediaSet implements ContentListener { mSupportedOperation = supported; mIndex.clear(); mIndex.put(0, new int[mSources.length]); - mName = mSources.length == 0 ? "" : mSources[0].getName(); } private void invalidateCache() { @@ -111,7 +108,7 @@ public class LocalMergeAlbum extends MediaSet implements ContentListener { @Override public String getName() { - return mName; + return mSources.length == 0 ? "" : mSources[0].getName(); } @Override diff --git a/src/com/android/gallery3d/data/LocalVideo.java b/src/com/android/gallery3d/data/LocalVideo.java index 44b853901..b1e1cb3aa 100644 --- a/src/com/android/gallery3d/data/LocalVideo.java +++ b/src/com/android/gallery3d/data/LocalVideo.java @@ -180,7 +180,7 @@ public class LocalVideo extends LocalMediaItem { @Override public int getSupportedOperations() { - return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM; + return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM | SUPPORT_MUTE; } @Override diff --git a/src/com/android/gallery3d/data/MediaDetails.java b/src/com/android/gallery3d/data/MediaDetails.java index 298224729..16716dae4 100644 --- a/src/com/android/gallery3d/data/MediaDetails.java +++ b/src/com/android/gallery3d/data/MediaDetails.java @@ -19,9 +19,15 @@ package com.android.gallery3d.data; import android.media.ExifInterface; import com.android.gallery3d.R; -import com.android.gallery3d.common.ExifTags; +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.exif.ExifData; +import com.android.gallery3d.exif.ExifInvalidFormatException; +import com.android.gallery3d.exif.ExifReader; +import com.android.gallery3d.exif.ExifTag; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; @@ -105,10 +111,18 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> { return mUnits.get(index); } - private static void setExifData(MediaDetails details, ExifInterface exif, String tag, + private static void setExifData(MediaDetails details, ExifTag tag, int key) { - String value = exif.getAttribute(tag); - if (value != null) { + if (tag != null) { + String value = null; + int type = tag.getDataType(); + if (type == ExifTag.TYPE_UNSIGNED_RATIONAL || type == ExifTag.TYPE_RATIONAL) { + value = String.valueOf(tag.getRational(0).toDouble()); + } else if (type == ExifTag.TYPE_ASCII) { + value = tag.getString(); + } else { + value = String.valueOf(tag.getValueAt(0)); + } if (key == MediaDetails.INDEX_FLASH) { MediaDetails.FlashState state = new MediaDetails.FlashState( Integer.valueOf(value.toString())); @@ -120,29 +134,37 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> { } public static void extractExifInfo(MediaDetails details, String filePath) { + InputStream is = null; try { - ExifInterface exif = new ExifInterface(filePath); - setExifData(details, exif, ExifInterface.TAG_FLASH, MediaDetails.INDEX_FLASH); - setExifData(details, exif, ExifInterface.TAG_IMAGE_WIDTH, MediaDetails.INDEX_WIDTH); - setExifData(details, exif, ExifInterface.TAG_IMAGE_LENGTH, - MediaDetails.INDEX_HEIGHT); - setExifData(details, exif, ExifInterface.TAG_MAKE, MediaDetails.INDEX_MAKE); - setExifData(details, exif, ExifInterface.TAG_MODEL, MediaDetails.INDEX_MODEL); - setExifData(details, exif, ExifTags.TAG_APERTURE, MediaDetails.INDEX_APERTURE); - setExifData(details, exif, ExifTags.TAG_ISO, MediaDetails.INDEX_ISO); - setExifData(details, exif, ExifInterface.TAG_WHITE_BALANCE, + is = new FileInputStream(filePath); + ExifData data = new ExifReader().read(is); + setExifData(details, data.getTag(ExifTag.TAG_FLASH), MediaDetails.INDEX_FLASH); + setExifData(details, data.getTag(ExifTag.TAG_IMAGE_WIDTH), MediaDetails.INDEX_WIDTH); + setExifData(details, data.getTag(ExifTag.TAG_IMAGE_LENGTH), MediaDetails.INDEX_HEIGHT); + setExifData(details, data.getTag(ExifTag.TAG_MAKE), MediaDetails.INDEX_MAKE); + setExifData(details, data.getTag(ExifTag.TAG_MODEL),MediaDetails.INDEX_MODEL); + setExifData(details, data.getTag(ExifTag.TAG_APERTURE_VALUE), + MediaDetails.INDEX_APERTURE); + setExifData(details, data.getTag(ExifTag.TAG_ISO_SPEED_RATINGS), + MediaDetails.INDEX_ISO); + setExifData(details, data.getTag(ExifTag.TAG_WHITE_BALANCE), MediaDetails.INDEX_WHITE_BALANCE); - setExifData(details, exif, ExifTags.TAG_EXPOSURE_TIME, + setExifData(details, data.getTag(ExifTag.TAG_EXPOSURE_TIME), MediaDetails.INDEX_EXPOSURE_TIME); - - double data = exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, 0); - if (data != 0f) { - details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH, data); + ExifTag focalTag = data.getTag(ExifTag.TAG_FOCAL_LENGTH); + if (focalTag != null) { + details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH, + focalTag.getRational(0).toDouble()); details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm); } } catch (IOException ex) { // ignore it. Log.w(TAG, "", ex); + } catch (ExifInvalidFormatException ex) { + // ignore it. + Log.w(TAG, "", ex); + } finally { + Utils.closeSilently(is); } } } diff --git a/src/com/android/gallery3d/data/MediaObject.java b/src/com/android/gallery3d/data/MediaObject.java index a41b275fb..9c82661f6 100644 --- a/src/com/android/gallery3d/data/MediaObject.java +++ b/src/com/android/gallery3d/data/MediaObject.java @@ -41,6 +41,7 @@ public abstract class MediaObject { public static final int SUPPORT_BACK = 1 << 14; public static final int SUPPORT_ACTION = 1 << 15; public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 16; + public static final int SUPPORT_MUTE = 1 << 17; public static final int SUPPORT_ALL = 0xffffffff; // These are the bits returned from getMediaType(): diff --git a/src/com/android/gallery3d/filtershow/CropExtras.java b/src/com/android/gallery3d/filtershow/CropExtras.java new file mode 100644 index 000000000..7ed8f1eb5 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/CropExtras.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012 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.filtershow; + +import android.net.Uri; + +public class CropExtras { + + public static final String KEY_CROPPED_RECT = "cropped-rect"; + 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_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded"; + public static final String KEY_ASPECT_X = "aspectX"; + public static final String KEY_ASPECT_Y = "aspectY"; + public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper"; + public static final String KEY_RETURN_DATA = "return-data"; + public static final String KEY_DATA = "data"; + public static final String KEY_SPOTLIGHT_X = "spotlightX"; + public static final String KEY_SPOTLIGHT_Y = "spotlightY"; + public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked"; + public static final String KEY_OUTPUT_FORMAT = "outputFormat"; + + private int mOutputX = 0; + private int mOutputY = 0; + private boolean mScaleUp = true; + private int mAspectX = 0; + private int mAspectY = 0; + private boolean mSetAsWallpaper = false; + private boolean mReturnData = false; + private Uri mExtraOutput = null; + private String mOutputFormat = null; + private boolean mShowWhenLocked = false; + private float mSpotlightX = 0; + private float mSpotlightY = 0; + + public CropExtras(int outputX, int outputY, boolean scaleUp, int aspectX, int aspectY, + boolean setAsWallpaper, boolean returnData, Uri extraOutput, String outputFormat, + boolean showWhenLocked, float spotlightX, float spotlightY) { + mOutputX = outputX; + mOutputY = outputY; + mScaleUp = scaleUp; + mAspectX = aspectX; + mAspectY = aspectY; + mSetAsWallpaper = setAsWallpaper; + mReturnData = returnData; + mExtraOutput = extraOutput; + mOutputFormat = outputFormat; + mShowWhenLocked = showWhenLocked; + mSpotlightX = spotlightX; + mSpotlightY = spotlightY; + } + + public CropExtras(CropExtras c) { + this(c.mOutputX, c.mOutputY, c.mScaleUp, c.mAspectX, c.mAspectY, c.mSetAsWallpaper, + c.mReturnData, c.mExtraOutput, c.mOutputFormat, c.mShowWhenLocked, + c.mSpotlightX, c.mSpotlightY); + } + + public int getOutputX() { + return mOutputX; + } + + public int getOutputY() { + return mOutputY; + } + + public boolean getScaleUp() { + return mScaleUp; + } + + public int getAspectX() { + return mAspectX; + } + + public int getAspectY() { + return mAspectY; + } + + public boolean getSetAsWallpaper() { + return mSetAsWallpaper; + } + + public boolean getReturnData() { + return mReturnData; + } + + public Uri getExtraOutput() { + return mExtraOutput; + } + + public String getOutputFormat() { + return mOutputFormat; + } + + public boolean getShowWhenLocked() { + return mShowWhenLocked; + } + + public float getSpotlightX() { + return mSpotlightX; + } + + public float getSpotlightY() { + return mSpotlightY; + } +} diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index c1e4f6add..356a0a074 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -20,6 +20,7 @@ import android.annotation.TargetApi; import android.app.ActionBar; import android.app.Activity; import android.app.ProgressDialog; +import android.app.WallpaperManager; import android.content.ContentValues; import android.content.Intent; import android.content.res.Configuration; @@ -32,6 +33,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.provider.MediaStore; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; @@ -58,6 +60,7 @@ import com.android.gallery3d.filtershow.filters.ImageFilter; import com.android.gallery3d.filtershow.filters.ImageFilterBorder; import com.android.gallery3d.filtershow.filters.ImageFilterBwFilter; import com.android.gallery3d.filtershow.filters.ImageFilterContrast; +import com.android.gallery3d.filtershow.filters.ImageFilterCurves; import com.android.gallery3d.filtershow.filters.ImageFilterExposure; import com.android.gallery3d.filtershow.filters.ImageFilterFx; import com.android.gallery3d.filtershow.filters.ImageFilterHue; @@ -65,6 +68,7 @@ import com.android.gallery3d.filtershow.filters.ImageFilterParametricBorder; import com.android.gallery3d.filtershow.filters.ImageFilterRS; import com.android.gallery3d.filtershow.filters.ImageFilterSaturated; import com.android.gallery3d.filtershow.filters.ImageFilterShadows; +import com.android.gallery3d.filtershow.filters.ImageFilterSharpen; import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet; import com.android.gallery3d.filtershow.filters.ImageFilterVibrance; import com.android.gallery3d.filtershow.filters.ImageFilterVignette; @@ -72,13 +76,13 @@ import com.android.gallery3d.filtershow.filters.ImageFilterWBalance; import com.android.gallery3d.filtershow.imageshow.ImageBorder; import com.android.gallery3d.filtershow.imageshow.ImageCrop; import com.android.gallery3d.filtershow.imageshow.ImageFlip; +import com.android.gallery3d.filtershow.imageshow.ImageRedEyes; import com.android.gallery3d.filtershow.imageshow.ImageRotate; import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.imageshow.ImageSmallBorder; import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter; import com.android.gallery3d.filtershow.imageshow.ImageStraighten; import com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet; -import com.android.gallery3d.filtershow.imageshow.ImageWithIcon; import com.android.gallery3d.filtershow.imageshow.ImageZoom; import com.android.gallery3d.filtershow.presets.ImagePreset; import com.android.gallery3d.filtershow.provider.SharedImageProvider; @@ -90,6 +94,7 @@ import com.android.gallery3d.filtershow.ui.Spline; import com.android.gallery3d.util.GalleryUtils; import java.io.File; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.Vector; @@ -97,14 +102,19 @@ import java.util.Vector; public class FilterShowActivity extends Activity implements OnItemClickListener, OnShareTargetSelectedListener { - public static final String CROP_ACTION = "com.android.camera.action.EDITOR_CROP"; + // fields for supporting crop action + public static final String CROP_ACTION = "com.android.camera.action.CROP"; + private CropExtras mCropExtras = null; + public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET"; public static final String LAUNCH_FULLSCREEN = "launch-fullscreen"; + public static final int MAX_BMAP_IN_INTENT = 990000; private final PanelController mPanelController = new PanelController(); private ImageLoader mImageLoader = null; private ImageShow mImageShow = null; private ImageCurves mImageCurves = null; private ImageBorder mImageBorders = null; + private ImageRedEyes mImageRedEyes = null; private ImageStraighten mImageStraighten = null; private ImageZoom mImageZoom = null; private ImageCrop mImageCrop = null; @@ -117,6 +127,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, private View mListGeometry = null; private View mListColors = null; private View mListFilterButtons = null; + private View mSaveButton = null; private ImageButton mFxButton = null; private ImageButton mBorderButton = null; @@ -155,7 +166,8 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, ImageFilterRS.setRenderScriptContext(this); ImageShow.setDefaultBackgroundColor(getResources().getColor(R.color.background_screen)); - ImageSmallFilter.setDefaultBackgroundColor(getResources().getColor(R.color.background_main_toolbar)); + ImageSmallFilter.setDefaultBackgroundColor(getResources().getColor( + R.color.background_main_toolbar)); // TODO: get those values from XML. ImageZoom.setZoomedSize(getPixelsFromDip(256)); FramedTextButton.setTextSize((int) getPixelsFromDip(14)); @@ -180,7 +192,8 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); actionBar.setCustomView(R.layout.filtershow_actionbar); - actionBar.getCustomView().setOnClickListener(new OnClickListener() { + mSaveButton = actionBar.getCustomView(); + mSaveButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { saveImage(); @@ -202,9 +215,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageRotate = (ImageRotate) findViewById(R.id.imageRotate); mImageFlip = (ImageFlip) findViewById(R.id.imageFlip); mImageTinyPlanet = (ImageTinyPlanet) findViewById(R.id.imageTinyPlanet); + mImageRedEyes = (ImageRedEyes) findViewById(R.id.imageRedEyes); mImageCrop.setAspectTextSize((int) getPixelsFromDip(18)); ImageCrop.setTouchTolerance((int) getPixelsFromDip(25)); + ImageCrop.setMinCropSize((int) getPixelsFromDip(55)); mImageViews.add(mImageShow); mImageViews.add(mImageCurves); mImageViews.add(mImageBorders); @@ -214,6 +229,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageViews.add(mImageRotate); mImageViews.add(mImageFlip); mImageViews.add(mImageTinyPlanet); + mImageViews.add(mImageRedEyes); + for (ImageShow imageShow : mImageViews) { + mImageLoader.addCacheListener(imageShow); + } mListFx = findViewById(R.id.fxList); mListBorders = findViewById(R.id.bordersList); @@ -248,6 +267,8 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageFlip.setMaster(mImageShow); mImageTinyPlanet.setImageLoader(mImageLoader); mImageTinyPlanet.setMaster(mImageShow); + mImageRedEyes.setImageLoader(mImageLoader); + mImageRedEyes.setMaster(mImageShow); mPanelController.setActivity(this); @@ -260,6 +281,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mPanelController.addImageView(findViewById(R.id.imageFlip)); mPanelController.addImageView(findViewById(R.id.imageZoom)); mPanelController.addImageView(findViewById(R.id.imageTinyPlanet)); + mPanelController.addImageView(findViewById(R.id.imageRedEyes)); mPanelController.addPanel(mFxButton, mListFx, 0); mPanelController.addPanel(mBorderButton, mListBorders, 1); @@ -269,86 +291,40 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mPanelController.addComponent(mGeometryButton, findViewById(R.id.cropButton)); mPanelController.addComponent(mGeometryButton, findViewById(R.id.rotateButton)); mPanelController.addComponent(mGeometryButton, findViewById(R.id.flipButton)); + mPanelController.addComponent(mGeometryButton, findViewById(R.id.redEyeButton)); mPanelController.addPanel(mColorsButton, mListColors, 3); - int[] recastIDs = { - R.id.tinyplanetButton, - R.id.vignetteButton, - R.id.vibranceButton, - R.id.contrastButton, - R.id.saturationButton, - R.id.bwfilterButton, - R.id.wbalanceButton, - R.id.hueButton, - R.id.exposureButton, - R.id.shadowRecoveryButton - }; ImageFilter[] filters = { new ImageFilterTinyPlanet(), + new ImageFilterWBalance(), + new ImageFilterExposure(), new ImageFilterVignette(), - new ImageFilterVibrance(), new ImageFilterContrast(), - new ImageFilterSaturated(), - new ImageFilterBwFilter(), - new ImageFilterWBalance(), + new ImageFilterShadows(), + new ImageFilterVibrance(), + new ImageFilterSharpen(), + new ImageFilterCurves(), new ImageFilterHue(), - new ImageFilterExposure(), - new ImageFilterShadows() + new ImageFilterSaturated(), + new ImageFilterBwFilter() }; for (int i = 0; i < filters.length; i++) { ImageSmallFilter fView = new ImageSmallFilter(this); - View v = listColors.findViewById(recastIDs[i]); - int pos = listColors.indexOfChild(v); - listColors.removeView(v); - filters[i].setParameter(filters[i].getPreviewParameter()); - if (v instanceof ImageButtonTitle) - filters[i].setName(((ImageButtonTitle) v).getText()); + filters[i].setName(getString(filters[i].getTextId())); fView.setImageFilter(filters[i]); fView.setController(this); fView.setImageLoader(mImageLoader); - fView.setId(recastIDs[i]); - mPanelController.addComponent(mColorsButton, fView); - listColors.addView(fView, pos); - } - - int[] overlayIDs = { - R.id.sharpenButton, - R.id.curvesButtonRGB - }; - int[] overlayBitmaps = { - R.drawable.filtershow_button_colors_sharpen, - R.drawable.filtershow_button_colors_curve - }; - int[] overlayNames = { - R.string.sharpness, - R.string.curvesRGB - }; - - for (int i = 0; i < overlayIDs.length; i++) { - ImageWithIcon fView = new ImageWithIcon(this); - View v = listColors.findViewById(overlayIDs[i]); - int pos = listColors.indexOfChild(v); - listColors.removeView(v); - final int sid = overlayNames[i]; - ImageFilterExposure efilter = new ImageFilterExposure() { - { - mName = getString(sid); - } - }; - efilter.setParameter(-300); - Bitmap bitmap = BitmapFactory.decodeResource(getResources(), - overlayBitmaps[i]); - - fView.setIcon(bitmap); - fView.setImageFilter(efilter); - fView.setController(this); - fView.setImageLoader(mImageLoader); - fView.setId(overlayIDs[i]); + fView.setId(filters[i].getButtonId()); + if (filters[i].getOverlayBitmaps() != 0) { + Bitmap bitmap = BitmapFactory.decodeResource(getResources(), + filters[i].getOverlayBitmaps()); + fView.setOverlayBitmap(bitmap); + } mPanelController.addComponent(mColorsButton, fView); - listColors.addView(fView, pos); + listColors.addView(fView); } mPanelController.addView(findViewById(R.id.applyEffect)); @@ -390,8 +366,37 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, pickImage(); } + // Handle behavior for various actions String action = intent.getAction(); if (action.equalsIgnoreCase(CROP_ACTION)) { + Bundle extras = intent.getExtras(); + if (extras != null) { + mCropExtras = 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)); + + if (mCropExtras.getShowWhenLocked()) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + mImageShow.getImagePreset().mGeoData.setCropExtras(mCropExtras); + + mImageCrop.setExtras(mCropExtras); + String s = getString(R.string.Fixed); + mImageCrop.setAspectString(s); + mImageCrop.setCropActionFlag(true); + mPanelController.setFixedAspect(mCropExtras.getAspectX() > 0 + && mCropExtras.getAspectY() > 0); + } mPanelController.showComponent(findViewById(R.id.cropButton)); } else if (action.equalsIgnoreCase(TINY_PLANET_ACTION)) { mPanelController.showComponent(findViewById(R.id.tinyplanetButton)); @@ -411,7 +416,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mLoadBitmapTask.execute(uri); } - private class LoadBitmapTask extends AsyncTask<Uri, Void, Boolean> { + private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> { View mTinyPlanetButton; int mBitmapSize; @@ -422,19 +427,26 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, @Override protected Boolean doInBackground(Uri... params) { - mImageLoader.loadBitmap(params[0], mBitmapSize); - publishProgress(); - return mImageLoader.queryLightCycle360(); + if (!mImageLoader.loadBitmap(params[0], mBitmapSize)) { + return false; + } + publishProgress(mImageLoader.queryLightCycle360()); + return true; } @Override - protected void onProgressUpdate(Void... values) { + protected void onProgressUpdate(Boolean... values) { super.onProgressUpdate(values); - if (isCancelled()) return; + if (isCancelled()) { + return; + } final View filters = findViewById(R.id.filtersPanel); final View loading = findViewById(R.id.loading); loading.setVisibility(View.GONE); filters.setVisibility(View.VISIBLE); + if (values[0]) { + mTinyPlanetButton.setVisibility(View.VISIBLE); + } } @Override @@ -442,9 +454,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, if (isCancelled()) { return; } - if (result) { - mTinyPlanetButton.setVisibility(View.VISIBLE); + if (!result) { + cannotLoadImage(); } + mLoadBitmapTask = null; super.onPostExecute(result); } @@ -640,6 +653,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, return false; } + public void enableSave(boolean enable) { + if (mSaveButton != null) + mSaveButton.setEnabled(enable); + } + private void fillListImages(LinearLayout listFilters) { // TODO: use listview // TODO: load the filters straight from the filesystem @@ -990,17 +1008,88 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, } } + private boolean mSaveToExtraUri = false; + private boolean mSaveAsWallpaper = false; + private boolean mReturnAsExtra = false; + private boolean outputted = false; + public void saveImage() { - if (mImageShow.hasModifications()) { - // Get the name of the album, to which the image will be saved - File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri()); - int bucketId = GalleryUtils.getBucketId(saveDir.getPath()); - String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null); - showSavingProgress(albumName); - mImageShow.saveImage(this, null); - } else { - finish(); + // boolean outputted = false; + if (mCropExtras != null) { + if (mCropExtras.getExtraOutput() != null) { + mSaveToExtraUri = true; + outputted = true; + } + if (mCropExtras.getSetAsWallpaper()) { + mSaveAsWallpaper = true; + outputted = true; + } + if (mCropExtras.getReturnData()) { + + mReturnAsExtra = true; + outputted = true; + } + + if (outputted) { + mImageShow.getImagePreset().mGeoData.setUseCropExtrasFlag(true); + showSavingProgress(null); + mImageShow.returnFilteredResult(this); + } + } + if (!outputted) { + if (mImageShow.hasModifications()) { + // Get the name of the album, to which the image will be saved + File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri()); + int bucketId = GalleryUtils.getBucketId(saveDir.getPath()); + String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null); + showSavingProgress(albumName); + mImageShow.saveImage(this, null); + } else { + done(); + } + } + } + + public void onFilteredResult(Bitmap filtered) { + Intent intent = new Intent(); + intent.putExtra(CropExtras.KEY_CROPPED_RECT, mImageShow.getImageCropBounds()); + if (mSaveToExtraUri) { + mImageShow.saveToUri(filtered, mCropExtras.getExtraOutput(), + mCropExtras.getOutputFormat(), this); + } + if (mSaveAsWallpaper) { + try { + WallpaperManager.getInstance(this).setBitmap(filtered); + } catch (IOException e) { + Log.w(LOGTAG, "fail to set wall paper", e); + } } + if (mReturnAsExtra) { + if (filtered != null) { + int bmapSize = filtered.getRowBytes() * filtered.getHeight(); + /* + * Max size of Binder transaction buffer is 1Mb, so constrain + * Bitmap to be somewhat less than this, otherwise we get + * TransactionTooLargeExceptions. + */ + if (bmapSize > MAX_BMAP_IN_INTENT) { + Log.w(LOGTAG, "Bitmap too large to be returned via intent"); + } else { + intent.putExtra(CropExtras.KEY_DATA, filtered); + } + } + } + setResult(RESULT_OK, intent); + if (!mSaveToExtraUri) { + done(); + } + } + + public void done() { + if (outputted) { + hideSavingProgress(); + } + finish(); } static { diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java index 52bf98aa7..1d9cc22b5 100644 --- a/src/com/android/gallery3d/filtershow/PanelController.java +++ b/src/com/android/gallery3d/filtershow/PanelController.java @@ -42,6 +42,7 @@ import com.android.gallery3d.filtershow.filters.ImageFilterVignette; import com.android.gallery3d.filtershow.filters.ImageFilterWBalance; import com.android.gallery3d.filtershow.imageshow.ImageCrop; import com.android.gallery3d.filtershow.imageshow.ImageShow; +import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter; import com.android.gallery3d.filtershow.presets.ImagePreset; import com.android.gallery3d.filtershow.ui.FramedTextButton; import com.android.gallery3d.filtershow.ui.ImageCurves; @@ -56,6 +57,11 @@ public class PanelController implements OnClickListener { private static int HORIZONTAL_MOVE = 1; private static final int ANIM_DURATION = 200; private static final String LOGTAG = "PanelController"; + private boolean mFixedAspect = false; + + public void setFixedAspect(boolean t) { + mFixedAspect = t; + } class Panel { private final View mView; @@ -587,6 +593,27 @@ public class PanelController implements OnClickListener { } mUtilityPanel.hideAspectButtons(); mUtilityPanel.hideCurvesButtons(); + + if (view instanceof ImageSmallFilter) { + ImageSmallFilter component = (ImageSmallFilter) view; + ImageFilter filter = component.getImageFilter(); + if (filter.getEditingViewId() != 0) { + mCurrentImage = showImageView(filter.getEditingViewId()); + mCurrentImage.setShowControls(filter.showEditingControls()); + String ename = mCurrentImage.getContext().getString(filter.getTextId()); + mUtilityPanel.setEffectName(ename); + if (view.getId() == R.id.curvesButtonRGB) { + // TODO: delegate to the filter / editing view the management of the + // panel accessory view + mUtilityPanel.setShowParameter(false); + mUtilityPanel.showCurvesButtons(); + } + ensureFilter(ename); + mCurrentImage.select(); + } + return; + } + switch (view.getId()) { case R.id.tinyplanetButton: { mCurrentImage = showImageView(R.id.imageTinyPlanet).setShowControls(true); @@ -606,11 +633,13 @@ public class PanelController implements OnClickListener { String ename = mCurrentImage.getContext().getString(R.string.crop); mUtilityPanel.setEffectName(ename); mUtilityPanel.setShowParameter(false); - if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed){ - ((ImageCrop) mCurrentImage).applyClear(); + if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed) { + ((ImageCrop) mCurrentImage).clear(); mUtilityPanel.firstTimeCropDisplayed = false; } - mUtilityPanel.showAspectButtons(); + if (!mFixedAspect) { + mUtilityPanel.showAspectButtons(); + } break; } case R.id.rotateButton: { @@ -626,89 +655,8 @@ public class PanelController implements OnClickListener { mUtilityPanel.setShowParameter(false); break; } - case R.id.vignetteButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.vignette); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.curvesButtonRGB: { - ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves); - String ename = curves.getContext().getString(R.string.curvesRGB); - mUtilityPanel.setEffectName(ename); - mUtilityPanel.setShowParameter(false); - mUtilityPanel.showCurvesButtons(); - mCurrentImage = curves; - ensureFilter(ename); - break; - } - case R.id.sharpenButton: { - mCurrentImage = showImageView(R.id.imageZoom).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.sharpness); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.contrastButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.contrast); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.saturationButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.saturation); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.bwfilterButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.bwfilter); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.wbalanceButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(false); - String ename = mCurrentImage.getContext().getString(R.string.wbalance); - mUtilityPanel.setEffectName(ename); - mUtilityPanel.setShowParameter(false); - ensureFilter(ename); - break; - } - case R.id.hueButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.hue); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.exposureButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.exposure); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.vibranceButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.vibrance); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.shadowRecoveryButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.shadow_recovery); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } case R.id.redEyeButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); + mCurrentImage = showImageView(R.id.imageRedEyes).setShowControls(true); String ename = mCurrentImage.getContext().getString(R.string.redeye); mUtilityPanel.setEffectName(ename); ensureFilter(ename); diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java index afef58aad..a1a1ba186 100644 --- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java +++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java @@ -18,6 +18,7 @@ package com.android.gallery3d.filtershow.cache; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteException; @@ -26,6 +27,7 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Matrix; import android.graphics.Rect; +import android.graphics.Bitmap.CompressFormat; import android.media.ExifInterface; import android.net.Uri; import android.provider.MediaStore; @@ -36,19 +38,27 @@ import com.adobe.xmp.XMPMeta; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.exif.ExifInvalidFormatException; +import com.android.gallery3d.exif.ExifParser; +import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.FilterShowActivity; import com.android.gallery3d.filtershow.HistoryAdapter; import com.android.gallery3d.filtershow.imageshow.ImageCrop; import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.presets.ImagePreset; +import com.android.gallery3d.filtershow.tools.BitmapTask; import com.android.gallery3d.filtershow.tools.SaveCopyTask; +import com.android.gallery3d.util.InterruptableOutputStream; import com.android.gallery3d.util.XmpUtilHelper; import java.io.Closeable; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.Vector; import java.util.concurrent.locks.ReentrantLock; @@ -69,13 +79,16 @@ public class ImageLoader { private FilterShowActivity mActivity = null; - public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL; - public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90; + public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; + public static final int DEFAULT_COMPRESS_QUALITY = 95; + + public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL; + public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90; public static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180; public static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270; - public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL; - public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL; - public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE; + public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL; + public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL; + public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE; public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE; private Context mContext = null; @@ -101,18 +114,24 @@ public class ImageLoader { return mActivity; } - public void loadBitmap(Uri uri,int size) { + public boolean loadBitmap(Uri uri, int size) { mLoadingLock.lock(); mUri = uri; mOrientation = getOrientation(mContext, uri); mOriginalBitmapSmall = loadScaledBitmap(uri, 160); if (mOriginalBitmapSmall == null) { // Couldn't read the bitmap, let's exit - mActivity.cannotLoadImage(); + mLoadingLock.unlock(); + return false; } mOriginalBitmapLarge = loadScaledBitmap(uri, size); + if (mOriginalBitmapLarge == null) { + mLoadingLock.unlock(); + return false; + } updateBitmaps(); mLoadingLock.unlock(); + return true; } public Uri getUri() { @@ -135,21 +154,25 @@ public class ImageLoader { 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 -1; - } - } else{ + 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 -1; + } + } else { return -1; } - } catch (SQLiteException e){ + } catch (SQLiteException e) { return ExifInterface.ORIENTATION_UNDEFINED; } catch (IllegalArgumentException e) { return ExifInterface.ORIENTATION_UNDEFINED; @@ -160,12 +183,27 @@ public class ImageLoader { static int getOrientationFromPath(String path) { int orientation = -1; + InputStream is = null; try { - ExifInterface EXIF = new ExifInterface(path); - orientation = EXIF.getAttributeInt(ExifInterface.TAG_ORIENTATION, - 1); + is = new FileInputStream(path); + ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0); + int event = parser.next(); + while (event != ExifParser.EVENT_END) { + if (event == ExifParser.EVENT_NEW_TAG) { + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifTag.TAG_ORIENTATION) { + orientation = (int) tag.getValueAt(0); + break; + } + } + event = parser.next(); + } } catch (IOException e) { e.printStackTrace(); + } catch (ExifInvalidFormatException e) { + e.printStackTrace(); + } finally { + Utils.closeSilently(is); } return orientation; } @@ -181,46 +219,46 @@ public class ImageLoader { warnListeners(); } - public static Bitmap rotateToPortrait(Bitmap bitmap,int ori) { - Matrix matrix = new Matrix(); - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - if (ori == ORI_ROTATE_90 || - ori == ORI_ROTATE_270 || - ori == ORI_TRANSPOSE|| - ori == ORI_TRANSVERSE) { - int tmp = w; - w = h; - h = tmp; - } - switch(ori){ - case ORI_ROTATE_90: - matrix.setRotate(90,w/2f,h/2f); - break; - case ORI_ROTATE_180: - matrix.setRotate(180,w/2f,h/2f); - break; - case ORI_ROTATE_270: - matrix.setRotate(270,w/2f,h/2f); - break; - case ORI_FLIP_HOR: - matrix.preScale(-1, 1); - break; - case ORI_FLIP_VERT: - matrix.preScale(1, -1); - break; - case ORI_TRANSPOSE: - matrix.setRotate(90,w/2f,h/2f); - matrix.preScale(1, -1); - break; - case ORI_TRANSVERSE: - matrix.setRotate(270,w/2f,h/2f); - matrix.preScale(1, -1); - break; - case ORI_NORMAL: - default: - return bitmap; - } + public static Bitmap rotateToPortrait(Bitmap bitmap, int ori) { + Matrix matrix = new Matrix(); + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + if (ori == ORI_ROTATE_90 || + ori == ORI_ROTATE_270 || + ori == ORI_TRANSPOSE || + ori == ORI_TRANSVERSE) { + int tmp = w; + w = h; + h = tmp; + } + switch (ori) { + case ORI_ROTATE_90: + matrix.setRotate(90, w / 2f, h / 2f); + break; + case ORI_ROTATE_180: + matrix.setRotate(180, w / 2f, h / 2f); + break; + case ORI_ROTATE_270: + matrix.setRotate(270, w / 2f, h / 2f); + break; + case ORI_FLIP_HOR: + matrix.preScale(-1, 1); + break; + case ORI_FLIP_VERT: + matrix.preScale(1, -1); + break; + case ORI_TRANSPOSE: + matrix.setRotate(90, w / 2f, h / 2f); + matrix.preScale(1, -1); + break; + case ORI_TRANSVERSE: + matrix.setRotate(270, w / 2f, h / 2f); + matrix.preScale(1, -1); + break; + case ORI_NORMAL: + default: + return bitmap; + } return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); @@ -253,6 +291,7 @@ public class ImageLoader { } static final int MAX_BITMAP_DIM = 2048; + private Bitmap loadScaledBitmap(Uri uri, int size) { InputStream is = null; try { @@ -338,7 +377,8 @@ public class ImageLoader { } }; - // TODO: this currently does the loading + filtering on the UI thread -- need to + // TODO: this currently does the loading + filtering on the UI thread -- + // need to // move this to a background thread. public Bitmap getScaleOneImageForPreset(ImageShow caller, ImagePreset imagePreset, Rect bounds, boolean force) { @@ -354,6 +394,7 @@ public class ImageLoader { bmp2 = imagePreset.apply(bmp2); imagePreset.setScaleFactor(scaleFactor); mZoomCache.setImage(imagePreset, bounds, bmp2); + mLoadingLock.unlock(); return bmp2; } } @@ -366,9 +407,11 @@ public class ImageLoader { boolean hiRes) { mLoadingLock.lock(); if (mOriginalBitmapSmall == null) { + mLoadingLock.unlock(); return null; } if (mOriginalBitmapLarge == null) { + mLoadingLock.unlock(); return null; } @@ -415,6 +458,109 @@ public class ImageLoader { }).execute(preset); } + public static Bitmap loadMutableBitmap(Context context, Uri sourceUri) + throws FileNotFoundException { + BitmapFactory.Options options = new BitmapFactory.Options(); + // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't + // exist) + options.inMutable = true; + + InputStream is = context.getContentResolver().openInputStream(sourceUri); + Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); + int orientation = ImageLoader.getOrientation(context, sourceUri); + bitmap = ImageLoader.rotateToPortrait(bitmap, orientation); + return bitmap; + } + + public void returnFilteredResult(ImagePreset preset, + final FilterShowActivity filterShowActivity) { + preset.setIsHighQuality(true); + preset.setScaleFactor(1.0f); + + BitmapTask.Callbacks<ImagePreset> cb = new BitmapTask.Callbacks<ImagePreset>() { + + @Override + public void onComplete(Bitmap result) { + filterShowActivity.onFilteredResult(result); + } + + @Override + public void onCancel() { + } + + @Override + public Bitmap onExecute(ImagePreset param) { + if (param == null) { + return null; + } + try { + Bitmap bitmap = param.apply(loadMutableBitmap(mContext, mUri)); + return bitmap; + } catch (FileNotFoundException ex) { + Log.w(LOGTAG, "Failed to save image!", ex); + return null; + } + } + }; + + (new BitmapTask<ImagePreset>(cb)).execute(preset); + } + + private String getFileExtension(String requestFormat) { + String outputFormat = (requestFormat == null) + ? "jpg" + : requestFormat; + outputFormat = outputFormat.toLowerCase(); + return (outputFormat.equals("png") || outputFormat.equals("gif")) + ? "png" // We don't support gif compression. + : "jpg"; + } + + private CompressFormat convertExtensionToCompressFormat(String extension) { + return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; + } + + public void saveToUri(Bitmap bmap, Uri uri, final String outputFormat, + final FilterShowActivity filterShowActivity) { + + OutputStream out = null; + try { + out = filterShowActivity.getContentResolver().openOutputStream(uri); + } catch (FileNotFoundException e) { + Log.w(LOGTAG, "cannot write output", e); + out = null; + } finally { + if (bmap == null || out == null) { + return; + } + } + + final InterruptableOutputStream ios = new InterruptableOutputStream(out); + + BitmapTask.Callbacks<Bitmap> cb = new BitmapTask.Callbacks<Bitmap>() { + + @Override + public void onComplete(Bitmap result) { + filterShowActivity.done(); + } + + @Override + public void onCancel() { + ios.interrupt(); + } + + @Override + public Bitmap onExecute(Bitmap param) { + CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(outputFormat)); + param.compress(cf, DEFAULT_COMPRESS_QUALITY, ios); + Utils.closeSilently(ios); + return null; + } + }; + + (new BitmapTask<Bitmap>(cb)).execute(bmap); + } + public void setAdapter(HistoryAdapter adapter) { mAdapter = adapter; } @@ -475,4 +621,9 @@ public class ImageLoader { } } + public void addCacheListener(ImageShow imageShow) { + mHiresCache.addObserver(imageShow); + mCache.addObserver(imageShow); + } + } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java index 7f4d5ed2a..5c24fbf65 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java @@ -18,6 +18,7 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; +import com.android.gallery3d.R; import com.android.gallery3d.filtershow.presets.ImagePreset; public class ImageFilter implements Cloneable { @@ -31,22 +32,42 @@ public class ImageFilter implements Cloneable { protected String mName = "Original"; private final String LOGTAG = "ImageFilter"; - public static final byte TYPE_BORDER =1; - public static final byte TYPE_FX = 2; + public static final byte TYPE_BORDER = 1; + public static final byte TYPE_FX = 2; public static final byte TYPE_WBALANCE = 3; public static final byte TYPE_VIGNETTE = 4; public static final byte TYPE_NORMAL = 5; public static final byte TYPE_TINYPLANET = 6; private byte filterType = TYPE_NORMAL; - public byte getFilterType(){ + public byte getFilterType() { return filterType; } - protected void setFilterType(byte type){ + protected void setFilterType(byte type) { filterType = type; } + public int getButtonId() { + return 0; + } + + public int getTextId() { + return 0; + } + + public int getOverlayBitmaps() { + return 0; + } + + public int getEditingViewId() { + return R.id.imageShow; + } + + public boolean showEditingControls() { + return true; + } + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilter filter = (ImageFilter) super.clone(); @@ -93,7 +114,7 @@ public class ImageFilter implements Cloneable { * The maximum allowed value (inclusive) * @return maximum value allowed as input to this filter */ - public int getMaxParameter(){ + public int getMaxParameter() { return mMaxParameter; } @@ -101,7 +122,7 @@ public class ImageFilter implements Cloneable { * The parameter value to be used in previews. * @return parameter value to be used to preview the filter */ - public int getPreviewParameter(){ + public int getPreviewParameter() { return mPreviewParameter; } @@ -109,7 +130,7 @@ public class ImageFilter implements Cloneable { * The minimum allowed value (inclusive) * @return minimum value allowed as input to this filter */ - public int getMinParameter(){ + public int getMinParameter() { return mMinParameter; } @@ -117,7 +138,7 @@ public class ImageFilter implements Cloneable { * Returns the default value returned by this filter. * @return default value */ - public int getDefaultParameter(){ + public int getDefaultParameter() { return mDefaultParameter; } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java index 558abe3c3..1bb5c76ac 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; import android.graphics.Color; @@ -29,6 +31,16 @@ public class ImageFilterBwFilter extends ImageFilter { } @Override + public int getButtonId() { + return R.id.bwfilterButton; + } + + @Override + public int getTextId() { + return R.string.bwfilter; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterBwFilter filter = (ImageFilterBwFilter) super.clone(); return filter; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java index 0c3bb37ca..70e3d8589 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterContrast extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterContrast extends ImageFilter { mName = "Contrast"; } + @Override + public int getButtonId() { + return R.id.contrastButton; + } + + @Override + public int getTextId() { + return R.string.contrast; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java index 89641d103..46a00f47a 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java @@ -18,6 +18,7 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; +import com.android.gallery3d.R; import com.android.gallery3d.filtershow.ui.Spline; public class ImageFilterCurves extends ImageFilter { @@ -31,6 +32,26 @@ public class ImageFilterCurves extends ImageFilter { } @Override + public int getButtonId() { + return R.id.curvesButtonRGB; + } + + @Override + public int getTextId() { + return R.string.curvesRGB; + } + + @Override + public int getOverlayBitmaps() { + return R.drawable.filtershow_button_colors_curve; + } + + @Override + public int getEditingViewId() { + return R.id.imageCurves; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterCurves filter = (ImageFilterCurves) super.clone(); for (int i = 0; i < 4; i++) { diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java index e38dc8eb5..63f860171 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterExposure extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterExposure extends ImageFilter { mName = "Exposure"; } + @Override + public int getButtonId() { + return R.id.exposureButton; + } + + @Override + public int getTextId() { + return R.string.exposure; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java index d74a6faab..33ecc8ab9 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java @@ -23,6 +23,7 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.imageshow.GeometryMath; import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; @@ -69,22 +70,56 @@ public class ImageFilterGeometry extends ImageFilter { // TODO: implement bilinear or bicubic here... for now, just use // canvas to do a simple implementation... // TODO: and be more memory efficient! (do it in native?) + + CropExtras extras = mGeometry.getCropExtras(); + boolean useExtras = mGeometry.getUseCropExtrasFlag(); + int outputX = 0; + int outputY = 0; + boolean s = false; + if (extras != null && useExtras){ + outputX = extras.getOutputX(); + outputY = extras.getOutputY(); + s = extras.getScaleUp(); + } + + Rect cropBounds = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); RectF crop = mGeometry.getCropBounds(bitmap); if (crop.width() > 0 && crop.height() > 0) cropBounds = GeometryMath.roundNearest(crop); - Bitmap temp = null; - if (mGeometry.hasSwitchedWidthHeight()) { - temp = Bitmap.createBitmap(cropBounds.height(), cropBounds.width(), mConfig); - } else { - temp = Bitmap.createBitmap(cropBounds.width(), cropBounds.height(), mConfig); + + int width = cropBounds.width(); + int height = cropBounds.height(); + + if (mGeometry.hasSwitchedWidthHeight()){ + int temp = width; + width = height; + height = temp; } + + if(outputX <= 0 || outputY <= 0){ + outputX = width; + outputY = height; + } + + float scaleX = 1; + float scaleY = 1; + if (s){ + scaleX = (float) outputX / width; + scaleY = (float) outputY / height; + } + + Bitmap temp = null; + temp = Bitmap.createBitmap(outputX, outputY, mConfig); + float[] displayCenter = { temp.getWidth() / 2f, temp.getHeight() / 2f }; Matrix m1 = mGeometry.buildTotalXform(bitmap.getWidth(), bitmap.getHeight(), displayCenter); + m1.postScale(scaleX, scaleY, displayCenter[0], displayCenter[1]); + Canvas canvas = new Canvas(temp); Paint paint = new Paint(); paint.setAntiAlias(true); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java index 279718edb..e2ea388dc 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterHue extends ImageFilter { @@ -29,6 +31,16 @@ public class ImageFilterHue extends ImageFilter { } @Override + public int getButtonId() { + return R.id.hueButton; + } + + @Override + public int getTextId() { + return R.string.hue; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterHue filter = (ImageFilterHue) super.clone(); filter.cmatrix = new ColorSpaceMatrix(cmatrix); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java index c77de330f..9ae6f511e 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java @@ -17,40 +17,167 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.RectF; -public class ImageFilterRedEye extends ImageFilter { - private static final String TAG = "ImageFilterRedEye"; +import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; + +import java.util.Vector; +public class ImageFilterRedEye extends ImageFilter { + private static final String LOGTAG = "ImageFilterRedEye"; + private Vector<RedEyeCandidate> mCandidates = null; public ImageFilterRedEye() { - mName = "Redeye"; + mName = "Red Eye"; + } + @Override + public int getButtonId() { + return R.id.redEyeButton; + } + + @Override + public int getTextId() { + return R.string.redeye; + } + + @Override + public int getEditingViewId() { + return R.id.imageRedEyes; } @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterRedEye filter = (ImageFilterRedEye) super.clone(); - + if (mCandidates != null) { + int size = mCandidates.size(); + filter.mCandidates = new Vector<RedEyeCandidate>(); + for (int i = 0; i < size; i++) { + filter.mCandidates.add(new RedEyeCandidate(mCandidates.elementAt(i))); + } + } return filter; } - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short []matrix); + @Override + public boolean isNil() { + if (mCandidates != null && mCandidates.size() > 0) { + return false; + } + return true; + } + + @Override + public boolean same(ImageFilter filter) { + boolean isRedEyeFilter = super.same(filter); + if (!isRedEyeFilter) { + return false; + } + ImageFilterRedEye redEyeFilter = (ImageFilterRedEye) filter; + if (redEyeFilter.mCandidates == null && mCandidates == null) { + return true; + } + if (redEyeFilter.mCandidates == null || mCandidates == null) { + return false; + } + if (redEyeFilter.mCandidates.size() != mCandidates.size()) { + return false; + } + int size = mCandidates.size(); + for (int i = 0; i < size; i++) { + RedEyeCandidate c1 = mCandidates.elementAt(i); + RedEyeCandidate c2 = redEyeFilter.mCandidates.elementAt(i); + if (!c1.equals(c2)) { + return false; + } + } + return true; + } + + public Vector<RedEyeCandidate> getCandidates() { + if (mCandidates == null) { + mCandidates = new Vector<RedEyeCandidate>(); + } + return mCandidates; + } + + public void addRect(RectF rect, RectF bounds) { + if (mCandidates == null) { + mCandidates = new Vector<RedEyeCandidate>(); + } + Vector<RedEyeCandidate> intersects = new Vector<RedEyeCandidate>(); + for (int i = 0; i < mCandidates.size(); i++) { + RedEyeCandidate r = mCandidates.elementAt(i); + if (r.intersect(rect)) { + intersects.add(r); + } + } + for (int i = 0; i < intersects.size(); i++) { + RedEyeCandidate r = intersects.elementAt(i); + rect.union(r.mRect); + bounds.union(r.mBounds); + mCandidates.remove(r); + } + mCandidates.add(new RedEyeCandidate(rect, bounds)); + } + + public void clear() { + if (mCandidates == null) { + mCandidates = new Vector<RedEyeCandidate>(); + } + mCandidates.clear(); + } + + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short[] matrix); @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); - float p = mParameter; - float value = p; - int box = Math.min(w, h); - int sizex = Math.min((int)((p+100)*box/400),w/2); - int sizey = Math.min((int)((p+100)*box/800),h/2); - - short [] rect = new short[]{ - (short) (w/2-sizex),(short) (w/2-sizey), - (short) (2*sizex),(short) (2*sizey)}; + short[] rect = new short[4]; - nativeApplyFilter(bitmap, w, h, rect); + if (mCandidates != null && mCandidates.size() > 0) { + for (int i = 0; i < mCandidates.size(); i++) { + RectF r = new RectF(mCandidates.elementAt(i).mRect); + GeometryMetadata geo = getImagePreset().mGeoData; + Matrix originalToScreen = geo.getOriginalToScreen(true, + getImagePreset().getImageLoader().getOriginalBounds().width(), + getImagePreset().getImageLoader().getOriginalBounds().height(), + w, h); + originalToScreen.mapRect(r); + if (r.left < 0) { + r.left = 0; + } + if (r.left > w) { + r.left = w; + } + if (r.top < 0) { + r.top = 0; + } + if (r.top > h) { + r.top = h; + } + if (r.right < 0) { + r.right = 0; + } + if (r.right > w) { + r.right = w; + } + if (r.bottom < 0) { + r.bottom = 0; + } + if (r.bottom > h) { + r.bottom = h; + } + rect[0] = (short) r.left; + rect[1] = (short) r.top; + rect[2] = (short) r.width(); + rect[3] = (short) r.height(); + nativeApplyFilter(bitmap, w, h, rect); + } + } return bitmap; } } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java index 1d3459195..129165b3e 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterSaturated extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterSaturated extends ImageFilter { mName = "Saturated"; } + @Override + public int getButtonId() { + return R.id.saturationButton; + } + + @Override + public int getTextId() { + return R.string.saturation; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float saturation); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java index 4e6b848ae..de8fcd5ea 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterShadows extends ImageFilter { @@ -26,6 +28,16 @@ public class ImageFilterShadows extends ImageFilter { } @Override + public int getButtonId() { + return R.id.shadowRecoveryButton; + } + + @Override + public int getTextId() { + return R.string.shadow_recovery; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterShadows filter = (ImageFilterShadows) super.clone(); return filter; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java index a355539c2..1951b9b9e 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java @@ -28,6 +28,26 @@ public class ImageFilterSharpen extends ImageFilterRS { } @Override + public int getButtonId() { + return R.id.sharpenButton; + } + + @Override + public int getTextId() { + return R.string.sharpness; + } + + @Override + public int getOverlayBitmaps() { + return R.drawable.filtershow_button_colors_sharpen; + } + + @Override + public int getEditingViewId() { + return R.id.imageZoom; + } + + @Override public void createFilter(android.content.res.Resources res, float scaleFactor, boolean highQuality) { int w = mInPixelsAllocation.getType().getX(); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java index effd89ebe..36bd62630 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java @@ -22,6 +22,7 @@ import android.graphics.RectF; import com.adobe.xmp.XMPException; import com.adobe.xmp.XMPMeta; +import com.android.gallery3d.R; import com.android.gallery3d.app.Log; import com.android.gallery3d.filtershow.presets.ImagePreset; @@ -59,6 +60,16 @@ public class ImageFilterTinyPlanet extends ImageFilter { mAngle = 0; } + @Override + public int getButtonId() { + return R.id.tinyplanetButton; + } + + @Override + public int getTextId() { + return R.string.tinyplanet; + } + public void setAngle(float angle) { mAngle = angle; } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java index 34f8b245e..7720d0490 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterVibrance extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterVibrance extends ImageFilter { mName = "Vibrance"; } + @Override + public int getButtonId() { + return R.id.vibranceButton; + } + + @Override + public int getTextId() { + return R.string.vibrance; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java index 7a471e5b9..3c904fa6c 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterVignette extends ImageFilter { @@ -25,6 +27,16 @@ public class ImageFilterVignette extends ImageFilter { mName = "Vignette"; } + @Override + public int getButtonId() { + return R.id.vignetteButton; + } + + @Override + public int getTextId() { + return R.string.vignette; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java index b00b867b3..8665dc54c 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterWBalance extends ImageFilter { @@ -26,13 +28,32 @@ public class ImageFilterWBalance extends ImageFilter { mName = "WBalance"; } + @Override + public int getButtonId() { + return R.id.wbalanceButton; + } + + @Override + public int getTextId() { + return R.string.wbalance; + } + + public boolean showEditingControls() { + return false; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, int locX, int locY); @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h, -1,-1); + nativeApplyFilter(bitmap, w, h, -1, -1); return bitmap; } + + @Override + public boolean isNil() { + return false; + } } diff --git a/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java b/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java new file mode 100644 index 000000000..58d3afa3b --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012 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.filtershow.filters; + +import android.graphics.RectF; + +public class RedEyeCandidate { + RectF mRect = new RectF(); + RectF mBounds = new RectF(); + + public RedEyeCandidate(RedEyeCandidate candidate) { + mRect.set(candidate.mRect); + mBounds.set(candidate.mBounds); + } + + public RedEyeCandidate(RectF rect, RectF bounds) { + mRect.set(rect); + mBounds.set(bounds); + } + + public boolean equals(RedEyeCandidate candidate) { + if (candidate.mRect.equals(mRect) + && candidate.mBounds.equals(mBounds)) { + return true; + } + return false; + } + + public boolean intersect(RectF rect) { + return mRect.intersect(rect); + } + + public RectF getRect() { + return mRect; + } +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java b/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java new file mode 100644 index 000000000..e94d1ed9e --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2012 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.filtershow.imageshow; + +import android.graphics.Matrix; +import android.graphics.RectF; + +import java.util.Arrays; + +/** + * Maintains invariant that inner rectangle is constrained to be within the + * outer, rotated rectangle. + */ +public class BoundedRect { + private float rot; + private RectF outer; + private RectF inner; + private float[] innerRotated; + + public BoundedRect() { + rot = 0; + outer = new RectF(); + inner = new RectF(); + innerRotated = new float[8]; + } + + public BoundedRect(float rotation, RectF outerRect, RectF innerRect) { + rot = rotation; + outer = new RectF(outerRect); + inner = new RectF(innerRect); + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + /** + * Sets inner, and re-constrains it to fit within the rotated bounding rect. + */ + public void setInner(RectF newInner) { + if (inner.equals(newInner)) + return; + inner = newInner; + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + /** + * Sets rotation, and re-constrains inner to fit within the rotated bounding rect. + */ + public void setRotation(float rotation) { + if (rotation == rot) + return; + rot = rotation; + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + public RectF getInner() { + return new RectF(inner); + } + + /** + * Tries to move the inner rectangle by (dx, dy). If this would cause it to leave + * the bounding rectangle, snaps the inner rectangle to the edge of the bounding + * rectangle. + */ + public void moveInner(float dx, float dy) { + Matrix m0 = getInverseRotMatrix(); + + RectF translatedInner = new RectF(inner); + translatedInner.offset(dx, dy); + + float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner); + float[] outerCorners = CropMath.getCornersFromRect(outer); + + m0.mapPoints(translatedInnerCorners); + float[] correction = { + 0, 0 + }; + + // find correction vectors for corners that have moved out of bounds + for (int i = 0; i < translatedInnerCorners.length; i += 2) { + float correctedInnerX = translatedInnerCorners[i] + correction[0]; + float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; + if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) { + float[] badCorner = { + correctedInnerX, correctedInnerY + }; + float[] nearestSide = CropMath.closestSide(badCorner, outerCorners); + float[] correctionVec = + GeometryMath.shortestVectorFromPointToLine(badCorner, nearestSide); + correction[0] += correctionVec[0]; + correction[1] += correctionVec[1]; + } + } + + for (int i = 0; i < translatedInnerCorners.length; i += 2) { + float correctedInnerX = translatedInnerCorners[i] + correction[0]; + float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; + if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) { + float[] correctionVec = { + correctedInnerX, correctedInnerY + }; + CropMath.getEdgePoints(outer, correctionVec); + correctionVec[0] -= correctedInnerX; + correctionVec[1] -= correctedInnerY; + correction[0] += correctionVec[0]; + correction[1] += correctionVec[1]; + } + } + + // Set correction + for (int i = 0; i < translatedInnerCorners.length; i += 2) { + float correctedInnerX = translatedInnerCorners[i] + correction[0]; + float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; + // update translated corners with correction vectors + translatedInnerCorners[i] = correctedInnerX; + translatedInnerCorners[i + 1] = correctedInnerY; + } + + innerRotated = translatedInnerCorners; + // reconstrain to update inner + reconstrain(); + } + + /** + * Attempts to resize the inner rectangle. If this would cause it to leave + * the bounding rect, clips the inner rectangle to fit. + */ + public void resizeInner(RectF newInner) { + Matrix m = getRotMatrix(); + Matrix m0 = getInverseRotMatrix(); + + float[] outerCorners = CropMath.getCornersFromRect(outer); + m.mapPoints(outerCorners); + float[] oldInnerCorners = CropMath.getCornersFromRect(inner); + float[] newInnerCorners = CropMath.getCornersFromRect(newInner); + RectF ret = new RectF(newInner); + + for (int i = 0; i < newInnerCorners.length; i += 2) { + float[] c = { + newInnerCorners[i], newInnerCorners[i + 1] + }; + float[] c0 = Arrays.copyOf(c, 2); + m0.mapPoints(c0); + if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) { + float[] outerSide = CropMath.closestSide(c, outerCorners); + float[] pathOfCorner = { + newInnerCorners[i], newInnerCorners[i + 1], + oldInnerCorners[i], oldInnerCorners[i + 1] + }; + float[] p = GeometryMath.lineIntersect(pathOfCorner, outerSide); + if (p == null) { + // lines are parallel or not well defined, so don't resize + p = new float[2]; + p[0] = oldInnerCorners[i]; + p[1] = oldInnerCorners[i + 1]; + } + // relies on corners being in same order as method + // getCornersFromRect + switch (i) { + case 0: + case 1: + ret.left = (p[0] > ret.left) ? p[0] : ret.left; + ret.top = (p[1] > ret.top) ? p[1] : ret.top; + break; + case 2: + case 3: + ret.right = (p[0] < ret.right) ? p[0] : ret.right; + ret.top = (p[1] > ret.top) ? p[1] : ret.top; + break; + case 4: + case 5: + ret.right = (p[0] < ret.right) ? p[0] : ret.right; + ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom; + break; + case 6: + case 7: + ret.left = (p[0] > ret.left) ? p[0] : ret.left; + ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom; + break; + default: + break; + } + } + } + float[] retCorners = CropMath.getCornersFromRect(ret); + m0.mapPoints(retCorners); + innerRotated = retCorners; + // reconstrain to update inner + reconstrain(); + } + + /** + * Attempts to resize the inner rectangle. If this would cause it to leave + * the bounding rect, clips the inner rectangle to fit while maintaining + * aspect ratio. + */ + public void fixedAspectResizeInner(RectF newInner) { + Matrix m = getRotMatrix(); + Matrix m0 = getInverseRotMatrix(); + + float aspectW = inner.width(); + float aspectH = inner.height(); + float aspRatio = aspectW / aspectH; + float[] corners = CropMath.getCornersFromRect(outer); + + m.mapPoints(corners); + float[] oldInnerCorners = CropMath.getCornersFromRect(inner); + float[] newInnerCorners = CropMath.getCornersFromRect(newInner); + + // find fixed corner + int fixed = -1; + if (inner.top == newInner.top) { + if (inner.left == newInner.left) + fixed = 0; // top left + else if (inner.right == newInner.right) + fixed = 2; // top right + } else if (inner.bottom == newInner.bottom) { + if (inner.right == newInner.right) + fixed = 4; // bottom right + else if (inner.left == newInner.left) + fixed = 6; // bottom left + } + // no fixed corner, return without update + if (fixed == -1) + return; + float widthSoFar = newInner.width(); + int moved = -1; + for (int i = 0; i < newInnerCorners.length; i += 2) { + float[] c = { + newInnerCorners[i], newInnerCorners[i + 1] + }; + float[] c0 = Arrays.copyOf(c, 2); + m0.mapPoints(c0); + if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) { + moved = i; + if (moved == fixed) + continue; + float[] l2 = CropMath.closestSide(c, corners); + float[] l1 = { + newInnerCorners[i], newInnerCorners[i + 1], + oldInnerCorners[i], oldInnerCorners[i + 1] + }; + float[] p = GeometryMath.lineIntersect(l1, l2); + if (p == null) { + // lines are parallel or not well defined, so set to old + // corner + p = new float[2]; + p[0] = oldInnerCorners[i]; + p[1] = oldInnerCorners[i + 1]; + } + // relies on corners being in same order as method + // getCornersFromRect + float fixed_x = oldInnerCorners[fixed]; + float fixed_y = oldInnerCorners[fixed + 1]; + float newWidth = Math.abs(fixed_x - p[0]); + float newHeight = Math.abs(fixed_y - p[1]); + newWidth = Math.max(newWidth, aspRatio * newHeight); + if (newWidth < widthSoFar) + widthSoFar = newWidth; + } + } + + float heightSoFar = widthSoFar / aspRatio; + RectF ret = new RectF(inner); + if (fixed == 0) { + ret.right = ret.left + widthSoFar; + ret.bottom = ret.top + heightSoFar; + } else if (fixed == 2) { + ret.left = ret.right - widthSoFar; + ret.bottom = ret.top + heightSoFar; + } else if (fixed == 4) { + ret.left = ret.right - widthSoFar; + ret.top = ret.bottom - heightSoFar; + } else if (fixed == 6) { + ret.right = ret.left + widthSoFar; + ret.top = ret.bottom - heightSoFar; + } + float[] retCorners = CropMath.getCornersFromRect(ret); + m0.mapPoints(retCorners); + innerRotated = retCorners; + // reconstrain to update inner + reconstrain(); + } + + // internal methods + + private boolean isConstrained() { + for (int i = 0; i < 8; i += 2) { + if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1])) + return false; + } + return true; + } + + private void reconstrain() { + // innerRotated has been changed to have incorrect values + CropMath.getEdgePoints(outer, innerRotated); + Matrix m = getRotMatrix(); + float[] unrotated = Arrays.copyOf(innerRotated, 8); + m.mapPoints(unrotated); + inner = CropMath.trapToRect(unrotated); + } + + private void rotateInner() { + Matrix m = getInverseRotMatrix(); + m.mapPoints(innerRotated); + } + + private Matrix getRotMatrix() { + Matrix m = new Matrix(); + m.setRotate(rot, outer.centerX(), outer.centerY()); + return m; + } + + private Matrix getInverseRotMatrix() { + Matrix m = new Matrix(); + m.setRotate(-rot, outer.centerX(), outer.centerY()); + return m; + } +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/CropMath.java b/src/com/android/gallery3d/filtershow/imageshow/CropMath.java new file mode 100644 index 000000000..9037ca043 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/CropMath.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2012 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.filtershow.imageshow; + +import android.graphics.Matrix; +import android.graphics.RectF; + +import java.util.Arrays; + +public class CropMath { + + /** + * Gets a float array of the 2D coordinates representing a rectangles + * corners. + * The order of the corners in the float array is: + * 0------->1 + * ^ | + * | v + * 3<-------2 + * + * @param r the rectangle to get the corners of + * @return the float array of corners (8 floats) + */ + + public static float[] getCornersFromRect(RectF r) { + float[] corners = { + r.left, r.top, + r.right, r.top, + r.right, r.bottom, + r.left, r.bottom + }; + return corners; + } + + /** + * Returns true iff point (x, y) is within or on the rectangle's bounds. + * RectF's "contains" function treats points on the bottom and right bound + * as not being contained. + * + * @param r the rectangle + * @param x the x value of the point + * @param y the y value of the point + * @return + */ + public static boolean inclusiveContains(RectF r, float x, float y) { + return !(x > r.right || x < r.left || y > r.bottom || y < r.top); + } + + /** + * Takes an array of 2D coordinates representing corners and returns the + * smallest rectangle containing those coordinates. + * + * @param array array of 2D coordinates + * @return smallest rectangle containing coordinates + */ + public static RectF trapToRect(float[] array) { + RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, + Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + for (int i = 1; i < array.length; i += 2) { + float x = array[i - 1]; + float y = array[i]; + r.left = (x < r.left) ? x : r.left; + r.top = (y < r.top) ? y : r.top; + r.right = (x > r.right) ? x : r.right; + r.bottom = (y > r.bottom) ? y : r.bottom; + } + r.sort(); + return r; + } + + /** + * If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the + * image bound rectangle, clamps it to the edge of the rectangle. + * + * @param imageBound the rectangle to clamp edge points to. + * @param array an array of points to clamp to the rectangle, gets set to + * the clamped values. + */ + public static void getEdgePoints(RectF imageBound, float[] array) { + if (array.length < 2) + return; + for (int x = 0; x < array.length; x += 2) { + array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right); + array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom); + } + } + + /** + * Takes a point and the corners of a rectangle and returns the two corners + * representing the side of the rectangle closest to the point. + * + * @param point the point which is being checked + * @param corners the corners of the rectangle + * @return two corners representing the side of the rectangle + */ + public static float[] closestSide(float[] point, float[] corners) { + int len = corners.length; + float oldMag = Float.POSITIVE_INFINITY; + float[] bestLine = null; + for (int i = 0; i < len; i += 2) { + float[] line = { + corners[i], corners[(i + 1) % len], + corners[(i + 2) % len], corners[(i + 3) % len] + }; + float mag = GeometryMath.vectorLength( + GeometryMath.shortestVectorFromPointToLine(point, line)); + if (mag < oldMag) { + oldMag = mag; + bestLine = line; + } + } + return bestLine; + } + + /** + * Checks if a given point is within a rotated rectangle. + * + * @param point 2D point to check + * @param bound rectangle to rotate + * @param rot angle of rotation about rectangle center + * @return true if point is within rotated rectangle + */ + public static boolean pointInRotatedRect(float[] point, RectF bound, float rot) { + Matrix m = new Matrix(); + float[] p = Arrays.copyOf(point, 2); + m.setRotate(rot, bound.centerX(), bound.centerY()); + Matrix m0 = new Matrix(); + if (!m.invert(m0)) + return false; + m0.mapPoints(p); + return inclusiveContains(bound, p[0], p[1]); + } + + /** + * Checks if a given point is within a rotated rectangle. + * + * @param point 2D point to check + * @param rotatedRect corners of a rotated rectangle + * @param center center of the rotated rectangle + * @return true if point is within rotated rectangle + */ + public static boolean pointInRotatedRect(float[] point, float[] rotatedRect, float[] center) { + RectF unrotated = new RectF(); + float angle = getUnrotated(rotatedRect, center, unrotated); + return pointInRotatedRect(point, unrotated, angle); + } + + /** + * Resizes rectangle to have a certain aspect ratio (center remains + * stationary). + * + * @param r rectangle to resize + * @param w new width aspect + * @param h new height aspect + */ + public static void fixAspectRatio(RectF r, float w, float h) { + float scale = Math.min(r.width() / w, r.height() / h); + float centX = r.centerX(); + float centY = r.centerY(); + float hw = scale * w / 2; + float hh = scale * h / 2; + r.set(centX - hw, centY - hh, centX + hw, centY + hh); + } + + private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) { + float dy = rotatedRect[1] - rotatedRect[3]; + float dx = rotatedRect[0] - rotatedRect[2]; + float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI); + Matrix m = new Matrix(); + m.setRotate(-angle, center[0], center[1]); + float[] unrotatedRect = new float[rotatedRect.length]; + m.mapPoints(unrotatedRect, rotatedRect); + unrotated.set(trapToRect(unrotatedRect)); + return angle; + } + +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java index 55f791820..568dadfc3 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java @@ -26,11 +26,37 @@ public class GeometryMath { return Math.max(Math.min(i, high), low); } - protected static float[] shortestVectorFromPointToLine(float[] point, float[] l1, float[] l2) { - float x1 = l1[0]; - float x2 = l2[0]; - float y1 = l1[1]; - float y2 = l2[1]; + public static float[] lineIntersect(float[] line1, float[] line2) { + float a0 = line1[0]; + float a1 = line1[1]; + float b0 = line1[2]; + float b1 = line1[3]; + float c0 = line2[0]; + float c1 = line2[1]; + float d0 = line2[2]; + float d1 = line2[3]; + float t0 = a0 - b0; + float t1 = a1 - b1; + float t2 = b0 - d0; + float t3 = d1 - b1; + float t4 = c0 - d0; + float t5 = c1 - d1; + + float denom = t1 * t4 - t0 * t5; + if (denom == 0) + return null; + float u = (t3 * t4 + t5 * t2) / denom; + float[] intersect = { + b0 + u * t0, b1 + u * t1 + }; + return intersect; + } + + public static float[] shortestVectorFromPointToLine(float[] point, float[] line) { + float x1 = line[0]; + float x2 = line[2]; + float y1 = line[1]; + float y2 = line[3]; float xdelt = x2 - x1; float ydelt = y2 - y1; if (xdelt == 0 && ydelt == 0) @@ -40,67 +66,75 @@ public class GeometryMath { float[] ret = { (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1)) }; - float [] vec = {ret[0] - point[0], ret[1] - point[1] }; + float[] vec = { + ret[0] - point[0], ret[1] - point[1] + }; return vec; } // A . B - public static float dotProduct(float[] a, float[] b){ + public static float dotProduct(float[] a, float[] b) { return a[0] * b[0] + a[1] * b[1]; } - public static float[] normalize(float[] a){ + public static float[] normalize(float[] a) { float length = (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]); - float[] b = { a[0] / length, a[1] / length }; + float[] b = { + a[0] / length, a[1] / length + }; return b; } // A onto B - public static float scalarProjection(float[] a, float[] b){ + public static float scalarProjection(float[] a, float[] b) { float length = (float) Math.sqrt(b[0] * b[0] + b[1] * b[1]); return dotProduct(a, b) / length; } - public static float[] getVectorFromPoints(float [] point1, float [] point2){ - float [] p = { point2[0] - point1[0], point2[1] - point1[1] }; + public static float[] getVectorFromPoints(float[] point1, float[] point2) { + float[] p = { + point2[0] - point1[0], point2[1] - point1[1] + }; return p; } - public static float[] getUnitVectorFromPoints(float [] point1, float [] point2){ - float [] p = { point2[0] - point1[0], point2[1] - point1[1] }; + public static float[] getUnitVectorFromPoints(float[] point1, float[] point2) { + float[] p = { + point2[0] - point1[0], point2[1] - point1[1] + }; float length = (float) Math.sqrt(p[0] * p[0] + p[1] * p[1]); p[0] = p[0] / length; p[1] = p[1] / length; return p; } - public static RectF scaleRect(RectF r, float scale){ + public static RectF scaleRect(RectF r, float scale) { return new RectF(r.left * scale, r.top * scale, r.right * scale, r.bottom * scale); } // A - B - public static float[] vectorSubtract(float [] a, float [] b){ + public static float[] vectorSubtract(float[] a, float[] b) { int len = a.length; if (len != b.length) return null; - float [] ret = new float[len]; - for (int i = 0; i < len; i++){ + float[] ret = new float[len]; + for (int i = 0; i < len; i++) { ret[i] = a[i] - b[i]; } return ret; } - public static float vectorLength(float [] a){ + public static float vectorLength(float[] a) { return (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]); } public static float scale(float oldWidth, float oldHeight, float newWidth, float newHeight) { if (oldHeight == 0 || oldWidth == 0) return 1; - return Math.min(newWidth / oldWidth , newHeight / oldHeight); + return Math.min(newWidth / oldWidth, newHeight / oldHeight); } - public static Rect roundNearest(RectF r){ + public static Rect roundNearest(RectF r) { Rect q = new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), Math.round(r.bottom)); return q; diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java index dffdc2449..b53284061 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java @@ -21,12 +21,11 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; +import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.ImageFilterGeometry; public class GeometryMetadata { - // Applied in order: rotate, crop, scale. - // Do not scale saved image (presumably?). private static final ImageFilterGeometry mImageFilter = new ImageFilterGeometry(); private static final String LOGTAG = "GeometryMetadata"; private float mScaleFactor = 1.0f; @@ -36,12 +35,29 @@ public class GeometryMetadata { private final RectF mPhotoBounds = new RectF(); private FLIP mFlip = FLIP.NONE; - private RectF mBounds = new RectF(); - public enum FLIP { NONE, VERTICAL, HORIZONTAL, BOTH } + // Output format data from intent extras + private boolean mUseCropExtras = false; + private CropExtras mCropExtras = null; + public void setUseCropExtrasFlag(boolean f){ + mUseCropExtras = f; + } + + public boolean getUseCropExtrasFlag(){ + return mUseCropExtras; + } + + public void setCropExtras(CropExtras e){ + mCropExtras = e; + } + + public CropExtras getCropExtras(){ + return mCropExtras; + } + public GeometryMetadata() { } @@ -86,7 +102,11 @@ public class GeometryMetadata { mCropBounds.set(g.mCropBounds); mPhotoBounds.set(g.mPhotoBounds); mFlip = g.mFlip; - mBounds = g.mBounds; + + mUseCropExtras = g.mUseCropExtras; + if (g.mCropExtras != null){ + mCropExtras = new CropExtras(g.mCropExtras); + } } public float getScaleFactor() { @@ -184,48 +204,16 @@ public class GeometryMetadata { + ",photoRect=" + mPhotoBounds.toShortString() + "]"; } - // TODO: refactor away - protected static Matrix getHorizontalMatrix(float width) { - Matrix flipHorizontalMatrix = new Matrix(); - flipHorizontalMatrix.setScale(-1, 1); - flipHorizontalMatrix.postTranslate(width, 0); - return flipHorizontalMatrix; - } - protected static void concatHorizontalMatrix(Matrix m, float width) { m.postScale(-1, 1); m.postTranslate(width, 0); } - // TODO: refactor away - protected static Matrix getVerticalMatrix(float height) { - Matrix flipVerticalMatrix = new Matrix(); - flipVerticalMatrix.setScale(1, -1); - flipVerticalMatrix.postTranslate(0, height); - return flipVerticalMatrix; - } - protected static void concatVerticalMatrix(Matrix m, float height) { m.postScale(1, -1); m.postTranslate(0, height); } - // TODO: refactor away - public static Matrix getFlipMatrix(float width, float height, FLIP type) { - if (type == FLIP.HORIZONTAL) { - return getHorizontalMatrix(width); - } else if (type == FLIP.VERTICAL) { - return getVerticalMatrix(height); - } else if (type == FLIP.BOTH) { - Matrix flipper = getVerticalMatrix(height); - flipper.postConcat(getHorizontalMatrix(width)); - return flipper; - } else { - Matrix m = new Matrix(); - m.reset(); // identity - return m; - } - } public static void concatMirrorMatrix(Matrix m, float width, float height, FLIP type) { if (type == FLIP.HORIZONTAL) { @@ -331,46 +319,10 @@ public class GeometryMetadata { return m1; } - // TODO: refactor away - public Matrix getFlipMatrix(float width, float height) { - FLIP type = getFlipType(); - return getFlipMatrix(width, height, type); - } - public boolean hasSwitchedWidthHeight() { return (((int) (mRotation / 90)) % 2) != 0; } - // TODO: refactor away - public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, - float rotation) { - float dx0 = width / 2; - float dy0 = height / 2; - Matrix m = getFlipMatrix(width, height); - m.postTranslate(-dx0, -dy0); - m.postRotate(rotation); - m.postScale(scaling, scaling); - m.postTranslate(dx, dy); - return m; - } - - // TODO: refactor away - public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, - boolean onlyRotate) { - float rot = mRotation; - if (!onlyRotate) { - rot += mStraightenRotation; - } - return buildGeometryMatrix(width, height, scaling, dx, dy, rot); - } - - // TODO: refactor away - public Matrix buildGeometryUIMatrix(float scaling, float dx, float dy) { - float w = mPhotoBounds.width(); - float h = mPhotoBounds.height(); - return buildGeometryMatrix(w, h, scaling, dx, dy, false); - } - public static Matrix buildPhotoMatrix(RectF photo, RectF crop, float rotation, float straighten, FLIP type) { Matrix m = new Matrix(); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java index a352a16e7..594e008a2 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java @@ -29,22 +29,25 @@ import android.util.AttributeSet; import android.util.Log; import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.CropExtras; public class ImageCrop extends ImageGeometry { private static final boolean LOGV = false; + + // Sides private static final int MOVE_LEFT = 1; private static final int MOVE_TOP = 2; private static final int MOVE_RIGHT = 4; private static final int MOVE_BOTTOM = 8; private static final int MOVE_BLOCK = 16; - //Corners + // Corners private static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT; private static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT; private static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT; private static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT; - private static final float MIN_CROP_WIDTH_HEIGHT = 0.1f; + private static int mMinSideSize = 100; private static int mTouchTolerance = 45; private boolean mFirstDraw = true; @@ -53,23 +56,30 @@ public class ImageCrop extends ImageGeometry { private boolean mFixAspectRatio = false; private float mLastRot = 0; - private final Paint borderPaint; + private BoundedRect mBounded = null; private int movingEdges; private final Drawable cropIndicator; private final int indicatorSize; private final int mBorderColor = Color.argb(128, 255, 255, 255); + // Offset between crop center and photo center + private float[] mOffset = { + 0, 0 + }; + private CropExtras mCropExtras = null; + private boolean mDoingCropIntentAction = false; + private static final String LOGTAG = "ImageCrop"; private String mAspect = ""; private int mAspectTextSize = 24; - public void setAspectTextSize(int textSize){ + public void setAspectTextSize(int textSize) { mAspectTextSize = textSize; } - public void setAspectString(String a){ + public void setAspectString(String a) { mAspect = a; } @@ -80,10 +90,6 @@ public class ImageCrop extends ImageGeometry { Resources resources = context.getResources(); cropIndicator = resources.getDrawable(R.drawable.camera_crop); indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); - borderPaint = new Paint(); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setColor(mBorderColor); - borderPaint.setStrokeWidth(2f); } public ImageCrop(Context context, AttributeSet attrs) { @@ -91,10 +97,6 @@ public class ImageCrop extends ImageGeometry { Resources resources = context.getResources(); cropIndicator = resources.getDrawable(R.drawable.camera_crop); indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); - borderPaint = new Paint(); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setColor(mBorderColor); - borderPaint.setStrokeWidth(2f); } @Override @@ -102,84 +104,46 @@ public class ImageCrop extends ImageGeometry { return getContext().getString(R.string.crop); } - private void swapAspect(){ + private void swapAspect() { + if (mDoingCropIntentAction) { + return; + } float temp = mAspectWidth; mAspectWidth = mAspectHeight; mAspectHeight = temp; } - public static void setTouchTolerance(int tolerance){ + /** + * Set tolerance for crop marker selection (in pixels) + */ + public static void setTouchTolerance(int tolerance) { mTouchTolerance = tolerance; } - private boolean switchCropBounds(int moving_corner, RectF dst) { - RectF crop = getCropBoundsDisplayed(); - float dx1 = 0; - float dy1 = 0; - float dx2 = 0; - float dy2 = 0; - if ((moving_corner & MOVE_RIGHT) != 0) { - dx1 = mCurrentX - crop.right; - } else if ((moving_corner & MOVE_LEFT) != 0) { - dx1 = mCurrentX - crop.left; - } - if ((moving_corner & MOVE_BOTTOM) != 0) { - dy1 = mCurrentY - crop.bottom; - } else if ((moving_corner & MOVE_TOP) != 0) { - dy1 = mCurrentY - crop.top; - } - RectF newCrop = null; - //Fix opposite corner in place and move sides - if (moving_corner == BOTTOM_RIGHT) { - newCrop = new RectF(crop.left, crop.top, crop.left + crop.height(), crop.top - + crop.width()); - } else if (moving_corner == BOTTOM_LEFT) { - newCrop = new RectF(crop.right - crop.height(), crop.top, crop.right, crop.top - + crop.width()); - } else if (moving_corner == TOP_LEFT) { - newCrop = new RectF(crop.right - crop.height(), crop.bottom - crop.width(), - crop.right, crop.bottom); - } else if (moving_corner == TOP_RIGHT) { - newCrop = new RectF(crop.left, crop.bottom - crop.width(), crop.left - + crop.height(), crop.bottom); - } - if ((moving_corner & MOVE_RIGHT) != 0) { - dx2 = mCurrentX - newCrop.right; - } else if ((moving_corner & MOVE_LEFT) != 0) { - dx2 = mCurrentX - newCrop.left; - } - if ((moving_corner & MOVE_BOTTOM) != 0) { - dy2 = mCurrentY - newCrop.bottom; - } else if ((moving_corner & MOVE_TOP) != 0) { - dy2 = mCurrentY - newCrop.top; - } - if (Math.sqrt(dx1*dx1 + dy1*dy1) > Math.sqrt(dx2*dx2 + dy2*dy2)){ - Matrix m = getCropBoundDisplayMatrix(); - Matrix m0 = new Matrix(); - if (!m.invert(m0)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX"); - return false; - } - if (!m0.mapRect(newCrop)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE"); - return false; - } - swapAspect(); - dst.set(newCrop); - return true; - } - return false; + /** + * Set minimum side length for crop box (in pixels) + */ + public static void setMinCropSize(int minHeightWidth) { + mMinSideSize = minHeightWidth; + } + + public void setExtras(CropExtras e) { + mCropExtras = e; + } + + public void setCropActionFlag(boolean f) { + mDoingCropIntentAction = f; } - public void apply(float w, float h){ + public void apply(float w, float h) { mFixAspectRatio = true; mAspectWidth = w; mAspectHeight = h; setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } @@ -194,202 +158,159 @@ public class ImageCrop extends ImageGeometry { mAspectHeight = h / scale; setLocalCropBounds(getUntranslatedStraightenCropBounds(photobounds, getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } public void applyClear() { mFixAspectRatio = false; + mAspectWidth = 1; + mAspectHeight = 1; setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } - private float getScaledMinWidthHeight() { - RectF disp = new RectF(0, 0, getWidth(), getHeight()); - float scaled = Math.min(disp.width(), disp.height()) * MIN_CROP_WIDTH_HEIGHT - / computeScale(getWidth(), getHeight()); - return scaled; - } - - protected Matrix getCropRotationMatrix(float rotation, RectF localImage) { - Matrix m = getLocalGeoFlipMatrix(localImage.width(), localImage.height()); - m.postRotate(rotation, localImage.centerX(), localImage.centerY()); - if (!m.rectStaysRect()) { - return null; - } - return m; - } - - protected Matrix getCropBoundDisplayMatrix(){ - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - m = new Matrix(); - } - float zoom = computeScale(getWidth(), getHeight()); - m.postTranslate(mXOffset, mYOffset); - m.postScale(zoom, zoom, mCenterX, mCenterY); - return m; - } - - protected RectF getCropBoundsDisplayed() { - RectF bounds = getLocalCropBounds(); - RectF crop = new RectF(bounds); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - m = new Matrix(); + public void clear() { + if (mCropExtras != null) { + int x = mCropExtras.getAspectX(); + int y = mCropExtras.getAspectY(); + if (mDoingCropIntentAction && x > 0 && y > 0) { + apply(x, y); + } } else { - m.mapRect(crop); + applyClear(); } - m = new Matrix(); - float zoom = computeScale(getWidth(), getHeight()); - m.setScale(zoom, zoom, mCenterX, mCenterY); - m.preTranslate(mXOffset, mYOffset); - m.mapRect(crop); - return crop; } - private RectF getRotatedCropBounds() { - RectF bounds = getLocalCropBounds(); - RectF crop = new RectF(bounds); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - return null; - } else { - m.mapRect(crop); - } - return crop; + private Matrix getPhotoBoundDisplayedMatrix() { + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); + Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop, + getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); + m.preScale(scale, scale); + return m; } - private RectF getUnrotatedCropBounds(RectF cropBounds) { - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO GET ROTATION MATRIX"); - return null; - } - Matrix m0 = new Matrix(); - if (!m.invert(m0)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX"); - return null; - } - RectF crop = new RectF(cropBounds); - if (!m0.mapRect(crop)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS"); - return null; - } - return crop; + private Matrix getCropBoundDisplayedMatrix() { + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); + Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop, + getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); + m1.preScale(scale, scale); + return m1; } - private RectF getRotatedStraightenBounds() { - RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), - getLocalStraighten()); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP STRAIGHTEN BOUNDS TO RECTANGLE"); - return null; - } else { - m.mapRect(straightenBounds); - } - return straightenBounds; + /** + * Takes the rotated corners of a rectangle and returns the angle; sets + * unrotated to be the unrotated version of the rectangle. + */ + private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) { + float dy = rotatedRect[1] - rotatedRect[3]; + float dx = rotatedRect[0] - rotatedRect[2]; + float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI); + Matrix m = new Matrix(); + m.setRotate(-angle, center[0], center[1]); + float[] unrotatedRect = new float[rotatedRect.length]; + m.mapPoints(unrotatedRect, rotatedRect); + unrotated.set(CropMath.trapToRect(unrotatedRect)); + return angle; } /** * Sets cropped bounds; modifies the bounds if it's smaller than the allowed * dimensions. */ - public void setCropBounds(RectF bounds) { - // Avoid cropping smaller than minimum width or height. + public boolean setCropBounds(RectF bounds) { RectF cbounds = new RectF(bounds); - float minWidthHeight = getScaledMinWidthHeight(); - float aw = mAspectWidth; - float ah = mAspectHeight; - if (mFixAspectRatio) { - minWidthHeight /= aw * ah; - int r = (int) (getLocalRotation() / 90); - if (r % 2 != 0) { - float temp = aw; - aw = ah; - ah = temp; - } - } - + Matrix mc = getCropBoundDisplayedMatrix(); + Matrix mcInv = new Matrix(); + mc.invert(mcInv); + mcInv.mapRect(cbounds); + // Avoid cropping smaller than minimum float newWidth = cbounds.width(); float newHeight = cbounds.height(); - if (mFixAspectRatio) { - if (newWidth < (minWidthHeight * aw) || newHeight < (minWidthHeight * ah)) { - newWidth = minWidthHeight * aw; - newHeight = minWidthHeight * ah; - } - } else { - if (newWidth < minWidthHeight) { - newWidth = minWidthHeight; - } - if (newHeight < minWidthHeight) { - newHeight = minWidthHeight; - } - } + float scale = getTransformState(null, null, null); + float minWidthHeight = mMinSideSize / scale; RectF pbounds = getLocalPhotoBounds(); - if (pbounds.width() < minWidthHeight) { - newWidth = pbounds.width(); + + // if photo is smaller than minimum, refuse to set crop bounds + if (pbounds.width() < minWidthHeight || pbounds.height() < minWidthHeight) { + return false; } - if (pbounds.height() < minWidthHeight) { - newHeight = pbounds.height(); + + // if incoming crop is smaller than minimum, refuse to set crop bounds + if (newWidth < minWidthHeight || newHeight < minWidthHeight) { + return false; } - cbounds.set(cbounds.left, cbounds.top, cbounds.left + newWidth, cbounds.top + newHeight); - RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), - getLocalStraighten()); - cbounds.intersect(straightenBounds); + float newX = bounds.centerX() - (getWidth() / 2f); + float newY = bounds.centerY() - (getHeight() / 2f); + mOffset[0] = newX; + mOffset[1] = newY; - if (mFixAspectRatio) { - fixAspectRatio(cbounds, aw, ah); - } setLocalCropBounds(cbounds); invalidate(); + return true; + } + + private BoundedRect getBoundedCrop(RectF crop) { + RectF photo = getLocalPhotoBounds(); + Matrix mp = getPhotoBoundDisplayedMatrix(); + float[] photoCorners = CropMath.getCornersFromRect(photo); + float[] photoCenter = { + photo.centerX(), photo.centerY() + }; + mp.mapPoints(photoCorners); + mp.mapPoints(photoCenter); + RectF scaledPhoto = new RectF(); + float angle = getUnrotated(photoCorners, photoCenter, scaledPhoto); + return new BoundedRect(angle, scaledPhoto, crop); } private void detectMovingEdges(float x, float y) { - RectF cropped = getCropBoundsDisplayed(); + Matrix m = getCropBoundDisplayedMatrix(); + RectF cropped = getLocalCropBounds(); + m.mapRect(cropped); + mBounded = getBoundedCrop(cropped); movingEdges = 0; - // Check left or right. float left = Math.abs(x - cropped.left); float right = Math.abs(x - cropped.right); - if ((left <= mTouchTolerance) && (left < right)) { + float top = Math.abs(y - cropped.top); + float bottom = Math.abs(y - cropped.bottom); + + // Check left or right. + if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) + && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) { movingEdges |= MOVE_LEFT; } - else if (right <= mTouchTolerance) { + else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) + && ((y - mTouchTolerance) <= cropped.bottom)) { movingEdges |= MOVE_RIGHT; } // Check top or bottom. - float top = Math.abs(y - cropped.top); - float bottom = Math.abs(y - cropped.bottom); - if ((top <= mTouchTolerance) & (top < bottom)) { + if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) + && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) { movingEdges |= MOVE_TOP; } - else if (bottom <= mTouchTolerance) { + else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) + && ((x - mTouchTolerance) <= cropped.right)) { movingEdges |= MOVE_BOTTOM; } - // Check inside block. - if (cropped.contains(x, y) && (movingEdges == 0)) { + if (movingEdges == 0) { movingEdges = MOVE_BLOCK; } if (mFixAspectRatio && (movingEdges != MOVE_BLOCK)) { @@ -398,7 +319,7 @@ public class ImageCrop extends ImageGeometry { invalidate(); } - private int fixEdgeToCorner(int moving_edges){ + private int fixEdgeToCorner(int moving_edges) { if (moving_edges == MOVE_LEFT) { moving_edges |= MOVE_TOP; } @@ -414,9 +335,9 @@ public class ImageCrop extends ImageGeometry { return moving_edges; } - private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy){ + private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) { RectF newCrop = null; - //Fix opposite corner in place and move sides + // Fix opposite corner in place and move sides if (moving_corner == BOTTOM_RIGHT) { newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height() + dy); @@ -434,120 +355,90 @@ public class ImageCrop extends ImageGeometry { } private void moveEdges(float dX, float dY) { - RectF cropped = getRotatedCropBounds(); - float minWidthHeight = getScaledMinWidthHeight(); - float scale = computeScale(getWidth(), getHeight()); - float deltaX = dX / scale; - float deltaY = dY / scale; - int select = movingEdges; - if (mFixAspectRatio && (select != MOVE_BLOCK)) { - - // TODO: add in orientation change for fixed aspect - /*if (select == TOP_LEFT || select == TOP_RIGHT || - select == BOTTOM_LEFT || select == BOTTOM_RIGHT){ - RectF blank = new RectF(); - if(switchCropBounds(select, blank)){ - setCropBounds(blank); - return; - } - }*/ - if (select == MOVE_LEFT) { - select |= MOVE_TOP; - } - if (select == MOVE_TOP) { - select |= MOVE_LEFT; - } - if (select == MOVE_RIGHT) { - select |= MOVE_BOTTOM; - } - if (select == MOVE_BOTTOM) { - select |= MOVE_RIGHT; - } - } - - if (select == MOVE_BLOCK) { - RectF straight = getRotatedStraightenBounds(); - // Move the whole cropped bounds within the photo display bounds. - deltaX = (deltaX > 0) ? Math.min(straight.right - cropped.right, deltaX) - : Math.max(straight.left - cropped.left, deltaX); - deltaY = (deltaY > 0) ? Math.min(straight.bottom - cropped.bottom, deltaY) - : Math.max(straight.top - cropped.top, deltaY); - cropped.offset(deltaX, deltaY); + RectF crop = mBounded.getInner(); + + Matrix mc = getCropBoundDisplayedMatrix(); + + RectF photo = getLocalPhotoBounds(); + Matrix mp = getPhotoBoundDisplayedMatrix(); + float[] photoCorners = CropMath.getCornersFromRect(photo); + float[] photoCenter = { + photo.centerX(), photo.centerY() + }; + mp.mapPoints(photoCorners); + mp.mapPoints(photoCenter); + + float minWidthHeight = mMinSideSize; + + if (movingEdges == MOVE_BLOCK) { + mBounded.moveInner(-dX, -dY); + RectF r = mBounded.getInner(); + setCropBounds(r); + return; } else { float dx = 0; float dy = 0; - if ((select & MOVE_LEFT) != 0) { - dx = Math.min(cropped.left + deltaX, cropped.right - minWidthHeight) - cropped.left; + if ((movingEdges & MOVE_LEFT) != 0) { + dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left; } - if ((select & MOVE_TOP) != 0) { - dy = Math.min(cropped.top + deltaY, cropped.bottom - minWidthHeight) - cropped.top; + if ((movingEdges & MOVE_TOP) != 0) { + dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top; } - if ((select & MOVE_RIGHT) != 0) { - dx = Math.max(cropped.right + deltaX, cropped.left + minWidthHeight) - - cropped.right; + if ((movingEdges & MOVE_RIGHT) != 0) { + dx = Math.max(crop.right + dX, crop.left + minWidthHeight) + - crop.right; } - if ((select & MOVE_BOTTOM) != 0) { - dy = Math.max(cropped.bottom + deltaY, cropped.top + minWidthHeight) - - cropped.bottom; + if ((movingEdges & MOVE_BOTTOM) != 0) { + dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight) + - crop.bottom; } if (mFixAspectRatio) { - RectF crop = getCropBoundsDisplayed(); - float [] l1 = {crop.left, crop.bottom}; - float [] l2 = {crop.right, crop.top}; - if(movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT){ + float[] l1 = { + crop.left, crop.bottom + }; + float[] l2 = { + crop.right, crop.top + }; + if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) { l1[1] = crop.top; l2[1] = crop.bottom; } - float[] b = { l1[0] - l2[0], l1[1] - l2[1] }; - float[] disp = {dx, dy}; + float[] b = { + l1[0] - l2[0], l1[1] - l2[1] + }; + float[] disp = { + dx, dy + }; float[] bUnit = GeometryMath.normalize(b); float sp = GeometryMath.scalarProjection(disp, bUnit); dx = sp * bUnit[0]; dy = sp * bUnit[1]; - RectF newCrop = fixedCornerResize(crop, select, dx * scale, dy * scale); - Matrix m = getCropBoundDisplayMatrix(); - Matrix m0 = new Matrix(); - if (!m.invert(m0)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX"); - return; - } - if (!m0.mapRect(newCrop)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE"); - return; - } + RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy); + + mBounded.fixedAspectResizeInner(newCrop); + newCrop = mBounded.getInner(); setCropBounds(newCrop); return; } else { - if ((select & MOVE_LEFT) != 0) { - cropped.left += dx; + if ((movingEdges & MOVE_LEFT) != 0) { + crop.left += dx; } - if ((select & MOVE_TOP) != 0) { - cropped.top += dy; + if ((movingEdges & MOVE_TOP) != 0) { + crop.top += dy; } - if ((select & MOVE_RIGHT) != 0) { - cropped.right += dx; + if ((movingEdges & MOVE_RIGHT) != 0) { + crop.right += dx; } - if ((select & MOVE_BOTTOM) != 0) { - cropped.bottom += dy; + if ((movingEdges & MOVE_BOTTOM) != 0) { + crop.bottom += dy; } } } - movingEdges = select; - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - Matrix m0 = new Matrix(); - if (!m.invert(m0)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX"); - } - if (!m0.mapRect(cropped)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS"); - } - setCropBounds(cropped); + mBounded.resizeInner(crop); + crop = mBounded.getInner(); + setCropBounds(crop); } private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) { @@ -560,7 +451,8 @@ public class ImageCrop extends ImageGeometry { @Override protected void setActionDown(float x, float y) { super.setActionDown(x, y); - detectMovingEdges(x, y); + detectMovingEdges(x + mOffset[0], y + mOffset[1]); + } @Override @@ -571,20 +463,54 @@ public class ImageCrop extends ImageGeometry { @Override protected void setActionMove(float x, float y) { - if (movingEdges != 0){ + + if (movingEdges != 0) { moveEdges(x - mCurrentX, y - mCurrentY); } super.setActionMove(x, y); + + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + setActionUp(); + cropSetup(); + invalidate(); } private void cropSetup() { + RectF crop = getLocalCropBounds(); + Matrix m = getCropBoundDisplayedMatrix(); + m.mapRect(crop); if (mFixAspectRatio) { - RectF cb = getRotatedCropBounds(); - fixAspectRatio(cb, mAspectWidth, mAspectHeight); - RectF cb0 = getUnrotatedCropBounds(cb); - setCropBounds(cb0); - } else { - setCropBounds(getLocalCropBounds()); + CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); + } + float dCentX = getWidth() / 2; + float dCentY = getHeight() / 2; + + BoundedRect r = getBoundedCrop(crop); + crop = r.getInner(); + if (!setCropBounds(crop)) { + float h = mMinSideSize / 2; + float wScale = 1; + float hScale = mAspectHeight / mAspectWidth; + if (hScale < 1) { + wScale = mAspectWidth / mAspectHeight; + hScale = 1; + } + crop.set(dCentX - h * wScale, dCentY - h * hScale, dCentX + h * wScale, dCentY + h + * hScale); + if (mFixAspectRatio) { + CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); + } + r.setInner(crop); + crop = r.getInner(); + if (!setCropBounds(crop)) { + crop.set(dCentX - h, dCentY - h, dCentX + h, dCentY + h); + r.setInner(crop); + crop = r.getInner(); + setCropBounds(crop); + } } } @@ -592,7 +518,7 @@ public class ImageCrop extends ImageGeometry { public void imageLoaded() { super.imageLoaded(); syncLocalToMasterGeometry(); - applyClear(); + clear(); invalidate(); } @@ -600,7 +526,7 @@ public class ImageCrop extends ImageGeometry { protected void gainedVisibility() { float rot = getLocalRotation(); // if has changed orientation via rotate - if( ((int) ((rot - mLastRot) / 90)) % 2 != 0 ){ + if (((int) ((rot - mLastRot) / 90)) % 2 != 0) { swapAspect(); } cropSetup(); @@ -610,7 +536,6 @@ public class ImageCrop extends ImageGeometry { @Override public void resetParameter() { super.resetParameter(); - cropSetup(); } @Override @@ -635,7 +560,6 @@ public class ImageCrop extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { - // TODO: move style to xml gPaint.setAntiAlias(true); gPaint.setFilterBitmap(true); gPaint.setDither(true); @@ -645,91 +569,102 @@ public class ImageCrop extends ImageGeometry { cropSetup(); mFirstDraw = false; } - float rotation = getLocalRotation(); - RectF crop = drawTransformed(canvas, image, gPaint); + RectF crop = drawTransformed(canvas, image, gPaint, mOffset); gPaint.setColor(mBorderColor); gPaint.setStrokeWidth(3); gPaint.setStyle(Paint.Style.STROKE); - drawRuleOfThird(canvas, crop, gPaint); - - if (mFixAspectRatio){ - float w = crop.width(); - float h = crop.height(); - float diag = (float) Math.sqrt(w*w + h*h); - - float dash_len = 20; - int num_intervals = (int) (diag / dash_len); - float [] tl = { crop.left, crop.top }; - float centX = tl[0] + w/2; - float centY = tl[1] + h/2 + 5; - float [] br = { crop.right, crop.bottom }; - float [] vec = GeometryMath.getUnitVectorFromPoints(tl, br); - - float [] counter = tl; - for (int x = 0; x < num_intervals; x++ ){ - float tempX = counter[0] + vec[0] * dash_len; - float tempY = counter[1] + vec[1] * dash_len; - if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2){ - canvas.drawLine(counter[0], counter[1], tempX, tempY, gPaint); - } - counter[0] = tempX; - counter[1] = tempY; + + boolean doThirds = true; + + if (mFixAspectRatio) { + float spotlightX = 0; + float spotlightY = 0; + if (mCropExtras != null) { + spotlightX = mCropExtras.getSpotlightX(); + spotlightY = mCropExtras.getSpotlightY(); } + if (mDoingCropIntentAction && spotlightX > 0 && spotlightY > 0) { + float sx = crop.width() * spotlightX; + float sy = crop.height() * spotlightY; + float cx = crop.centerX(); + float cy = crop.centerY(); + RectF r1 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2); + float temp = sx; + sx = sy; + sy = temp; + RectF r2 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2); + canvas.drawRect(r1, gPaint); + canvas.drawRect(r2, gPaint); + doThirds = false; + } else { + float w = crop.width(); + float h = crop.height(); + float diag = (float) Math.sqrt(w * w + h * h); + + float dash_len = 20; + int num_intervals = (int) (diag / dash_len); + float[] tl = { + crop.left, crop.top + }; + float centX = tl[0] + w / 2; + float centY = tl[1] + h / 2 + 5; + float[] br = { + crop.right, crop.bottom + }; + float[] vec = GeometryMath.getUnitVectorFromPoints(tl, br); + + float[] counter = tl; + for (int x = 0; x < num_intervals; x++) { + float tempX = counter[0] + vec[0] * dash_len; + float tempY = counter[1] + vec[1] * dash_len; + if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2) { + canvas.drawLine(counter[0], counter[1], tempX, tempY, gPaint); + } + counter[0] = tempX; + counter[1] = tempY; + } - gPaint.setTextAlign(Paint.Align.CENTER); - gPaint.setTextSize(mAspectTextSize); - canvas.drawText(mAspect, centX, centY, gPaint); + gPaint.setTextAlign(Paint.Align.CENTER); + gPaint.setTextSize(mAspectTextSize); + canvas.drawText(mAspect, centX, centY, gPaint); + } } - gPaint.setColor(mBorderColor); - gPaint.setStrokeWidth(3); - gPaint.setStyle(Paint.Style.STROKE); - drawStraighten(canvas, gPaint); - - int decoded_moving = decoder(movingEdges, rotation); - canvas.save(); - canvas.rotate(rotation, mCenterX, mCenterY); - RectF scaledCrop = unrotatedCropBounds(); - boolean notMoving = decoded_moving == 0; - if (((decoded_moving & MOVE_TOP) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); - } - if (((decoded_moving & MOVE_BOTTOM) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); - } - if (((decoded_moving & MOVE_LEFT) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); - } - if (((decoded_moving & MOVE_RIGHT) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); + if (doThirds) { + drawRuleOfThird(canvas, crop, gPaint); + } - canvas.restore(); - } - - private int bitCycleLeft(int x, int times, int d) { - int mask = (1 << d) - 1; - int mout = x & mask; - times %= d; - int hi = mout >> (d - times); - int low = (mout << times) & mask; - int ret = x & ~mask; - ret |= low; - ret |= hi; - return ret; - } - - protected int decoder(int movingEdges, float rotation) { - int rot = constrainedRotation(rotation); - switch (rot) { - case 90: - return bitCycleLeft(movingEdges, 3, 4); - case 180: - return bitCycleLeft(movingEdges, 2, 4); - case 270: - return bitCycleLeft(movingEdges, 1, 4); - default: - return movingEdges; + + RectF scaledCrop = crop; + boolean notMoving = (movingEdges == 0); + if (mFixAspectRatio) { + if ((movingEdges == TOP_LEFT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.top); + } + if ((movingEdges == TOP_RIGHT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.top); + } + if ((movingEdges == BOTTOM_LEFT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.bottom); + } + if ((movingEdges == BOTTOM_RIGHT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.bottom); + } + } else { + if (((movingEdges & MOVE_TOP) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); + } + if (((movingEdges & MOVE_BOTTOM) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); + } + if (((movingEdges & MOVE_LEFT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); + } + if (((movingEdges & MOVE_RIGHT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); + } } } + } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java index 42dd139bc..c8ae444da 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java @@ -33,7 +33,7 @@ import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP; import com.android.gallery3d.filtershow.presets.ImagePreset; public abstract class ImageGeometry extends ImageSlave { - private boolean mVisibilityGained = false; + protected boolean mVisibilityGained = false; private boolean mHasDrawn = false; protected static final float MAX_STRAIGHTEN_ANGLE = 45; @@ -191,8 +191,8 @@ public abstract class ImageGeometry extends ImageSlave { return r * 90; } - protected Matrix getLocalGeoFlipMatrix(float width, float height) { - return mLocalGeometry.getFlipMatrix(width, height); + protected boolean isHeightWidthSwapped() { + return ((int) (getLocalRotation() / 90)) % 2 != 0; } protected void setLocalStraighten(float r) { @@ -217,32 +217,6 @@ public abstract class ImageGeometry extends ImageSlave { return getLocalRotation() + getLocalStraighten(); } - protected static float[] getCornersFromRect(RectF r) { - // Order is: - // 0------->1 - // ^ | - // | v - // 3<-------2 - float[] corners = { - r.left, r.top, // 0 - r.right, r.top, // 1 - r.right, r.bottom,// 2 - r.left, r.bottom // 3 - }; - return corners; - } - - // If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the - // image bound rectangle, clamps it to the edge of the rectangle. - protected static void getEdgePoints(RectF imageBound, float[] array) { - if (array.length < 2) - return; - for (int x = 0; x < array.length; x += 2) { - array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right); - array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom); - } - } - protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) { Path crop = new Path(); crop.moveTo(points[0], points[1]); @@ -254,16 +228,6 @@ public abstract class ImageGeometry extends ImageSlave { return crop; } - protected static void fixAspectRatio(RectF r, float w, float h) { - float scale = Math.min(r.width() / w, r.height() / h); - float centX = r.centerX(); - float centY = r.centerY(); - float hw = scale * w / 2; - float hh = scale * h / 2; - r.set(centX - hw, centY - hh, centX + hw, centY + hh); - - } - protected static float getNewHeightForWidthAspect(float width, float w, float h) { return width * h / w; } @@ -290,11 +254,11 @@ public abstract class ImageGeometry extends ImageSlave { } protected void gainedVisibility() { - // TODO: Override this stub. + // Override this stub. } protected void lostVisibility() { - // TODO: Override this stub. + // Override this stub. } @Override @@ -327,7 +291,7 @@ public abstract class ImageGeometry extends ImageSlave { } protected int getLocalValue() { - return 0; // TODO: Override this + return 0; // Override this } protected void setActionDown(float x, float y) { @@ -402,110 +366,19 @@ public abstract class ImageGeometry extends ImageSlave { return new RectF(left, top, right, bottom); } - protected Matrix getGeoMatrix(RectF r, boolean onlyRotate) { - RectF pbounds = getLocalPhotoBounds(); - float scale = GeometryMath - .scale(pbounds.width(), pbounds.height(), getWidth(), getHeight()); - if (((int) (getLocalRotation() / 90)) % 2 != 0) { - scale = GeometryMath.scale(pbounds.width(), pbounds.height(), getHeight(), getWidth()); - } - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - float w = r.left * 2 + r.width(); - float h = r.top * 2 + r.height(); - return mLocalGeometry.buildGeometryMatrix(w, h, scale, xoff, yoff, onlyRotate); - } - - protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) { - canvas.save(); - canvas.drawBitmap(bitmap, m, paint); - canvas.restore(); - } - - protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint) { - float scale = computeScale(getWidth(), getHeight()); - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - Matrix m = mLocalGeometry.buildGeometryUIMatrix(scale, xoff, yoff); - drawImageBitmap(canvas, bitmap, paint, m); - } - protected RectF straightenBounds() { RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten()); - Matrix m = getGeoMatrix(bounds, true); - m.mapRect(bounds); - return bounds; - } - - protected void drawStraighten(Canvas canvas, Paint paint) { - RectF bounds = straightenBounds(); - canvas.save(); - canvas.drawRect(bounds, paint); - canvas.restore(); - } - - protected RectF unrotatedCropBounds() { - RectF bounds = getLocalCropBounds(); - RectF pbounds = getLocalPhotoBounds(); float scale = computeScale(getWidth(), getHeight()); - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - Matrix m = mLocalGeometry.buildGeometryMatrix(pbounds.width(), pbounds.height(), scale, - xoff, yoff, 0); - m.mapRect(bounds); - return bounds; - } - - protected RectF cropBounds() { - RectF bounds = getLocalCropBounds(); - Matrix m = getGeoMatrix(getLocalPhotoBounds(), true); - m.mapRect(bounds); + bounds = GeometryMath.scaleRect(bounds, scale); + float dx = (getWidth() / 2) - bounds.centerX(); + float dy = (getHeight() / 2) - bounds.centerY(); + bounds.offset(dx, dy); return bounds; } - // Fails for non-90 degree - protected void drawCrop(Canvas canvas, Paint paint) { - RectF bounds = cropBounds(); - canvas.save(); - canvas.drawRect(bounds, paint); - canvas.restore(); - } - - protected void drawCropSafe(Canvas canvas, Paint paint) { - Matrix m = getGeoMatrix(getLocalPhotoBounds(), true); - RectF crop = getLocalCropBounds(); - if (!m.rectStaysRect()) { - float[] corners = getCornersFromRect(crop); - m.mapPoints(corners); - drawClosedPath(canvas, paint, corners); - } else { - m.mapRect(crop); - Path path = new Path(); - path.addRect(crop, Path.Direction.CCW); - canvas.drawPath(path, paint); - } - } - - protected void drawTransformedBitmap(Canvas canvas, Bitmap bitmap, Paint paint, boolean clip) { - paint.setARGB(255, 0, 0, 0); - drawImageBitmap(canvas, bitmap, paint); - paint.setColor(Color.WHITE); - paint.setStyle(Style.STROKE); - paint.setStrokeWidth(2); - drawCropSafe(canvas, paint); - paint.setColor(getDefaultBackgroundColor()); - paint.setStyle(Paint.Style.FILL); - drawShadows(canvas, paint, unrotatedCropBounds()); - } - - protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) { - RectF display = new RectF(0, 0, getWidth(), getHeight()); - drawShadows(canvas, p, innerBounds, display, getLocalRotation(), getWidth() / 2, - getHeight() / 2); - } - - protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds, + protected static void drawRotatedShadows(Canvas canvas, Paint p, RectF innerBounds, + RectF outerBounds, float rotation, float centerX, float centerY) { canvas.save(); canvas.rotate(rotation, centerX, centerY); @@ -527,6 +400,15 @@ public abstract class ImageGeometry extends ImageSlave { canvas.restore(); } + protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) { + float w = getWidth(); + float h = getHeight(); + canvas.drawRect(0f, 0f, w, innerBounds.top, p); + canvas.drawRect(0f, innerBounds.top, innerBounds.left, innerBounds.bottom, p); + canvas.drawRect(innerBounds.right, innerBounds.top, w, innerBounds.bottom, p); + canvas.drawRect(0f, innerBounds.bottom, w, h, p); + } + @Override public void onDraw(Canvas canvas) { if (getDirtyGeometryFlag()) { @@ -547,21 +429,38 @@ public abstract class ImageGeometry extends ImageSlave { // TODO: Override this stub. } - protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p) { - p.setARGB(255, 0, 0, 0); + /** + * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix + * and returns the scale factor. + */ + protected float getTransformState(RectF photo, RectF crop, float[] displayCenter) { RectF photoBounds = getLocalPhotoBounds(); RectF cropBounds = getLocalCropBounds(); float scale = computeScale(getWidth(), getHeight()); // checks if local rotation is an odd multiple of 90. - if (((int) (getLocalRotation() / 90)) % 2 != 0) { + if (isHeightWidthSwapped()) { scale = computeScale(getHeight(), getWidth()); } // put in screen coordinates - RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale); - RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale); - float[] displayCenter = { - getWidth() / 2f, getHeight() / 2f - }; + if (crop != null) { + crop.set(GeometryMath.scaleRect(cropBounds, scale)); + } + if (photo != null) { + photo.set(GeometryMath.scaleRect(photoBounds, scale)); + } + if (displayCenter != null && displayCenter.length >= 2) { + displayCenter[0] = getWidth() / 2f; + displayCenter[1] = getHeight() / 2f; + } + return scale; + } + + protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p, float[] offset) { + p.setARGB(255, 0, 0, 0); + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop, getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); @@ -569,9 +468,11 @@ public abstract class ImageGeometry extends ImageSlave { getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); m1.mapRect(scaledCrop); Path path = new Path(); + scaledCrop.offset(-offset[0], -offset[1]); path.addRect(scaledCrop, Path.Direction.CCW); m.preScale(scale, scale); + m.postTranslate(-offset[0], -offset[1]); canvas.save(); canvas.drawBitmap(photo, m, p); canvas.restore(); @@ -580,6 +481,11 @@ public abstract class ImageGeometry extends ImageSlave { p.setStyle(Style.STROKE); p.setStrokeWidth(2); canvas.drawPath(path, p); + + p.setColor(getDefaultBackgroundColor()); + p.setAlpha(128); + p.setStyle(Paint.Style.FILL); + drawShadows(canvas, p, scaledCrop); return scaledCrop; } @@ -590,7 +496,7 @@ public abstract class ImageGeometry extends ImageSlave { float imageHeight = cropBounds.height(); float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight()); // checks if local rotation is an odd multiple of 90. - if (((int) (getLocalRotation() / 90)) % 2 != 0) { + if (isHeightWidthSwapped()) { scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth()); } // put in screen coordinates @@ -618,6 +524,8 @@ public abstract class ImageGeometry extends ImageSlave { p.setStyle(Paint.Style.FILL); scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1] - scaledCrop.centerY()); - drawShadows(canvas, p, scaledCrop); + RectF display = new RectF(0, 0, getWidth(), getHeight()); + drawRotatedShadows(canvas, p, scaledCrop, display, getLocalRotation(), getWidth() / 2, + getHeight() / 2); } } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRedEyes.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEyes.java new file mode 100644 index 000000000..5119dff3c --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEyes.java @@ -0,0 +1,148 @@ + +package com.android.gallery3d.filtershow.imageshow; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import com.android.gallery3d.filtershow.filters.ImageFilterRedEye; +import com.android.gallery3d.filtershow.filters.RedEyeCandidate; + +public class ImageRedEyes extends ImageSlave { + + private static final String LOGTAG = "ImageRedEyes"; + private RectF mCurrentRect = null; + private static float mTouchPadding = 80; + + public static void setTouchPadding(float padding) { + mTouchPadding = padding; + } + + public ImageRedEyes(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ImageRedEyes(Context context) { + super(context); + } + + @Override + public void resetParameter() { + ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter(); + if (filter != null) { + filter.clear(); + } + mCurrentRect = null; + invalidate(); + } + + @Override + public void updateImage() { + super.updateImage(); + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + float ex = event.getX(); + float ey = event.getY(); + + ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter(); + + // let's transform (ex, ey) to displayed image coordinates + if (event.getAction() == MotionEvent.ACTION_DOWN) { + mCurrentRect = new RectF(); + mCurrentRect.left = ex - mTouchPadding; + mCurrentRect.top = ey - mTouchPadding; + } + if (event.getAction() == MotionEvent.ACTION_MOVE) { + mCurrentRect.right = ex + mTouchPadding; + mCurrentRect.bottom = ey + mTouchPadding; + } + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mCurrentRect != null) { + // transform to original coordinates + GeometryMetadata geo = getImagePreset().mGeoData; + Matrix originalToScreen = geo.getOriginalToScreen(true, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), + getWidth(), getHeight()); + Matrix originalNoRotateToScreen = geo.getOriginalToScreen(false, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), + getWidth(), getHeight()); + + Matrix invert = new Matrix(); + originalToScreen.invert(invert); + RectF r = new RectF(mCurrentRect); + invert.mapRect(r); + RectF r2 = new RectF(mCurrentRect); + invert.reset(); + originalNoRotateToScreen.invert(invert); + invert.mapRect(r2); + filter.addRect(r, r2); + this.resetImageCaches(this); + } + mCurrentRect = null; + } + invalidate(); + return true; + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + Paint paint = new Paint(); + paint.setStyle(Style.STROKE); + paint.setColor(Color.RED); + paint.setStrokeWidth(2); + if (mCurrentRect != null) { + paint.setColor(Color.RED); + RectF drawRect = new RectF(mCurrentRect); + canvas.drawRect(drawRect, paint); + } + + GeometryMetadata geo = getImagePreset().mGeoData; + Matrix originalToScreen = geo.getOriginalToScreen(false, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), getWidth(), getHeight()); + Matrix originalRotateToScreen = geo.getOriginalToScreen(true, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), getWidth(), getHeight()); + + ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter(); + for (RedEyeCandidate candidate : filter.getCandidates()) { + RectF rect = candidate.getRect(); + RectF drawRect = new RectF(); + originalToScreen.mapRect(drawRect, rect); + RectF fullRect = new RectF(); + originalRotateToScreen.mapRect(fullRect, rect); + paint.setColor(Color.BLUE); + canvas.drawRect(fullRect, paint); + canvas.drawLine(fullRect.centerX(), fullRect.top, + fullRect.centerX(), fullRect.bottom, paint); + canvas.drawLine(fullRect.left, fullRect.centerY(), + fullRect.right, fullRect.centerY(), paint); + paint.setColor(Color.GREEN); + float dw = drawRect.width(); + float dh = drawRect.height(); + float dx = fullRect.centerX() - dw/2; + float dy = fullRect.centerY() - dh/2; + drawRect.set(dx, dy, dx + dw, dy + dh); + canvas.drawRect(drawRect, paint); + canvas.drawLine(drawRect.centerX(), drawRect.top, + drawRect.centerX(), drawRect.bottom, paint); + canvas.drawLine(drawRect.left, drawRect.centerY(), + drawRect.right, drawRect.centerY(), paint); + canvas.drawCircle(drawRect.centerX(), drawRect.centerY(), + mTouchPadding, paint); + } + } +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index 0145c24dc..d9df7b7fc 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java @@ -23,6 +23,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import android.net.Uri; import android.os.Handler; import android.util.AttributeSet; import android.view.GestureDetector; @@ -83,7 +84,7 @@ public class ImageShow extends View implements OnGestureListener, private HistoryAdapter mHistoryAdapter = null; private ImageStateAdapter mImageStateAdapter = null; - private Rect mImageBounds = new Rect(); + protected Rect mImageBounds = new Rect(); private boolean mTouchShowOriginal = false; private long mTouchShowOriginalDate = 0; @@ -230,6 +231,7 @@ public class ImageShow extends View implements OnGestureListener, } updateSeekBar(parameter, minp, maxp); invalidate(); + mActivity.enableSave(hasModifications()); } @Override @@ -323,6 +325,10 @@ public class ImageShow extends View implements OnGestureListener, return dst; } + public Rect getImageCropBounds() { + return GeometryMath.roundNearest(getImagePreset().mGeoData.getPreviewCropBounds()); + } + public Rect getDisplayedImageBounds() { return mImageBounds; } @@ -396,6 +402,7 @@ public class ImageShow extends View implements OnGestureListener, public void updateImagePresets(boolean force) { ImagePreset preset = getImagePreset(); if (preset == null) { + mActivity.enableSave(false); return; } if (force) { @@ -419,6 +426,7 @@ public class ImageShow extends View implements OnGestureListener, mFiltersOnlyImage = null; } } + mActivity.enableSave(hasModifications()); } public void requestFilteredImages() { @@ -643,13 +651,13 @@ public class ImageShow extends View implements OnGestureListener, } public void updateImage() { + invalidate(); if (!updateGeometryFlags()) { return; } Bitmap bitmap = mImageLoader.getOriginalBitmapLarge(); if (bitmap != null) { imageSizeChanged(bitmap); - invalidate(); } } @@ -666,6 +674,14 @@ public class ImageShow extends View implements OnGestureListener, mImageLoader.saveImage(getImagePreset(), filterShowActivity, file); } + public void saveToUri(Bitmap f, Uri u, String m, FilterShowActivity filterShowActivity) { + mImageLoader.saveToUri(f, u, m, filterShowActivity); + } + + public void returnFilteredResult(FilterShowActivity filterShowActivity) { + mImageLoader.returnFilteredResult(getImagePreset(), filterShowActivity); + } + @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java index 6a79e18a1..2a3ee2856 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java @@ -47,6 +47,13 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener protected final int mTextColor = Color.WHITE; private ImageSmallFilter mNullFilter; + private Bitmap mOverlayBitmap = null; + private final int mOverlayTint = Color.argb(100, 0, 0, 0); + + public void setOverlayBitmap(Bitmap bitmap){ + mOverlayBitmap = bitmap; + } + public static void setMargin(int value) { mMargin = value; } @@ -77,6 +84,10 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener mImagePreset.add(mImageFilter); } + public ImageFilter getImageFilter() { + return mImageFilter; + } + @Override public void setSelected(boolean value) { if (mIsSelected != value) { @@ -188,6 +199,13 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener mPaint.setTextSize(mTextSize); mPaint.setColor(mTextColor); canvas.drawText(mImageFilter.getName(), x, y - mTextMargin, mPaint); + if (mOverlayBitmap != null) { + mPaint.setColor(mOverlayTint); + canvas.drawRect(0, mMargin, getWidth(), getWidth() + mMargin, mPaint); + Rect d = new Rect(0, mMargin, getWidth() - mMargin, getWidth()); + mPaint.setColor(Color.BLACK); + drawImage(canvas, mOverlayBitmap, d); + } } public void drawImage(Canvas canvas, Bitmap image, Rect destination) { diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java index 57a22aab3..7a539da8f 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; @@ -105,10 +106,10 @@ public class ImageStraighten extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { - drawTransformed(canvas, image, gPaint); + float [] o = {0, 0}; + RectF bounds = drawTransformed(canvas, image, gPaint, o); // Draw the grid - RectF bounds = straightenBounds(); Path path = new Path(); path.addRect(bounds, Path.Direction.CCW); gPaint.setARGB(255, 255, 255, 255); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java b/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java deleted file mode 100644 index a332fa72a..000000000 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2012 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.filtershow.imageshow; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; - -/** - * TODO: Insert description here. (generated by hoford) - */ -public class ImageWithIcon extends ImageSmallFilter { - /** - * @param context - */ - public ImageWithIcon(Context context) { - super(context); - // TODO(hoford): Auto-generated constructor stub - } - - private Bitmap bitmap; - - public void setIcon(Bitmap bitmap){ - this.bitmap = bitmap; - } - - @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (bitmap != null) { - Rect d = new Rect(0, mMargin, getWidth() - mMargin, getWidth()); - drawImage(canvas, bitmap, d); - } - } -} diff --git a/src/com/android/gallery3d/filtershow/tools/BitmapTask.java b/src/com/android/gallery3d/filtershow/tools/BitmapTask.java new file mode 100644 index 000000000..62801c1f2 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/tools/BitmapTask.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 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.filtershow.tools; + +import android.graphics.Bitmap; +import android.os.AsyncTask; + +/** + * Asynchronous task filtering or doign I/O with bitmaps. + */ +public class BitmapTask <T> extends AsyncTask<T, Void, Bitmap> { + + private Callbacks<T> mCallbacks; + private static final String LOGTAG = "BitmapTask"; + + public BitmapTask(Callbacks<T> callbacks) { + mCallbacks = callbacks; + } + + @Override + protected Bitmap doInBackground(T... params) { + if (params == null || mCallbacks == null) { + return null; + } + return mCallbacks.onExecute(params[0]); + } + + @Override + protected void onPostExecute(Bitmap result) { + if (mCallbacks == null) { + return; + } + mCallbacks.onComplete(result); + } + + @Override + protected void onCancelled() { + if (mCallbacks == null) { + return; + } + mCallbacks.onCancel(); + } + + /** + * Callbacks for the asynchronous task. + */ + public interface Callbacks<P> { + void onComplete(Bitmap result); + + void onCancel(); + + Bitmap onExecute(P param); + } +} diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java index 9c55623d1..30659e677 100644 --- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java +++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java @@ -52,9 +52,6 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { private static final String LOGTAG = "SaveCopyTask"; - private static final int DEFAULT_COMPRESS_QUALITY = 95; - private static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; - /** * Saves the bitmap in the final destination */ @@ -62,7 +59,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { OutputStream os = null; try { os = new FileOutputStream(destination); - bitmap.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, os); + bitmap.compress(CompressFormat.JPEG, ImageLoader.DEFAULT_COMPRESS_QUALITY, os); } catch (FileNotFoundException e) { Log.v(LOGTAG,"Error in writing "+destination.getAbsolutePath()); } finally { @@ -123,7 +120,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { File saveDirectory = getSaveDirectory(context, sourceUri); if ((saveDirectory == null) || !saveDirectory.canWrite()) { saveDirectory = new File(Environment.getExternalStorageDirectory(), - DEFAULT_SAVE_DIRECTORY); + ImageLoader.DEFAULT_SAVE_DIRECTORY); } // Create the directory if it doesn't exist if (!saveDirectory.exists()) saveDirectory.mkdirs(); @@ -137,19 +134,6 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { return new File(saveDirectory, filename + ".JPG"); } - private Bitmap loadMutableBitmap() throws FileNotFoundException { - BitmapFactory.Options options = new BitmapFactory.Options(); - // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't - // exist) - options.inMutable = true; - - InputStream is = context.getContentResolver().openInputStream(sourceUri); - Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); - int orientation = ImageLoader.getOrientation(context, sourceUri); - bitmap = ImageLoader.rotateToPortrait(bitmap, orientation); - return bitmap; - } - private static final String[] COPY_EXIF_ATTRIBUTES = new String[] { ExifInterface.TAG_APERTURE, ExifInterface.TAG_DATETIME, @@ -228,7 +212,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { ImagePreset preset = params[0]; try { - Bitmap bitmap = preset.apply(loadMutableBitmap()); + Bitmap bitmap = preset.apply(ImageLoader.loadMutableBitmap(context, sourceUri)); Object xmp = null; InputStream is = null; diff --git a/src/com/android/gallery3d/gadget/WidgetConfigure.java b/src/com/android/gallery3d/gadget/WidgetConfigure.java index 331e7d2c4..3a1b52bdc 100644 --- a/src/com/android/gallery3d/gadget/WidgetConfigure.java +++ b/src/com/android/gallery3d/gadget/WidgetConfigure.java @@ -27,9 +27,10 @@ import android.widget.RemoteViews; import com.android.gallery3d.R; import com.android.gallery3d.app.AlbumPicker; -import com.android.gallery3d.app.CropImage; import com.android.gallery3d.app.DialogPicker; import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.CropExtras; public class WidgetConfigure extends Activity { @SuppressWarnings("unused") @@ -142,14 +143,14 @@ public class WidgetConfigure extends Activity { int widgetHeight = Math.round(height * scale); mPickedItem = data.getData(); - Intent request = new Intent(CropImage.ACTION_CROP, mPickedItem) - .putExtra(CropImage.KEY_OUTPUT_X, widgetWidth) - .putExtra(CropImage.KEY_OUTPUT_Y, widgetHeight) - .putExtra(CropImage.KEY_ASPECT_X, widgetWidth) - .putExtra(CropImage.KEY_ASPECT_Y, widgetHeight) - .putExtra(CropImage.KEY_SCALE_UP_IF_NEEDED, true) - .putExtra(CropImage.KEY_SCALE, true) - .putExtra(CropImage.KEY_RETURN_DATA, true); + Intent request = new Intent(FilterShowActivity.CROP_ACTION, mPickedItem) + .putExtra(CropExtras.KEY_OUTPUT_X, widgetWidth) + .putExtra(CropExtras.KEY_OUTPUT_Y, widgetHeight) + .putExtra(CropExtras.KEY_ASPECT_X, widgetWidth) + .putExtra(CropExtras.KEY_ASPECT_Y, widgetHeight) + .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true) + .putExtra(CropExtras.KEY_SCALE, true) + .putExtra(CropExtras.KEY_RETURN_DATA, true); startActivityForResult(request, REQUEST_CROP_IMAGE); } diff --git a/src/com/android/gallery3d/ui/ActionModeHandler.java b/src/com/android/gallery3d/ui/ActionModeHandler.java index 7191599ad..d90fc20d0 100644 --- a/src/com/android/gallery3d/ui/ActionModeHandler.java +++ b/src/com/android/gallery3d/ui/ActionModeHandler.java @@ -17,21 +17,21 @@ package com.android.gallery3d.ui; import android.annotation.TargetApi; -import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.nfc.NfcAdapter; import android.os.Handler; -import android.view.ActionMode; -import android.view.ActionMode.Callback; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.widget.Button; -import android.widget.ShareActionProvider; -import android.widget.ShareActionProvider.OnShareTargetSelectedListener; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.ActionMode.Callback; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.widget.ShareActionProvider; +import com.actionbarsherlock.widget.ShareActionProvider.OnShareTargetSelectedListener; import com.android.gallery3d.R; import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.common.ApiHelper; @@ -129,7 +129,7 @@ public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickLi } public void startActionMode() { - Activity a = mActivity; + SherlockActivity a = mActivity; mActionMode = a.startActionMode(this); View customView = LayoutInflater.from(a).inflate( R.layout.action_mode, null); @@ -408,6 +408,15 @@ public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickLi // Pass1: Deal with unexpanded media object list for menu operation. ArrayList<MediaObject> selected = getSelectedMediaObjects(jc); if (selected == null) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + mMenuTask = null; + if (jc.isCancelled()) return; + // Disable all the operations when no item is selected + MenuExecutor.updateMenuOperation(mMenu, 0); + } + }); return null; } final int operation = computeMenuOptions(selected); @@ -466,7 +475,12 @@ public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickLi mMenuExecutor.pause(); } + public void destroy() { + mMenuExecutor.destroy(); + } + public void resume() { if (mSelectionManager.inSelectionMode()) updateSupportedOperation(); + mMenuExecutor.resume(); } } diff --git a/src/com/android/gallery3d/ui/BasicTexture.java b/src/com/android/gallery3d/ui/BasicTexture.java index 99cf0571c..38686d59f 100644 --- a/src/com/android/gallery3d/ui/BasicTexture.java +++ b/src/com/android/gallery3d/ui/BasicTexture.java @@ -36,7 +36,7 @@ abstract class BasicTexture implements Texture { // Log a warning if a texture is larger along a dimension private static final int MAX_TEXTURE_SIZE = 4096; - protected int mId; + protected int mId = -1; protected int mState; protected int mWidth = UNSPECIFIED; @@ -165,8 +165,9 @@ abstract class BasicTexture implements Texture { private void freeResource() { GLCanvas canvas = mCanvasRef; - if (canvas != null && isLoaded()) { + if (canvas != null && mId != -1) { canvas.unloadTexture(this); + mId = -1; // Don't free it again. } mState = STATE_UNLOADED; setAssociatedCanvas(null); diff --git a/src/com/android/gallery3d/ui/CropView.java b/src/com/android/gallery3d/ui/CropView.java deleted file mode 100644 index 1890c7630..000000000 --- a/src/com/android/gallery3d/ui/CropView.java +++ /dev/null @@ -1,801 +0,0 @@ -/* - * 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.ui; - -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PointF; -import android.graphics.RectF; -import android.media.FaceDetector; -import android.os.Handler; -import android.os.Message; -import android.util.FloatMath; -import android.view.MotionEvent; -import android.view.animation.DecelerateInterpolator; -import android.widget.Toast; - -import com.android.gallery3d.R; -import com.android.gallery3d.anim.Animation; -import com.android.gallery3d.app.AbstractGalleryActivity; -import com.android.gallery3d.common.Utils; - -import java.util.ArrayList; - -import javax.microedition.khronos.opengles.GL11; - -/** - * The activity can crop specific region of interest from an image. - */ -public class CropView extends GLView { - @SuppressWarnings("unused") - private static final String TAG = "CropView"; - - private static final int FACE_PIXEL_COUNT = 120000; // around 400x300 - - private static final int COLOR_OUTLINE = 0xFF008AFF; - private static final int COLOR_FACE_OUTLINE = 0xFF000000; - - private static final float OUTLINE_WIDTH = 3f; - - private static final int SIZE_UNKNOWN = -1; - private static final int TOUCH_TOLERANCE = 30; - - private static final float MIN_SELECTION_LENGTH = 16f; - public static final float UNSPECIFIED = -1f; - - private static final int MAX_FACE_COUNT = 3; - private static final float FACE_EYE_RATIO = 2f; - - private static final int ANIMATION_DURATION = 1250; - - private static final int MOVE_LEFT = 1; - private static final int MOVE_TOP = 2; - private static final int MOVE_RIGHT = 4; - private static final int MOVE_BOTTOM = 8; - private static final int MOVE_BLOCK = 16; - - private static final float MAX_SELECTION_RATIO = 0.8f; - private static final float MIN_SELECTION_RATIO = 0.4f; - private static final float SELECTION_RATIO = 0.60f; - private static final int ANIMATION_TRIGGER = 64; - - private static final int MSG_UPDATE_FACES = 1; - - private float mAspectRatio = UNSPECIFIED; - private float mSpotlightRatioX = 0; - private float mSpotlightRatioY = 0; - - private Handler mMainHandler; - - private FaceHighlightView mFaceDetectionView; - private HighlightRectangle mHighlightRectangle; - private TileImageView mImageView; - private AnimationController mAnimation = new AnimationController(); - - private int mImageWidth = SIZE_UNKNOWN; - private int mImageHeight = SIZE_UNKNOWN; - - private AbstractGalleryActivity mActivity; - - private GLPaint mPaint = new GLPaint(); - private GLPaint mFacePaint = new GLPaint(); - - private int mImageRotation; - - public CropView(AbstractGalleryActivity activity) { - mActivity = activity; - mImageView = new TileImageView(activity); - mFaceDetectionView = new FaceHighlightView(); - mHighlightRectangle = new HighlightRectangle(); - - addComponent(mImageView); - addComponent(mFaceDetectionView); - addComponent(mHighlightRectangle); - - mHighlightRectangle.setVisibility(GLView.INVISIBLE); - - mPaint.setColor(COLOR_OUTLINE); - mPaint.setLineWidth(OUTLINE_WIDTH); - - mFacePaint.setColor(COLOR_FACE_OUTLINE); - mFacePaint.setLineWidth(OUTLINE_WIDTH); - - mMainHandler = new SynchronizedHandler(activity.getGLRoot()) { - @Override - public void handleMessage(Message message) { - Utils.assertTrue(message.what == MSG_UPDATE_FACES); - ((DetectFaceTask) message.obj).updateFaces(); - } - }; - } - - public void setAspectRatio(float ratio) { - mAspectRatio = ratio; - } - - public void setSpotlightRatio(float ratioX, float ratioY) { - mSpotlightRatioX = ratioX; - mSpotlightRatioY = ratioY; - } - - @Override - public void onLayout(boolean changed, int l, int t, int r, int b) { - int width = r - l; - int height = b - t; - - mFaceDetectionView.layout(0, 0, width, height); - mHighlightRectangle.layout(0, 0, width, height); - mImageView.layout(0, 0, width, height); - if (mImageHeight != SIZE_UNKNOWN) { - mAnimation.initialize(); - if (mHighlightRectangle.getVisibility() == GLView.VISIBLE) { - mAnimation.parkNow( - mHighlightRectangle.mHighlightRect); - } - } - } - - private boolean setImageViewPosition(int centerX, int centerY, float scale) { - int inverseX = mImageWidth - centerX; - int inverseY = mImageHeight - centerY; - TileImageView t = mImageView; - int rotation = mImageRotation; - switch (rotation) { - case 0: return t.setPosition(centerX, centerY, scale, 0); - case 90: return t.setPosition(centerY, inverseX, scale, 90); - case 180: return t.setPosition(inverseX, inverseY, scale, 180); - case 270: return t.setPosition(inverseY, centerX, scale, 270); - default: throw new IllegalArgumentException(String.valueOf(rotation)); - } - } - - @Override - public void render(GLCanvas canvas) { - AnimationController a = mAnimation; - if (a.calculate(AnimationTime.get())) invalidate(); - setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale()); - super.render(canvas); - } - - @Override - public void renderBackground(GLCanvas canvas) { - canvas.clearBuffer(); - } - - public RectF getCropRectangle() { - if (mHighlightRectangle.getVisibility() == GLView.INVISIBLE) return null; - RectF rect = mHighlightRectangle.mHighlightRect; - RectF result = new RectF(rect.left * mImageWidth, rect.top * mImageHeight, - rect.right * mImageWidth, rect.bottom * mImageHeight); - return result; - } - - public int getImageWidth() { - return mImageWidth; - } - - public int getImageHeight() { - return mImageHeight; - } - - private class FaceHighlightView extends GLView { - private static final int INDEX_NONE = -1; - private ArrayList<RectF> mFaces = new ArrayList<RectF>(); - private RectF mRect = new RectF(); - private int mPressedFaceIndex = INDEX_NONE; - - public void addFace(RectF faceRect) { - mFaces.add(faceRect); - invalidate(); - } - - private void renderFace(GLCanvas canvas, RectF face, boolean pressed) { - GL11 gl = canvas.getGLInstance(); - if (pressed) { - gl.glEnable(GL11.GL_STENCIL_TEST); - gl.glClear(GL11.GL_STENCIL_BUFFER_BIT); - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); - gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1); - } - - RectF r = mAnimation.mapRect(face, mRect); - canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT); - canvas.drawRect(r.left, r.top, r.width(), r.height(), mFacePaint); - - if (pressed) { - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); - } - } - - @Override - protected void renderBackground(GLCanvas canvas) { - ArrayList<RectF> faces = mFaces; - for (int i = 0, n = faces.size(); i < n; ++i) { - renderFace(canvas, faces.get(i), i == mPressedFaceIndex); - } - - GL11 gl = canvas.getGLInstance(); - if (mPressedFaceIndex != INDEX_NONE) { - gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); - canvas.fillRect(0, 0, getWidth(), getHeight(), 0x66000000); - gl.glDisable(GL11.GL_STENCIL_TEST); - } - } - - private void setPressedFace(int index) { - if (mPressedFaceIndex == index) return; - mPressedFaceIndex = index; - invalidate(); - } - - private int getFaceIndexByPosition(float x, float y) { - ArrayList<RectF> faces = mFaces; - for (int i = 0, n = faces.size(); i < n; ++i) { - RectF r = mAnimation.mapRect(faces.get(i), mRect); - if (r.contains(x, y)) return i; - } - return INDEX_NONE; - } - - @Override - protected boolean onTouch(MotionEvent event) { - float x = event.getX(); - float y = event.getY(); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_MOVE: { - setPressedFace(getFaceIndexByPosition(x, y)); - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - int index = mPressedFaceIndex; - setPressedFace(INDEX_NONE); - if (index != INDEX_NONE) { - mHighlightRectangle.setRectangle(mFaces.get(index)); - mHighlightRectangle.setVisibility(GLView.VISIBLE); - setVisibility(GLView.INVISIBLE); - } - } - } - return true; - } - } - - private class AnimationController extends Animation { - private int mCurrentX; - private int mCurrentY; - private float mCurrentScale; - private int mStartX; - private int mStartY; - private float mStartScale; - private int mTargetX; - private int mTargetY; - private float mTargetScale; - - public AnimationController() { - setDuration(ANIMATION_DURATION); - setInterpolator(new DecelerateInterpolator(4)); - } - - public void initialize() { - mCurrentX = mImageWidth / 2; - mCurrentY = mImageHeight / 2; - mCurrentScale = Math.min(2, Math.min( - (float) getWidth() / mImageWidth, - (float) getHeight() / mImageHeight)); - } - - public void startParkingAnimation(RectF highlight) { - RectF r = mAnimation.mapRect(highlight, new RectF()); - int width = getWidth(); - int height = getHeight(); - - float wr = r.width() / width; - float hr = r.height() / height; - final int d = ANIMATION_TRIGGER; - if (wr >= MIN_SELECTION_RATIO && wr < MAX_SELECTION_RATIO - && hr >= MIN_SELECTION_RATIO && hr < MAX_SELECTION_RATIO - && r.left >= d && r.right < width - d - && r.top >= d && r.bottom < height - d) return; - - mStartX = mCurrentX; - mStartY = mCurrentY; - mStartScale = mCurrentScale; - calculateTarget(highlight); - start(); - } - - public void parkNow(RectF highlight) { - calculateTarget(highlight); - forceStop(); - mStartX = mCurrentX = mTargetX; - mStartY = mCurrentY = mTargetY; - mStartScale = mCurrentScale = mTargetScale; - } - - public void inverseMapPoint(PointF point) { - float s = mCurrentScale; - point.x = Utils.clamp(((point.x - getWidth() * 0.5f) / s - + mCurrentX) / mImageWidth, 0, 1); - point.y = Utils.clamp(((point.y - getHeight() * 0.5f) / s - + mCurrentY) / mImageHeight, 0, 1); - } - - public RectF mapRect(RectF input, RectF output) { - float offsetX = getWidth() * 0.5f; - float offsetY = getHeight() * 0.5f; - int x = mCurrentX; - int y = mCurrentY; - float s = mCurrentScale; - output.set( - offsetX + (input.left * mImageWidth - x) * s, - offsetY + (input.top * mImageHeight - y) * s, - offsetX + (input.right * mImageWidth - x) * s, - offsetY + (input.bottom * mImageHeight - y) * s); - return output; - } - - @Override - protected void onCalculate(float progress) { - mCurrentX = Math.round(mStartX + (mTargetX - mStartX) * progress); - mCurrentY = Math.round(mStartY + (mTargetY - mStartY) * progress); - mCurrentScale = mStartScale + (mTargetScale - mStartScale) * progress; - - if (mCurrentX == mTargetX && mCurrentY == mTargetY - && mCurrentScale == mTargetScale) forceStop(); - } - - public int getCenterX() { - return mCurrentX; - } - - public int getCenterY() { - return mCurrentY; - } - - public float getScale() { - return mCurrentScale; - } - - private void calculateTarget(RectF highlight) { - float width = getWidth(); - float height = getHeight(); - - if (mImageWidth != SIZE_UNKNOWN) { - float minScale = Math.min(width / mImageWidth, height / mImageHeight); - float scale = Utils.clamp(SELECTION_RATIO * Math.min( - width / (highlight.width() * mImageWidth), - height / (highlight.height() * mImageHeight)), minScale, 2f); - int centerX = Math.round( - mImageWidth * (highlight.left + highlight.right) * 0.5f); - int centerY = Math.round( - mImageHeight * (highlight.top + highlight.bottom) * 0.5f); - - if (Math.round(mImageWidth * scale) > width) { - int limitX = Math.round(width * 0.5f / scale); - centerX = Math.round( - (highlight.left + highlight.right) * mImageWidth / 2); - centerX = Utils.clamp(centerX, limitX, mImageWidth - limitX); - } else { - centerX = mImageWidth / 2; - } - if (Math.round(mImageHeight * scale) > height) { - int limitY = Math.round(height * 0.5f / scale); - centerY = Math.round( - (highlight.top + highlight.bottom) * mImageHeight / 2); - centerY = Utils.clamp(centerY, limitY, mImageHeight - limitY); - } else { - centerY = mImageHeight / 2; - } - mTargetX = centerX; - mTargetY = centerY; - mTargetScale = scale; - } - } - - } - - private class HighlightRectangle extends GLView { - private RectF mHighlightRect = new RectF(0.25f, 0.25f, 0.75f, 0.75f); - private RectF mTempRect = new RectF(); - private PointF mTempPoint = new PointF(); - - private ResourceTexture mArrow; - - private int mMovingEdges = 0; - private float mReferenceX; - private float mReferenceY; - - public HighlightRectangle() { - mArrow = new ResourceTexture(mActivity.getAndroidContext(), - R.drawable.camera_crop_holo); - } - - public void setInitRectangle() { - float targetRatio = mAspectRatio == UNSPECIFIED - ? 1f - : mAspectRatio * mImageHeight / mImageWidth; - float w = SELECTION_RATIO / 2f; - float h = SELECTION_RATIO / 2f; - if (targetRatio > 1) { - h = w / targetRatio; - } else { - w = h * targetRatio; - } - mHighlightRect.set(0.5f - w, 0.5f - h, 0.5f + w, 0.5f + h); - } - - public void setRectangle(RectF faceRect) { - mHighlightRect.set(faceRect); - mAnimation.startParkingAnimation(faceRect); - invalidate(); - } - - private void moveEdges(MotionEvent event) { - float scale = mAnimation.getScale(); - float dx = (event.getX() - mReferenceX) / scale / mImageWidth; - float dy = (event.getY() - mReferenceY) / scale / mImageHeight; - mReferenceX = event.getX(); - mReferenceY = event.getY(); - RectF r = mHighlightRect; - - if ((mMovingEdges & MOVE_BLOCK) != 0) { - dx = Utils.clamp(dx, -r.left, 1 - r.right); - dy = Utils.clamp(dy, -r.top , 1 - r.bottom); - r.top += dy; - r.bottom += dy; - r.left += dx; - r.right += dx; - } else { - PointF point = mTempPoint; - point.set(mReferenceX, mReferenceY); - mAnimation.inverseMapPoint(point); - float left = r.left + MIN_SELECTION_LENGTH / mImageWidth; - float right = r.right - MIN_SELECTION_LENGTH / mImageWidth; - float top = r.top + MIN_SELECTION_LENGTH / mImageHeight; - float bottom = r.bottom - MIN_SELECTION_LENGTH / mImageHeight; - if ((mMovingEdges & MOVE_RIGHT) != 0) { - r.right = Utils.clamp(point.x, left, 1f); - } - if ((mMovingEdges & MOVE_LEFT) != 0) { - r.left = Utils.clamp(point.x, 0, right); - } - if ((mMovingEdges & MOVE_TOP) != 0) { - r.top = Utils.clamp(point.y, 0, bottom); - } - if ((mMovingEdges & MOVE_BOTTOM) != 0) { - r.bottom = Utils.clamp(point.y, top, 1f); - } - if (mAspectRatio != UNSPECIFIED) { - float targetRatio = mAspectRatio * mImageHeight / mImageWidth; - if (r.width() / r.height() > targetRatio) { - float height = r.width() / targetRatio; - if ((mMovingEdges & MOVE_BOTTOM) != 0) { - r.bottom = Utils.clamp(r.top + height, top, 1f); - } else { - r.top = Utils.clamp(r.bottom - height, 0, bottom); - } - } else { - float width = r.height() * targetRatio; - if ((mMovingEdges & MOVE_LEFT) != 0) { - r.left = Utils.clamp(r.right - width, 0, right); - } else { - r.right = Utils.clamp(r.left + width, left, 1f); - } - } - if (r.width() / r.height() > targetRatio) { - float width = r.height() * targetRatio; - if ((mMovingEdges & MOVE_LEFT) != 0) { - r.left = Utils.clamp(r.right - width, 0, right); - } else { - r.right = Utils.clamp(r.left + width, left, 1f); - } - } else { - float height = r.width() / targetRatio; - if ((mMovingEdges & MOVE_BOTTOM) != 0) { - r.bottom = Utils.clamp(r.top + height, top, 1f); - } else { - r.top = Utils.clamp(r.bottom - height, 0, bottom); - } - } - } - } - invalidate(); - } - - private void setMovingEdges(MotionEvent event) { - RectF r = mAnimation.mapRect(mHighlightRect, mTempRect); - float x = event.getX(); - float y = event.getY(); - - if (x > r.left + TOUCH_TOLERANCE && x < r.right - TOUCH_TOLERANCE - && y > r.top + TOUCH_TOLERANCE && y < r.bottom - TOUCH_TOLERANCE) { - mMovingEdges = MOVE_BLOCK; - return; - } - - boolean inVerticalRange = (r.top - TOUCH_TOLERANCE) <= y - && y <= (r.bottom + TOUCH_TOLERANCE); - boolean inHorizontalRange = (r.left - TOUCH_TOLERANCE) <= x - && x <= (r.right + TOUCH_TOLERANCE); - - if (inVerticalRange) { - boolean left = Math.abs(x - r.left) <= TOUCH_TOLERANCE; - boolean right = Math.abs(x - r.right) <= TOUCH_TOLERANCE; - if (left && right) { - left = Math.abs(x - r.left) < Math.abs(x - r.right); - right = !left; - } - if (left) mMovingEdges |= MOVE_LEFT; - if (right) mMovingEdges |= MOVE_RIGHT; - if (mAspectRatio != UNSPECIFIED && inHorizontalRange) { - mMovingEdges |= (y > - (r.top + r.bottom) / 2) ? MOVE_BOTTOM : MOVE_TOP; - } - } - if (inHorizontalRange) { - boolean top = Math.abs(y - r.top) <= TOUCH_TOLERANCE; - boolean bottom = Math.abs(y - r.bottom) <= TOUCH_TOLERANCE; - if (top && bottom) { - top = Math.abs(y - r.top) < Math.abs(y - r.bottom); - bottom = !top; - } - if (top) mMovingEdges |= MOVE_TOP; - if (bottom) mMovingEdges |= MOVE_BOTTOM; - if (mAspectRatio != UNSPECIFIED && inVerticalRange) { - mMovingEdges |= (x > - (r.left + r.right) / 2) ? MOVE_RIGHT : MOVE_LEFT; - } - } - } - - @Override - protected boolean onTouch(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: { - mReferenceX = event.getX(); - mReferenceY = event.getY(); - setMovingEdges(event); - invalidate(); - return true; - } - case MotionEvent.ACTION_MOVE: - moveEdges(event); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - mMovingEdges = 0; - mAnimation.startParkingAnimation(mHighlightRect); - invalidate(); - return true; - } - } - return true; - } - - @Override - protected void renderBackground(GLCanvas canvas) { - RectF r = mAnimation.mapRect(mHighlightRect, mTempRect); - drawHighlightRectangle(canvas, r); - - float centerY = (r.top + r.bottom) / 2; - float centerX = (r.left + r.right) / 2; - boolean notMoving = mMovingEdges == 0; - if ((mMovingEdges & MOVE_RIGHT) != 0 || notMoving) { - mArrow.draw(canvas, - Math.round(r.right - mArrow.getWidth() / 2), - Math.round(centerY - mArrow.getHeight() / 2)); - } - if ((mMovingEdges & MOVE_LEFT) != 0 || notMoving) { - mArrow.draw(canvas, - Math.round(r.left - mArrow.getWidth() / 2), - Math.round(centerY - mArrow.getHeight() / 2)); - } - if ((mMovingEdges & MOVE_TOP) != 0 || notMoving) { - mArrow.draw(canvas, - Math.round(centerX - mArrow.getWidth() / 2), - Math.round(r.top - mArrow.getHeight() / 2)); - } - if ((mMovingEdges & MOVE_BOTTOM) != 0 || notMoving) { - mArrow.draw(canvas, - Math.round(centerX - mArrow.getWidth() / 2), - Math.round(r.bottom - mArrow.getHeight() / 2)); - } - } - - private void drawHighlightRectangle(GLCanvas canvas, RectF r) { - GL11 gl = canvas.getGLInstance(); - gl.glLineWidth(3.0f); - gl.glEnable(GL11.GL_LINE_SMOOTH); - - gl.glEnable(GL11.GL_STENCIL_TEST); - gl.glClear(GL11.GL_STENCIL_BUFFER_BIT); - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); - gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1); - - if (mSpotlightRatioX == 0 || mSpotlightRatioY == 0) { - canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT); - canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint); - } else { - float sx = r.width() * mSpotlightRatioX; - float sy = r.height() * mSpotlightRatioY; - float cx = r.centerX(); - float cy = r.centerY(); - - canvas.fillRect(cx - sx / 2, cy - sy / 2, sx, sy, Color.TRANSPARENT); - canvas.drawRect(cx - sx / 2, cy - sy / 2, sx, sy, mPaint); - canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint); - - gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); - - canvas.drawRect(cx - sy / 2, cy - sx / 2, sy, sx, mPaint); - canvas.fillRect(cx - sy / 2, cy - sx / 2, sy, sx, Color.TRANSPARENT); - canvas.fillRect(r.left, r.top, r.width(), r.height(), 0x80000000); - } - - gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); - - canvas.fillRect(0, 0, getWidth(), getHeight(), 0xA0000000); - - gl.glDisable(GL11.GL_STENCIL_TEST); - } - } - - private class DetectFaceTask extends Thread { - private final FaceDetector.Face[] mFaces = new FaceDetector.Face[MAX_FACE_COUNT]; - private final Bitmap mFaceBitmap; - private int mFaceCount; - - public DetectFaceTask(Bitmap bitmap) { - mFaceBitmap = bitmap; - setName("face-detect"); - } - - @Override - public void run() { - Bitmap bitmap = mFaceBitmap; - FaceDetector detector = new FaceDetector( - bitmap.getWidth(), bitmap.getHeight(), MAX_FACE_COUNT); - mFaceCount = detector.findFaces(bitmap, mFaces); - mMainHandler.sendMessage( - mMainHandler.obtainMessage(MSG_UPDATE_FACES, this)); - } - - private RectF getFaceRect(FaceDetector.Face face) { - PointF point = new PointF(); - face.getMidPoint(point); - - int width = mFaceBitmap.getWidth(); - int height = mFaceBitmap.getHeight(); - float rx = face.eyesDistance() * FACE_EYE_RATIO; - float ry = rx; - float aspect = mAspectRatio; - if (aspect != UNSPECIFIED) { - if (aspect > 1) { - rx = ry * aspect; - } else { - ry = rx / aspect; - } - } - - RectF r = new RectF( - point.x - rx, point.y - ry, point.x + rx, point.y + ry); - r.intersect(0, 0, width, height); - - if (aspect != UNSPECIFIED) { - if (r.width() / r.height() > aspect) { - float w = r.height() * aspect; - r.left = (r.left + r.right - w) * 0.5f; - r.right = r.left + w; - } else { - float h = r.width() / aspect; - r.top = (r.top + r.bottom - h) * 0.5f; - r.bottom = r.top + h; - } - } - - r.left /= width; - r.right /= width; - r.top /= height; - r.bottom /= height; - return r; - } - - public void updateFaces() { - if (mFaceCount > 1) { - for (int i = 0, n = mFaceCount; i < n; ++i) { - mFaceDetectionView.addFace(getFaceRect(mFaces[i])); - } - mFaceDetectionView.setVisibility(GLView.VISIBLE); - Toast.makeText(mActivity.getAndroidContext(), - R.string.multiface_crop_help, Toast.LENGTH_SHORT).show(); - } else if (mFaceCount == 1) { - mFaceDetectionView.setVisibility(GLView.INVISIBLE); - mHighlightRectangle.setRectangle(getFaceRect(mFaces[0])); - mHighlightRectangle.setVisibility(GLView.VISIBLE); - } else /*mFaceCount == 0*/ { - mHighlightRectangle.setInitRectangle(); - mHighlightRectangle.setVisibility(GLView.VISIBLE); - } - } - } - - public void setDataModel(TileImageView.Model dataModel, int rotation) { - if (((rotation / 90) & 0x01) != 0) { - mImageWidth = dataModel.getImageHeight(); - mImageHeight = dataModel.getImageWidth(); - } else { - mImageWidth = dataModel.getImageWidth(); - mImageHeight = dataModel.getImageHeight(); - } - - mImageRotation = rotation; - - mImageView.setModel(dataModel); - mAnimation.initialize(); - } - - public void detectFaces(Bitmap bitmap) { - int rotation = mImageRotation; - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - float scale = FloatMath.sqrt((float) FACE_PIXEL_COUNT / (width * height)); - - // faceBitmap is a correctly rotated bitmap, as viewed by a user. - Bitmap faceBitmap; - if (((rotation / 90) & 1) == 0) { - int w = (Math.round(width * scale) & ~1); // must be even - int h = Math.round(height * scale); - faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565); - Canvas canvas = new Canvas(faceBitmap); - canvas.rotate(rotation, w / 2, h / 2); - canvas.scale((float) w / width, (float) h / height); - canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG)); - } else { - int w = (Math.round(height * scale) & ~1); // must be even - int h = Math.round(width * scale); - faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565); - Canvas canvas = new Canvas(faceBitmap); - canvas.translate(w / 2, h / 2); - canvas.rotate(rotation); - canvas.translate(-h / 2, -w / 2); - canvas.scale((float) w / height, (float) h / width); - canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG)); - } - new DetectFaceTask(faceBitmap).start(); - } - - public void initializeHighlightRectangle() { - mHighlightRectangle.setInitRectangle(); - mHighlightRectangle.setVisibility(GLView.VISIBLE); - } - - public void resume() { - mImageView.prepareTextures(); - } - - public void pause() { - mImageView.freeTextures(); - } -} - diff --git a/src/com/android/gallery3d/ui/DetailsHelper.java b/src/com/android/gallery3d/ui/DetailsHelper.java index 301601156..47296f655 100644 --- a/src/com/android/gallery3d/ui/DetailsHelper.java +++ b/src/com/android/gallery3d/ui/DetailsHelper.java @@ -16,6 +16,8 @@ package com.android.gallery3d.ui; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.view.View.MeasureSpec; import com.android.gallery3d.R; @@ -44,6 +46,10 @@ public class DetailsHelper { public void hide(); } + public interface ResolutionResolvingListener { + public void onResolutionAvailable(int width, int height); + } + public DetailsHelper(AbstractGalleryActivity activity, GLView rootPane, DetailsSource source) { mContainer = new DialogDetailsView(activity, source); } @@ -75,6 +81,12 @@ public class DetailsHelper { return sAddressResolver.resolveAddress(latlng, listener); } + public static void resolveResolution(String path, ResolutionResolvingListener listener) { + Bitmap bitmap = BitmapFactory.decodeFile(path); + if (bitmap == null) return; + listener.onResolutionAvailable(bitmap.getWidth(), bitmap.getHeight()); + } + public static void pause() { if (sAddressResolver != null) sAddressResolver.cancel(); } diff --git a/src/com/android/gallery3d/ui/DialogDetailsView.java b/src/com/android/gallery3d/ui/DialogDetailsView.java index 8d96b821a..058c03654 100644 --- a/src/com/android/gallery3d/ui/DialogDetailsView.java +++ b/src/com/android/gallery3d/ui/DialogDetailsView.java @@ -37,6 +37,7 @@ import com.android.gallery3d.ui.DetailsAddressResolver.AddressResolvingListener; import com.android.gallery3d.ui.DetailsHelper.CloseListener; import com.android.gallery3d.ui.DetailsHelper.DetailsSource; import com.android.gallery3d.ui.DetailsHelper.DetailsViewContainer; +import com.android.gallery3d.ui.DetailsHelper.ResolutionResolvingListener; import java.util.ArrayList; import java.util.Map.Entry; @@ -111,9 +112,13 @@ public class DialogDetailsView implements DetailsViewContainer { }); } - private class DetailsAdapter extends BaseAdapter implements AddressResolvingListener { + + private class DetailsAdapter extends BaseAdapter + implements AddressResolvingListener, ResolutionResolvingListener { private final ArrayList<String> mItems; private int mLocationIndex; + private int mWidthIndex = -1; + private int mHeightIndex = -1; public DetailsAdapter(MediaDetails details) { Context context = mActivity.getAndroidContext(); @@ -123,6 +128,8 @@ public class DialogDetailsView implements DetailsViewContainer { } private void setDetails(Context context, MediaDetails details) { + boolean resolutionIsValid = true; + String path = null; for (Entry<Integer, Object> detail : details) { String value; switch (detail.getKey()) { @@ -170,6 +177,26 @@ public class DialogDetailsView implements DetailsViewContainer { } break; } + case MediaDetails.INDEX_WIDTH: + mWidthIndex = mItems.size(); + value = detail.getValue().toString(); + if (value.equalsIgnoreCase("0")) { + value = context.getString(R.string.unknown); + resolutionIsValid = false; + } + break; + case MediaDetails.INDEX_HEIGHT: { + mHeightIndex = mItems.size(); + value = detail.getValue().toString(); + if (value.equalsIgnoreCase("0")) { + value = context.getString(R.string.unknown); + resolutionIsValid = false; + } + break; + } + case MediaDetails.INDEX_PATH: + // Get the path and then fall through to the default case + path = detail.getValue().toString(); default: { Object valueObj = detail.getValue(); // This shouldn't happen, log its key to help us diagnose the problem. @@ -189,6 +216,9 @@ public class DialogDetailsView implements DetailsViewContainer { context, key), value); } mItems.add(value); + if (!resolutionIsValid) { + DetailsHelper.resolveResolution(path, this); + } } } @@ -235,6 +265,20 @@ public class DialogDetailsView implements DetailsViewContainer { mItems.set(mLocationIndex, address); notifyDataSetChanged(); } + + @Override + public void onResolutionAvailable(int width, int height) { + if (width == 0 || height == 0) return; + // Update the resolution with the new width and height + Context context = mActivity.getAndroidContext(); + String widthString = String.format("%s: %d", DetailsHelper.getDetailsName( + context, MediaDetails.INDEX_WIDTH), width); + String heightString = String.format("%s: %d", DetailsHelper.getDetailsName( + context, MediaDetails.INDEX_HEIGHT), height); + mItems.set(mWidthIndex, String.valueOf(widthString)); + mItems.set(mHeightIndex, String.valueOf(heightString)); + notifyDataSetChanged(); + } } @Override diff --git a/src/com/android/gallery3d/ui/ExtTexture.java b/src/com/android/gallery3d/ui/ExtTexture.java index eac504fe5..180a89dce 100644 --- a/src/com/android/gallery3d/ui/ExtTexture.java +++ b/src/com/android/gallery3d/ui/ExtTexture.java @@ -16,49 +16,21 @@ package com.android.gallery3d.ui; -import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11Ext; // ExtTexture is a texture whose content comes from a external texture. // Before drawing, setSize() should be called. public class ExtTexture extends BasicTexture { - private static int[] sTextureId = new int[1]; - private static float[] sCropRect = new float[4]; private int mTarget; public ExtTexture(int target) { - GLId.glGenTextures(1, sTextureId, 0); - mId = sTextureId[0]; + GLId glId = GLCanvas.getGLId(); + mId = glId.generateTexture(); mTarget = target; } private void uploadToCanvas(GLCanvas canvas) { - GL11 gl = canvas.getGLInstance(); - - int width = getWidth(); - int height = getHeight(); - // Define a vertically flipped crop rectangle for OES_draw_texture. - // The four values in sCropRect are: left, bottom, width, and - // height. Negative value of width or height means flip. - sCropRect[0] = 0; - sCropRect[1] = height; - sCropRect[2] = width; - sCropRect[3] = -height; - - // Set texture parameters. - gl.glBindTexture(mTarget, mId); - gl.glTexParameterfv(mTarget, - GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); - gl.glTexParameteri(mTarget, - GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(mTarget, - GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameterf(mTarget, - GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - gl.glTexParameterf(mTarget, - GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - + canvas.setTextureParameters(this); setAssociatedCanvas(canvas); mState = STATE_LOADED; } diff --git a/src/com/android/gallery3d/ui/GLCanvas.java b/src/com/android/gallery3d/ui/GLCanvas.java index 6f8baef7e..1dbee5dd9 100644 --- a/src/com/android/gallery3d/ui/GLCanvas.java +++ b/src/com/android/gallery3d/ui/GLCanvas.java @@ -16,8 +16,11 @@ package com.android.gallery3d.ui; +import android.graphics.Bitmap; import android.graphics.RectF; +import com.android.gallery3d.common.ApiHelper; + import javax.microedition.khronos.opengles.GL11; // @@ -26,107 +29,236 @@ import javax.microedition.khronos.opengles.GL11; // When a rectangle is specified in this interface, it means the region // [x, x+width) * [y, y+height) // -public interface GLCanvas { +public abstract class GLCanvas { + public enum Blending { + Additive, Mix, + } + + private static GLCanvas sInstance = instantiateCanvas(); + private static GLId sGLId = instantiateGLId(); + + public static GLId getGLId() { + return sGLId; + } + + public static GLCanvas getInstance() { + return sInstance; + } + + private static GLId instantiateGLId() { + return ApiHelper.HAS_GLES20_REQUIRED ? (GLES20Canvas) sInstance : new GLIdImpl(); + } + + private static GLCanvas instantiateCanvas() { + return ApiHelper.HAS_GLES20_REQUIRED ? new GLES20Canvas() : new GLCanvasImpl(); + } + + public static int getEGLContextClientVersion() { + return ApiHelper.HAS_GLES20_REQUIRED ? 2 : 1; + } + + public abstract void initialize(GL11 gl); + // Tells GLCanvas the size of the underlying GL surface. This should be // called before first drawing and when the size of GL surface is changed. // This is called by GLRoot and should not be called by the clients // who only want to draw on the GLCanvas. Both width and height must be // nonnegative. - public void setSize(int width, int height); + public abstract void setSize(int width, int height); // Clear the drawing buffers. This should only be used by GLRoot. - public void clearBuffer(); - public void clearBuffer(float[] argb); + public abstract void clearBuffer(); + + public abstract void clearBuffer(float[] argb); // Sets and gets the current alpha, alpha must be in [0, 1]. - public void setAlpha(float alpha); - public float getAlpha(); + public abstract void setAlpha(float alpha); + + public abstract float getAlpha(); // (current alpha) = (current alpha) * alpha - public void multiplyAlpha(float alpha); + public abstract void multiplyAlpha(float alpha); // Change the current transform matrix. - public void translate(float x, float y, float z); - public void translate(float x, float y); - public void scale(float sx, float sy, float sz); - public void rotate(float angle, float x, float y, float z); - public void multiplyMatrix(float[] mMatrix, int offset); + public abstract void translate(float x, float y, float z); + + public abstract void translate(float x, float y); + + public abstract void scale(float sx, float sy, float sz); + + public abstract void rotate(float angle, float x, float y, float z); + + public abstract void multiplyMatrix(float[] mMatrix, int offset); // Pushes the configuration state (matrix, and alpha) onto // a private stack. - public void save(); + public abstract void save(); // Same as save(), but only save those specified in saveFlags. - public void save(int saveFlags); + public abstract void save(int saveFlags); public static final int SAVE_FLAG_ALL = 0xFFFFFFFF; public static final int SAVE_FLAG_ALPHA = 0x01; public static final int SAVE_FLAG_MATRIX = 0x02; + public static final int SAVE_FLAG_BLEND = 0x04; // Pops from the top of the stack as current configuration state (matrix, // alpha, and clip). This call balances a previous call to save(), and is // used to remove all modifications to the configuration state since the // last save call. - public void restore(); + public abstract void restore(); // Draws a line using the specified paint from (x1, y1) to (x2, y2). // (Both end points are included). - public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint); + public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint); // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2). // (Both end points are included). - public void drawRect(float x1, float y1, float x2, float y2, GLPaint paint); + public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint); // Fills the specified rectangle with the specified color. - public void fillRect(float x, float y, float width, float height, int color); + public abstract void fillRect(float x, float y, float width, float height, int color); // Draws a texture to the specified rectangle. - public void drawTexture( + public abstract void drawTexture( BasicTexture texture, int x, int y, int width, int height); - public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, + + public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, int uvBuffer, int indexBuffer, int indexCount); // Draws the source rectangle part of the texture to the target rectangle. - public void drawTexture(BasicTexture texture, RectF source, RectF target); + public abstract void drawTexture(BasicTexture texture, RectF source, RectF target); // Draw a texture with a specified texture transform. - public void drawTexture(BasicTexture texture, float[] mTextureTransform, + public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform, int x, int y, int w, int h); // Draw two textures to the specified rectangle. The actual texture used is // from * (1 - ratio) + to * ratio // The two textures must have the same size. - public void drawMixed(BasicTexture from, int toColor, + public abstract void drawMixed(BasicTexture from, int toColor, float ratio, int x, int y, int w, int h); // Draw a region of a texture and a specified color to the specified // rectangle. The actual color used is from * (1 - ratio) + to * ratio. // The region of the texture is defined by parameter "src". The target // rectangle is specified by parameter "target". - public void drawMixed(BasicTexture from, int toColor, + public abstract void drawMixed(BasicTexture from, int toColor, float ratio, RectF src, RectF target); - // Gets the underlying GL instance. This is used only when direct access to - // GL is needed. - public GL11 getGLInstance(); - // Unloads the specified texture from the canvas. The resource allocated // to draw the texture will be released. The specified texture will return // to the unloaded state. This function should be called only from // BasicTexture or its descendant - public boolean unloadTexture(BasicTexture texture); + public abstract boolean unloadTexture(BasicTexture texture); // Delete the specified buffer object, similar to unloadTexture. - public void deleteBuffer(int bufferId); + public abstract void deleteBuffer(int bufferId); // Delete the textures and buffers in GL side. This function should only be // called in the GL thread. - public void deleteRecycledResources(); + public abstract void deleteRecycledResources(); // Dump statistics information and clear the counters. For debug only. - public void dumpStatisticsAndClear(); + public abstract void dumpStatisticsAndClear(); + + public abstract void beginRenderTarget(RawTexture texture); + + public abstract void endRenderTarget(); + + /** + * Sets texture parameters to use GL_CLAMP_TO_EDGE for both + * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be + * GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER. + * bindTexture() must be called prior to this. + * + * @param texture The texture to set parameters on. + */ + public abstract void setTextureParameters(BasicTexture texture); + + /** + * Initializes the texture to a size by calling texImage2D on it. + * + * @param texture The texture to initialize the size. + * @param format The texture format (e.g. GL_RGBA) + * @param type The texture type (e.g. GL_UNSIGNED_BYTE) + */ + public abstract void initializeTextureSize(BasicTexture texture, int format, int type); + + /** + * Initializes the texture to a size by calling texImage2D on it. + * + * @param texture The texture to initialize the size. + * @param bitmap The bitmap to initialize the bitmap with. + */ + public abstract void initializeTexture(BasicTexture texture, Bitmap bitmap); + + /** + * Calls glTexSubImage2D to upload a bitmap to the texture. + * + * @param texture The target texture to write to. + * @param xOffset Specifies a texel offset in the x direction within the + * texture array. + * @param yOffset Specifies a texel offset in the y direction within the + * texture array. + * @param format The texture format (e.g. GL_RGBA) + * @param type The texture type (e.g. GL_UNSIGNED_BYTE) + */ + public abstract void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, + Bitmap bitmap, + int format, int type); + + /** + * Generates buffers and uploads the buffer data. + * + * @param buffer The buffer to upload + * @return The buffer ID that was generated. + */ + public abstract int uploadBuffer(java.nio.FloatBuffer buffer); + + /** + * Generates buffers and uploads the element array buffer data. + * + * @param buffer The buffer to upload + * @return The buffer ID that was generated. + */ + public abstract int uploadBuffer(java.nio.ByteBuffer buffer); + + /** + * Sets the blending algorithm if a texture is not opaque. + * + * @param blending Either mixing (overlay) or adding a texture. + */ + public abstract void setBlending(Blending blending); + + /** + * Enable stencil test + */ + public abstract void enableStencil(); + + /** + * Disable stencil. + */ + public abstract void disableStencil(); + + /** + * Clears the stencil so that a new stencil can be generated. + */ + public abstract void clearStencilBuffer(); - public void beginRenderTarget(RawTexture texture); + /** + * Start/stop updating the stencil buffer. + * + * @param update True if the stencil should be updated, false otherwise. + */ + public abstract void updateStencil(boolean update); - public void endRenderTarget(); + /** + * Changes how the stencil buffer is used. + * + * @param onlyOutside If true, only the area outside the stencil can be + * changed. If false, the area inside the stencil can be drawn to + * as well. + */ + public abstract void drawOnlyOutsideStencil(boolean onlyOutside); } diff --git a/src/com/android/gallery3d/ui/GLCanvasImpl.java b/src/com/android/gallery3d/ui/GLCanvasImpl.java index 45903b3cd..54c231c3b 100644 --- a/src/com/android/gallery3d/ui/GLCanvasImpl.java +++ b/src/com/android/gallery3d/ui/GLCanvasImpl.java @@ -16,13 +16,16 @@ package com.android.gallery3d.ui; +import android.graphics.Bitmap; import android.graphics.RectF; import android.opengl.GLU; +import android.opengl.GLUtils; import android.opengl.Matrix; import com.android.gallery3d.common.Utils; import com.android.gallery3d.util.IntArray; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; @@ -33,7 +36,7 @@ import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11Ext; import javax.microedition.khronos.opengles.GL11ExtensionPack; -public class GLCanvasImpl implements GLCanvas { +public class GLCanvasImpl extends GLCanvas { @SuppressWarnings("unused") private static final String TAG = "GLCanvasImp"; @@ -47,7 +50,7 @@ public class GLCanvasImpl implements GLCanvas { 0, 0, 1, 1, // used for drawing a line 0, 0, 0, 1, 1, 1, 1, 0}; // used for drawing the outline of a rectangle - private final GL11 mGL; + private GL11 mGL; private final float mMatrixValues[] = new float[16]; private final float mTextureMatrixValues[] = new float[16]; @@ -60,7 +63,7 @@ public class GLCanvasImpl implements GLCanvas { private int mBoxCoords; - private final GLState mGLState; + private GLState mGLState; private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>(); private float mAlpha; @@ -76,8 +79,10 @@ public class GLCanvasImpl implements GLCanvas { private int mScreenHeight; private boolean mBlendEnabled = true; private int mFrameBuffer[] = new int[1]; + private static float[] sCropRect = new float[4]; private RawTexture mTargetTexture; + private Blending mBlending = Blending.Mix; // Drawing statistics int mCountDrawLine; @@ -86,10 +91,7 @@ public class GLCanvasImpl implements GLCanvas { int mCountTextureRect; int mCountTextureOES; - GLCanvasImpl(GL11 gl) { - mGL = gl; - mGLState = new GLState(gl); - initialize(); + GLCanvasImpl() { } @Override @@ -141,16 +143,18 @@ public class GLCanvasImpl implements GLCanvas { return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); } - private void initialize() { - GL11 gl = mGL; - + @Override + public void initialize(GL11 gl) { + mGL = gl; + mGLState = new GLState(gl); // First create an nio buffer, then create a VBO from it. int size = BOX_COORDINATES.length * Float.SIZE / Byte.SIZE; FloatBuffer xyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer(); xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0); int[] name = new int[1]; - GLId.glGenBuffers(1, name, 0); + GLId glId = getGLId(); + glId.glGenBuffers(1, name, 0); mBoxCoords = name[0]; gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords); @@ -684,11 +688,6 @@ public class GLCanvasImpl implements GLCanvas { } @Override - public GL11 getGLInstance() { - return mGL; - } - - @Override public void clearBuffer(float[] argb) { if(argb != null && argb.length == 4) { mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]); @@ -748,14 +747,15 @@ public class GLCanvasImpl implements GLCanvas { public void deleteRecycledResources() { synchronized (mUnboundTextures) { IntArray ids = mUnboundTextures; + GLId glId = getGLId(); if (ids.size() > 0) { - GLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0); + glId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0); ids.clear(); } ids = mDeleteBuffers; if (ids.size() > 0) { - GLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0); + glId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0); ids.clear(); } } @@ -776,6 +776,11 @@ public class GLCanvasImpl implements GLCanvas { config.mAlpha = -1; } + if ((saveFlags & SAVE_FLAG_BLEND) != 0) { + config.mBlending = mBlending; + } else { + config.mBlending = null; + } if ((saveFlags & SAVE_FLAG_MATRIX) != 0) { System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16); @@ -811,6 +816,7 @@ public class GLCanvasImpl implements GLCanvas { private static class ConfigState { float mAlpha; float mMatrix[] = new float[16]; + Blending mBlending; ConfigState mNextFree; public void restore(GLCanvasImpl canvas) { @@ -818,6 +824,9 @@ public class GLCanvasImpl implements GLCanvas { if (mMatrix[0] != Float.NEGATIVE_INFINITY) { System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16); } + if (mBlending != null) { + canvas.setBlending(mBlending); + } } } @@ -847,7 +856,8 @@ public class GLCanvasImpl implements GLCanvas { GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL; if (mTargetTexture == null && texture != null) { - GLId.glGenBuffers(1, mFrameBuffer, 0); + GLId glId = getGLId(); + glId.glGenBuffers(1, mFrameBuffer, 0); gl11ep.glBindFramebufferOES( GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]); } @@ -917,4 +927,110 @@ public class GLCanvasImpl implements GLCanvas { throw new RuntimeException(msg + ":" + Integer.toHexString(status)); } } + + @Override + public void setTextureParameters(BasicTexture texture) { + int width = texture.getWidth(); + int height = texture.getHeight(); + // Define a vertically flipped crop rectangle for OES_draw_texture. + // The four values in sCropRect are: left, bottom, width, and + // height. Negative value of width or height means flip. + sCropRect[0] = 0; + sCropRect[1] = height; + sCropRect[2] = width; + sCropRect[3] = -height; + + // Set texture parameters. + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + mGL.glTexParameterfv(target, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); + mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); + mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); + mGL.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + mGL.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + } + + @Override + public void initializeTextureSize(BasicTexture texture, int format, int type) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + int width = texture.getTextureWidth(); + int height = texture.getTextureHeight(); + mGL.glTexImage2D(target, 0, format, width, height, 0, format, type, null); + } + + @Override + public void initializeTexture(BasicTexture texture, Bitmap bitmap) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + GLUtils.texImage2D(target, 0, bitmap, 0); + } + + @Override + public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, + int format, int type) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type); + } + + @Override + public int uploadBuffer(FloatBuffer buf) { + return uploadBuffer(buf, Float.SIZE / Byte.SIZE); + } + + @Override + public int uploadBuffer(ByteBuffer buf) { + return uploadBuffer(buf, 1); + } + + private int uploadBuffer(Buffer buf, int elementSize) { + int[] bufferIds = new int[1]; + GLId glId = getGLId(); + glId.glGenBuffers(bufferIds.length, bufferIds, 0); + int bufferId = bufferIds[0]; + mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId); + mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf, + GL11.GL_STATIC_DRAW); + return bufferId; + } + + @Override + public void setBlending(Blending blending) { + if (mBlending == blending) { + return; + } + Utils.assertTrue(blending == Blending.Additive || blending == Blending.Mix); + mBlending = blending; + int srcFunc = GL11.GL_ONE; + int dstFunc = (blending == Blending.Additive) ? GL11.GL_ONE : GL11.GL_ONE_MINUS_SRC_ALPHA; + mGL.glBlendFunc(srcFunc, dstFunc); + } + + @Override + public void enableStencil() { + mGL.glEnable(GL11.GL_STENCIL_TEST); + } + + @Override + public void disableStencil() { + mGL.glDisable(GL11.GL_STENCIL_TEST); + } + + @Override + public void clearStencilBuffer() { + mGL.glClear(GL11.GL_STENCIL_BUFFER_BIT); + } + + @Override + public void updateStencil(boolean update) { + int passOp = update ? GL11.GL_REPLACE : GL11.GL_KEEP; + mGL.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, passOp); + } + + @Override + public void drawOnlyOutsideStencil(boolean onlyOutside) { + int func = onlyOutside ? GL11.GL_NOTEQUAL : GL11.GL_ALWAYS; + mGL.glStencilFunc(func, 1, 1); + } } diff --git a/src/com/android/gallery3d/ui/GLES20Canvas.java b/src/com/android/gallery3d/ui/GLES20Canvas.java new file mode 100644 index 000000000..b720a773b --- /dev/null +++ b/src/com/android/gallery3d/ui/GLES20Canvas.java @@ -0,0 +1,1068 @@ +/* + * Copyright (C) 2012 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.ui; + +import android.graphics.Bitmap; +import android.graphics.RectF; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.util.Log; + +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.util.IntArray; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Arrays; + +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11ExtensionPack; + +public class GLES20Canvas extends GLCanvas implements GLId { + // ************** Constants ********************** + private static final String TAG = GLES20Canvas.class.getSimpleName(); + private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE; + private static final float OPAQUE_ALPHA = 0.95f; + + private static final int COORDS_PER_VERTEX = 2; + private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE; + + private static final int COUNT_FILL_VERTEX = 4; + private static final int COUNT_LINE_VERTEX = 2; + private static final int COUNT_RECT_VERTEX = 4; + private static final int OFFSET_FILL_RECT = 0; + private static final int OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX; + private static final int OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX; + + private static final float[] BOX_COORDINATES = { + 0, 0, // Fill rectangle + 1, 0, + 0, 1, + 1, 1, + 0, 0, // Draw line + 1, 1, + 0, 0, // Draw rectangle outline + 0, 1, + 1, 1, + 1, 0, + }; + + private static final String POSITION_ATTRIBUTE = "aPosition"; + private static final String COLOR_UNIFORM = "uColor"; + private static final String MATRIX_UNIFORM = "uMatrix"; + private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix"; + private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler"; + private static final String ALPHA_UNIFORM = "uAlpha"; + private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate"; + + private static final String DRAW_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + "}\n"; + + private static final String DRAW_FRAGMENT_SHADER = "" + + "precision mediump float;\n" + + "uniform vec4 " + COLOR_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = " + COLOR_UNIFORM + ";\n" + + "}\n"; + + private static final String TEXTURE_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + " vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n" + + "}\n"; + + private static final String MESH_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + " vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n" + + "}\n"; + + private static final String TEXTURE_FRAGMENT_SHADER = "" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform float " + ALPHA_UNIFORM + ";\n" + + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n" + + " gl_FragColor.a *= " + ALPHA_UNIFORM + ";\n" + + "}\n"; + + private static final String OES_TEXTURE_FRAGMENT_SHADER = "" + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform float " + ALPHA_UNIFORM + ";\n" + + "uniform samplerExternalOES " + TEXTURE_SAMPLER_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n" + + " gl_FragColor.a *= " + ALPHA_UNIFORM + ";\n" + + "}\n"; + + private static final int INITIAL_RESTORE_STATE_SIZE = 8; + private static final int MATRIX_SIZE = 16; + + // Keep track of restore state + private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE]; + private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE]; + private IntArray mSaveFlags = new IntArray(); + private ArrayList<Blending> mBlendings = new ArrayList<Blending>(); + + private int mCurrentAlphaIndex = 0; + private int mCurrentMatrixIndex = 0; + + // Viewport size + private int mWidth; + private int mHeight; + + // Projection matrix + private float[] mProjectionMatrix = new float[MATRIX_SIZE]; + + // Screen size for when we aren't bound to a texture + private int mScreenWidth; + private int mScreenHeight; + + // GL programs + private int mDrawProgram; + private int mTextureProgram; + private int mOesTextureProgram; + private int mMeshProgram; + + // GL buffer containing BOX_COORDINATES + private int mBoxCoordinates; + + // Handle indices -- common + private static final int INDEX_POSITION = 0; + private static final int INDEX_MATRIX = 1; + + // Handle indices -- draw + private static final int INDEX_COLOR = 2; + + // Handle indices -- texture + private static final int INDEX_TEXTURE_MATRIX = 2; + private static final int INDEX_TEXTURE_SAMPLER = 3; + private static final int INDEX_ALPHA = 4; + + // Handle indices -- mesh + private static final int INDEX_TEXTURE_COORD = 2; + + private abstract static class ShaderParameter { + public int handle; + protected final String mName; + + public ShaderParameter(String name) { + mName = name; + } + + public abstract void loadHandle(int program); + } + + private static class UniformShaderParameter extends ShaderParameter { + public UniformShaderParameter(String name) { + super(name); + } + + @Override + public void loadHandle(int program) { + handle = GLES20.glGetUniformLocation(program, mName); + checkError(); + } + } + + private static class AttributeShaderParameter extends ShaderParameter { + public AttributeShaderParameter(String name) { + super(name); + } + + @Override + public void loadHandle(int program) { + handle = GLES20.glGetAttribLocation(program, mName); + checkError(); + } + } + + ShaderParameter[] mDrawParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR + }; + ShaderParameter[] mTextureParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX + new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER + new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA + }; + ShaderParameter[] mOesTextureParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX + new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER + new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA + }; + ShaderParameter[] mMeshParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD + new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER + new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA + }; + + private final IntArray mUnboundTextures = new IntArray(); + private final IntArray mDeleteBuffers = new IntArray(); + + // Keep track of statistics for debugging + private int mCountDrawMesh = 0; + private int mCountTextureRect = 0; + private int mCountFillRect = 0; + private int mCountDrawLine = 0; + + private int mNextTextureId = 1; + + // Buffer for framebuffer IDs -- we keep track so we can switch the attached + // texture. + private int[] mFrameBuffer = new int[1]; + + // Bound textures. + private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>(); + + // Temporary variables used within calculations + private final float[] mTempMatrix = new float[32]; + private final float[] mTempColor = new float[4]; + private final RectF mTempSourceRect = new RectF(); + private final RectF mTempTargetRect = new RectF(); + private final float[] mTempTextureMatrix = new float[MATRIX_SIZE]; + private final int[] mTempIntArray = new int[1]; + + public GLES20Canvas() { + Matrix.setIdentityM(mTempTextureMatrix, 0); + Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); + mAlphas[mCurrentAlphaIndex] = 1f; + mTargetTextures.add(null); + } + + @Override + public void initialize(GL11 gl) { + FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES); + mBoxCoordinates = uploadBuffer(boxBuffer); + + int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER); + int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER); + int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER); + int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER); + int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER); + int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, + OES_TEXTURE_FRAGMENT_SHADER); + + mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters); + mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader, + mTextureParameters); + mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader, + mOesTextureParameters); + mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters); + + mBlendings.clear(); + mBlendings.add(null); + setBlending(Blending.Mix); + } + + private static FloatBuffer createBuffer(float[] values) { + // First create an nio buffer, then create a VBO from it. + int size = values.length * FLOAT_SIZE; + FloatBuffer buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + buffer.put(values, 0, values.length).position(0); + return buffer; + } + + private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) { + int program = GLES20.glCreateProgram(); + checkError(); + if (program == 0) { + throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError()); + } + GLES20.glAttachShader(program, vertexShader); + checkError(); + GLES20.glAttachShader(program, fragmentShader); + checkError(); + GLES20.glLinkProgram(program); + checkError(); + int[] mLinkStatus = mTempIntArray; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0); + if (mLinkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + program = 0; + } + for (int i = 0; i < params.length; i++) { + params[i].loadHandle(program); + } + return program; + } + + private static int loadShader(int type, String shaderCode) { + // create a vertex shader type (GLES20.GL_VERTEX_SHADER) + // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) + int shader = GLES20.glCreateShader(type); + + // add the source code to the shader and compile it + GLES20.glShaderSource(shader, shaderCode); + checkError(); + GLES20.glCompileShader(shader); + checkError(); + + return shader; + } + + @Override + public void setSize(int width, int height) { + mWidth = width; + mHeight = height; + GLES20.glViewport(0, 0, mWidth, mHeight); + checkError(); + Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); + Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1); + if (getTargetTexture() == null) { + mScreenWidth = width; + mScreenHeight = height; + Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0); + Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1); + } + } + + @Override + public void clearBuffer() { + GLES20.glClearColor(0f, 0f, 0f, 1f); + checkError(); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + checkError(); + } + + @Override + public void clearBuffer(float[] argb) { + GLES20.glClearColor(argb[1], argb[2], argb[3], argb[0]); + checkError(); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + checkError(); + } + + @Override + public float getAlpha() { + return mAlphas[mCurrentAlphaIndex]; + } + + @Override + public void setAlpha(float alpha) { + mAlphas[mCurrentAlphaIndex] = alpha; + } + + @Override + public void multiplyAlpha(float alpha) { + setAlpha(getAlpha() * alpha); + } + + @Override + public void translate(float x, float y, float z) { + Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z); + } + + // This is a faster version of translate(x, y, z) because + // (1) we knows z = 0, (2) we inline the Matrix.translateM call, + // (3) we unroll the loop + @Override + public void translate(float x, float y) { + int index = mCurrentMatrixIndex; + float[] m = mMatrices; + m[index + 12] += m[index + 0] * x + m[index + 4] * y; + m[index + 13] += m[index + 1] * x + m[index + 5] * y; + m[index + 14] += m[index + 2] * x + m[index + 6] * y; + m[index + 15] += m[index + 3] * x + m[index + 7] * y; + } + + @Override + public void scale(float sx, float sy, float sz) { + Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz); + } + + @Override + public void rotate(float angle, float x, float y, float z) { + if (angle == 0f) { + return; + } + float[] temp = mTempMatrix; + Matrix.setRotateM(temp, 0, angle, x, y, z); + float[] matrix = mMatrices; + int index = mCurrentMatrixIndex; + Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0); + System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE); + } + + @Override + public void multiplyMatrix(float[] matrix, int offset) { + float[] temp = mTempMatrix; + float[] currentMatrix = mMatrices; + int index = mCurrentMatrixIndex; + Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset); + System.arraycopy(temp, 0, currentMatrix, index, 16); + } + + @Override + public void save() { + save(SAVE_FLAG_ALL); + } + + @Override + public void save(int saveFlags) { + boolean saveAlpha = (saveFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA; + if (saveAlpha) { + float currentAlpha = getAlpha(); + mCurrentAlphaIndex++; + if (mAlphas.length <= mCurrentAlphaIndex) { + mAlphas = Arrays.copyOf(mAlphas, mAlphas.length * 2); + } + mAlphas[mCurrentAlphaIndex] = currentAlpha; + } + boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX; + if (saveMatrix) { + int currentIndex = mCurrentMatrixIndex; + mCurrentMatrixIndex += MATRIX_SIZE; + if (mMatrices.length <= mCurrentMatrixIndex) { + mMatrices = Arrays.copyOf(mMatrices, mMatrices.length * 2); + } + System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE); + } + boolean saveBlending = (saveFlags & SAVE_FLAG_BLEND) == SAVE_FLAG_BLEND; + if (saveBlending) { + mBlendings.add(mBlendings.get(mBlendings.size() - 1)); + } + mSaveFlags.add(saveFlags); + } + + @Override + public void restore() { + int restoreFlags = mSaveFlags.removeLast(); + boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA; + if (restoreAlpha) { + mCurrentAlphaIndex--; + } + boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX; + if (restoreMatrix) { + mCurrentMatrixIndex -= MATRIX_SIZE; + } + boolean restoreBlending = (restoreFlags & SAVE_FLAG_BLEND) == SAVE_FLAG_BLEND; + if (restoreBlending) { + setBlending(mBlendings.get(mBlendings.size() - 2)); + mBlendings.remove(mBlendings.size() - 1); + } + } + + @Override + public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) { + draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1, + paint); + mCountDrawLine++; + } + + @Override + public void drawRect(float x, float y, float width, float height, GLPaint paint) { + draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint); + mCountDrawLine++; + } + + private void draw(int type, int offset, int count, float x, float y, float width, float height, + GLPaint paint) { + draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth()); + } + + private void draw(int type, int offset, int count, float x, float y, float width, float height, + int color, float lineWidth) { + prepareDraw(offset, color, lineWidth); + draw(mDrawParameters, type, count, x, y, width, height); + } + + private void prepareDraw(int offset, int color, float lineWidth) { + GLES20.glUseProgram(mDrawProgram); + checkError(); + if (lineWidth > 0) { + GLES20.glLineWidth(lineWidth); + checkError(); + } + float[] colorArray = getColor(color); + boolean blendingEnabled = (colorArray[3] < 1f); + enableBlending(blendingEnabled); + if (blendingEnabled) { + GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]); + checkError(); + } + + GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0); + setPosition(mDrawParameters, offset); + checkError(); + } + + private float[] getColor(int color) { + float alpha = ((color >>> 24) & 0xFF) / 255f * getAlpha(); + float red = ((color >>> 16) & 0xFF) / 255f * alpha; + float green = ((color >>> 8) & 0xFF) / 255f * alpha; + float blue = (color & 0xFF) / 255f * alpha; + mTempColor[0] = red; + mTempColor[1] = green; + mTempColor[2] = blue; + mTempColor[3] = alpha; + return mTempColor; + } + + private void enableBlending(boolean enableBlending) { + if (enableBlending) { + GLES20.glEnable(GLES20.GL_BLEND); + checkError(); + } else { + GLES20.glDisable(GLES20.GL_BLEND); + checkError(); + } + } + + private void setPosition(ShaderParameter[] params, int offset) { + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates); + checkError(); + GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX, + GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE); + checkError(); + } + + private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width, + float height) { + setMatrix(params, x, y, width, height); + int positionHandle = params[INDEX_POSITION].handle; + GLES20.glEnableVertexAttribArray(positionHandle); + checkError(); + GLES20.glDrawArrays(type, 0, count); + checkError(); + GLES20.glDisableVertexAttribArray(positionHandle); + checkError(); + } + + private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) { + Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f); + Matrix.scaleM(mTempMatrix, 0, width, height, 1f); + Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0); + GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE); + checkError(); + } + + @Override + public void fillRect(float x, float y, float width, float height, int color) { + draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height, + color, 0f); + mCountFillRect++; + } + + @Override + public void drawTexture(BasicTexture texture, int x, int y, int width, int height) { + if (width <= 0 || height <= 0) { + return; + } + copyTextureCoordinates(texture, mTempSourceRect); + mTempTargetRect.set(x, y, x + width, y + height); + convertCoordinate(mTempSourceRect, mTempTargetRect, texture); + drawTextureRect(texture, mTempSourceRect, mTempTargetRect); + } + + private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) { + int left = 0; + int top = 0; + int right = texture.getWidth(); + int bottom = texture.getHeight(); + if (texture.hasBorder()) { + left = 1; + top = 1; + right -= 1; + bottom -= 1; + } + outRect.set(left, top, right, bottom); + } + + @Override + public void drawTexture(BasicTexture texture, RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) { + return; + } + mTempSourceRect.set(source); + mTempTargetRect.set(target); + + convertCoordinate(mTempSourceRect, mTempTargetRect, texture); + drawTextureRect(texture, mTempSourceRect, mTempTargetRect); + } + + @Override + public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w, + int h) { + if (w <= 0 || h <= 0) { + return; + } + mTempTargetRect.set(x, y, x + w, y + h); + drawTextureRect(texture, textureTransform, mTempTargetRect); + } + + private void drawTextureRect(BasicTexture texture, RectF source, RectF target) { + setTextureMatrix(source); + drawTextureRect(texture, mTempTextureMatrix, target); + } + + private void setTextureMatrix(RectF source) { + mTempTextureMatrix[0] = source.width(); + mTempTextureMatrix[5] = source.height(); + mTempTextureMatrix[12] = source.left; + mTempTextureMatrix[13] = source.top; + } + + // This function changes the source coordinate to the texture coordinates. + // It also clips the source and target coordinates if it is beyond the + // bound of the texture. + private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) { + int width = texture.getWidth(); + int height = texture.getHeight(); + int texWidth = texture.getTextureWidth(); + int texHeight = texture.getTextureHeight(); + // Convert to texture coordinates + source.left /= texWidth; + source.right /= texWidth; + source.top /= texHeight; + source.bottom /= texHeight; + + // Clip if the rendering range is beyond the bound of the texture. + float xBound = (float) width / texWidth; + if (source.right > xBound) { + target.right = target.left + target.width() * (xBound - source.left) / source.width(); + source.right = xBound; + } + float yBound = (float) height / texHeight; + if (source.bottom > yBound) { + target.bottom = target.top + target.height() * (yBound - source.top) / source.height(); + source.bottom = yBound; + } + } + + private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) { + ShaderParameter[] params = prepareTexture(texture); + setPosition(params, OFFSET_FILL_RECT); + GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0); + checkError(); + draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top, + target.width(), target.height()); + mCountTextureRect++; + } + + private ShaderParameter[] prepareTexture(BasicTexture texture) { + ShaderParameter[] params; + int program; + if (texture.getTarget() == GLES20.GL_TEXTURE_2D) { + params = mTextureParameters; + program = mTextureProgram; + } else { + params = mOesTextureParameters; + program = mOesTextureProgram; + } + prepareTexture(texture, program, params); + return params; + } + + private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) { + GLES20.glUseProgram(program); + checkError(); + enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + checkError(); + texture.onBind(this); + GLES20.glBindTexture(texture.getTarget(), texture.getId()); + checkError(); + GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0); + checkError(); + GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha()); + checkError(); + } + + @Override + public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer, + int indexBuffer, int indexCount) { + prepareTexture(texture, mMeshProgram, mMeshParameters); + + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer); + checkError(); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer); + checkError(); + int positionHandle = mMeshParameters[INDEX_POSITION].handle; + GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, + VERTEX_STRIDE, 0); + checkError(); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer); + checkError(); + int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle; + GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, + false, VERTEX_STRIDE, 0); + checkError(); + + GLES20.glEnableVertexAttribArray(positionHandle); + checkError(); + GLES20.glEnableVertexAttribArray(texCoordHandle); + checkError(); + + setMatrix(mMeshParameters, x, y, 1, 1); + GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0); + checkError(); + + GLES20.glDisableVertexAttribArray(positionHandle); + checkError(); + GLES20.glDisableVertexAttribArray(texCoordHandle); + checkError(); + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); + checkError(); + mCountDrawMesh++; + } + + @Override + public void drawMixed(BasicTexture texture, int toColor, float ratio, int x, int y, int w, int h) { + copyTextureCoordinates(texture, mTempSourceRect); + mTempTargetRect.set(x, y, x + w, y + h); + drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect); + } + + @Override + public void drawMixed(BasicTexture texture, int toColor, float ratio, RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) { + return; + } + save(SAVE_FLAG_ALPHA); + + float currentAlpha = getAlpha(); + float cappedRatio = Math.min(1f, Math.max(0f, ratio)); + + float textureAlpha = (1f - cappedRatio) * currentAlpha; + setAlpha(textureAlpha); + drawTexture(texture, source, target); + + float colorAlpha = cappedRatio * currentAlpha; + setAlpha(colorAlpha); + fillRect(target.left, target.top, target.width(), target.height(), toColor); + + restore(); + } + + @Override + public boolean unloadTexture(BasicTexture texture) { + boolean unload = texture.isLoaded(); + if (unload) { + synchronized (mUnboundTextures) { + mUnboundTextures.add(texture.getId()); + } + } + return unload; + } + + @Override + public void deleteBuffer(int bufferId) { + synchronized (mUnboundTextures) { + mDeleteBuffers.add(bufferId); + } + } + + @Override + public void deleteRecycledResources() { + synchronized (mUnboundTextures) { + IntArray ids = mUnboundTextures; + if (mUnboundTextures.size() > 0) { + glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + + ids = mDeleteBuffers; + if (ids.size() > 0) { + glDeleteBuffers(null, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + } + } + + @Override + public void dumpStatisticsAndClear() { + String line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh, + mCountTextureRect, mCountFillRect, mCountDrawLine); + mCountDrawMesh = 0; + mCountTextureRect = 0; + mCountFillRect = 0; + mCountDrawLine = 0; + Log.d(TAG, line); + } + + @Override + public void endRenderTarget() { + RawTexture oldTexture = mTargetTextures.remove(mTargetTextures.size() - 1); + RawTexture texture = getTargetTexture(); + setRenderTarget(oldTexture, texture); + restore(); // restore matrix and alpha + } + + @Override + public void beginRenderTarget(RawTexture texture) { + save(); // save matrix and alpha and blending + RawTexture oldTexture = getTargetTexture(); + mTargetTextures.add(texture); + setRenderTarget(oldTexture, texture); + } + + private RawTexture getTargetTexture() { + return mTargetTextures.get(mTargetTextures.size() - 1); + } + + private void setRenderTarget(BasicTexture oldTexture, RawTexture texture) { + if (oldTexture == null && texture != null) { + GLES20.glGenFramebuffers(1, mFrameBuffer, 0); + checkError(); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]); + checkError(); + } else if (oldTexture != null && texture == null) { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + checkError(); + GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0); + checkError(); + } + + if (texture == null) { + setSize(mScreenWidth, mScreenHeight); + } else { + setSize(texture.getWidth(), texture.getHeight()); + + if (!texture.isLoaded()) { + texture.prepare(this); + } + + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, + texture.getTarget(), texture.getId(), 0); + checkError(); + + checkFramebufferStatus(); + } + } + + private static void checkFramebufferStatus() { + int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { + String msg = ""; + switch (status) { + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; + break; + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; + break; + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; + break; + case GLES20.GL_FRAMEBUFFER_UNSUPPORTED: + msg = "GL_FRAMEBUFFER_UNSUPPORTED"; + break; + } + throw new RuntimeException(msg + ":" + Integer.toHexString(status)); + } + } + + @Override + public void setTextureParameters(BasicTexture texture) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + } + + @Override + public void initializeTextureSize(BasicTexture texture, int format, int type) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + int width = texture.getTextureWidth(); + int height = texture.getTextureHeight(); + GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null); + } + + @Override + public void initializeTexture(BasicTexture texture, Bitmap bitmap) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + GLUtils.texImage2D(target, 0, bitmap, 0); + } + + @Override + public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, + int format, int type) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type); + } + + @Override + public int uploadBuffer(FloatBuffer buf) { + return uploadBuffer(buf, FLOAT_SIZE); + } + + @Override + public int uploadBuffer(ByteBuffer buf) { + return uploadBuffer(buf, 1); + } + + private int uploadBuffer(Buffer buffer, int elementSize) { + glGenBuffers(1, mTempIntArray, 0); + checkError(); + int bufferId = mTempIntArray[0]; + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId); + checkError(); + GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer, + GLES20.GL_STATIC_DRAW); + checkError(); + return bufferId; + } + + @Override + public void setBlending(Blending blending) { + Blending currentBlending = mBlendings.get(mBlendings.size() - 1); + if (currentBlending == blending) { + return; // nothing to change + } + mBlendings.set(mBlendings.size() - 1, blending); + int srcFunc = GLES20.GL_ONE; + int dstFunc; + switch (blending) { + case Additive: + dstFunc = GLES20.GL_ONE; + break; + case Mix: + dstFunc = GLES20.GL_ONE_MINUS_SRC_ALPHA; + break; + default: + Utils.fail("Unknown blend: " + blending); + dstFunc = GLES20.GL_ONE_MINUS_SRC_ALPHA; + break; + } + GLES20.glBlendFunc(srcFunc, dstFunc); + checkError(); + } + + @Override + public int generateTexture() { + // Can use anything as a lock. No need to create a new object. + synchronized (mTempIntArray) { + return mNextTextureId++; + } + } + + @Override + public void glGenBuffers(int n, int[] buffers, int offset) { + GLES20.glGenBuffers(n, buffers, offset); + checkError(); + } + + @Override + public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) { + GLES20.glDeleteTextures(n, textures, offset); + checkError(); + } + + @Override + public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) { + GLES20.glDeleteBuffers(n, buffers, offset); + checkError(); + } + + @Override + public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) { + GLES20.glDeleteFramebuffers(n, buffers, offset); + checkError(); + } + + @Override + public void enableStencil() { + GLES20.glEnable(GLES20.GL_STENCIL_TEST); + } + + @Override + public void disableStencil() { + GLES20.glDisable(GLES20.GL_STENCIL_TEST); + } + + @Override + public void clearStencilBuffer() { + GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT); + } + + @Override + public void updateStencil(boolean update) { + int passOp = update ? GLES20.GL_REPLACE : GLES20.GL_KEEP; + GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, passOp); + } + + @Override + public void drawOnlyOutsideStencil(boolean onlyOutside) { + int func = onlyOutside ? GLES20.GL_NOTEQUAL : GLES20.GL_ALWAYS; + GLES20.glStencilFunc(func, 1, 1); + } + + private static void checkError() { + int error = GLES20.glGetError(); + if (error != 0) { + Throwable t = new Throwable(); + Log.e(TAG, "GL error: " + error, t); + } + } + + @SuppressWarnings("unused") + private static void printMatrix(String message, float[] m, int offset) { + StringBuilder b = new StringBuilder(message); + for (int i = 0; i < MATRIX_SIZE; i++) { + b.append(' '); + if (i % 4 == 0) { + b.append('\n'); + } + b.append(m[offset + i]); + } + Log.v(TAG, b.toString()); + } + +} diff --git a/src/com/android/gallery3d/ui/GLId.java b/src/com/android/gallery3d/ui/GLId.java index 689cf192e..04977c337 100644 --- a/src/com/android/gallery3d/ui/GLId.java +++ b/src/com/android/gallery3d/ui/GLId.java @@ -20,31 +20,14 @@ import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11ExtensionPack; // This mimics corresponding GL functions. -public class GLId { - static int sNextId = 1; +public interface GLId { + public int generateTexture(); - public synchronized static void glGenTextures(int n, int[] textures, int offset) { - while (n-- > 0) { - textures[offset + n] = sNextId++; - } - } + public void glGenBuffers(int n, int[] buffers, int offset); - public synchronized static void glGenBuffers(int n, int[] buffers, int offset) { - while (n-- > 0) { - buffers[offset + n] = sNextId++; - } - } + public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset); - public synchronized static void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) { - gl.glDeleteTextures(n, textures, offset); - } + public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset); - public synchronized static void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) { - gl.glDeleteBuffers(n, buffers, offset); - } - - public synchronized static void glDeleteFramebuffers( - GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) { - gl11ep.glDeleteFramebuffersOES(n, buffers, offset); - } + public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset); } diff --git a/src/com/android/gallery3d/ui/GLIdImpl.java b/src/com/android/gallery3d/ui/GLIdImpl.java new file mode 100644 index 000000000..7a0232bf0 --- /dev/null +++ b/src/com/android/gallery3d/ui/GLIdImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 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.ui; + +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11ExtensionPack; + +/** + * Open GL ES 1.1 implementation for generating and destroying texture IDs and + * buffer IDs + */ +public class GLIdImpl implements GLId { + private static int sNextId = 1; + // Mutex for sNextId + private static Object sLock = new Object(); + + @Override + public int generateTexture() { + synchronized (sLock) { + return sNextId++; + } + } + + @Override + public void glGenBuffers(int n, int[] buffers, int offset) { + synchronized (sLock) { + while (n-- > 0) { + buffers[offset + n] = sNextId++; + } + } + } + + @Override + public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) { + synchronized (sLock) { + gl.glDeleteTextures(n, textures, offset); + } + } + + @Override + public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) { + synchronized (sLock) { + gl.glDeleteBuffers(n, buffers, offset); + } + } + + @Override + public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) { + synchronized (sLock) { + gl11ep.glDeleteFramebuffersOES(n, buffers, offset); + } + } + + +} diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java index b7c48bf2e..6b76999b6 100644 --- a/src/com/android/gallery3d/ui/GLRootView.java +++ b/src/com/android/gallery3d/ui/GLRootView.java @@ -117,6 +117,7 @@ public class GLRootView extends GLSurfaceView super(context, attrs); mFlags |= FLAG_INITIALIZED; setBackgroundDrawable(null); + setEGLContextClientVersion(GLCanvas.getEGLContextClientVersion()); setEGLConfigChooser(mEglConfigChooser); setRenderer(this); if (ApiHelper.USE_888_PIXEL_FORMAT) { @@ -283,7 +284,8 @@ public class GLRootView extends GLSurfaceView mRenderLock.lock(); try { mGL = gl; - mCanvas = new GLCanvasImpl(gl); + mCanvas = GLCanvas.getInstance(); + mCanvas.initialize(gl); BasicTexture.invalidateAllTextures(); } finally { mRenderLock.unlock(); diff --git a/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java b/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java index deeb3b76d..f57a312cb 100644 --- a/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java +++ b/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java @@ -49,12 +49,35 @@ class GalleryEGLConfigChooser implements EGLConfigChooser { EGL10.EGL_NONE }; + private final int mConfig2Spec565[] = new int[] { + EGL10.EGL_RED_SIZE, 5, + EGL10.EGL_GREEN_SIZE, 6, + EGL10.EGL_BLUE_SIZE, 5, + EGL10.EGL_ALPHA_SIZE, 0, + EGL10.EGL_RENDERABLE_TYPE, 4, /* EGL_OPENGL_ES2_BIT */ + EGL10.EGL_NONE + }; + + private final int mConfig2Spec888[] = new int[] { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 0, + EGL10.EGL_RENDERABLE_TYPE, 4, /* EGL_OPENGL_ES2_BIT */ + EGL10.EGL_NONE + }; + @Override public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { int[] numConfig = new int[1]; - int mConfigSpec[] = ApiHelper.USE_888_PIXEL_FORMAT - ? mConfigSpec888 : mConfigSpec565; - if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, numConfig)) { + + int configSpec[]; + if (GLCanvas.getEGLContextClientVersion() == 2) { + configSpec = ApiHelper.USE_888_PIXEL_FORMAT ? mConfig2Spec888 : mConfig2Spec565; + } else { + configSpec = ApiHelper.USE_888_PIXEL_FORMAT ? mConfigSpec888 : mConfigSpec565; + } + if (!egl.eglChooseConfig(display, configSpec, null, 0, numConfig)) { throw new RuntimeException("eglChooseConfig failed"); } @@ -64,7 +87,7 @@ class GalleryEGLConfigChooser implements EGLConfigChooser { EGLConfig[] configs = new EGLConfig[numConfig[0]]; if (!egl.eglChooseConfig(display, - mConfigSpec, configs, configs.length, numConfig)) { + configSpec, configs, configs.length, numConfig)) { throw new RuntimeException(); } diff --git a/src/com/android/gallery3d/ui/GestureRecognizer.java b/src/com/android/gallery3d/ui/GestureRecognizer.java index e4e0c49f5..1e5250b9b 100644 --- a/src/com/android/gallery3d/ui/GestureRecognizer.java +++ b/src/com/android/gallery3d/ui/GestureRecognizer.java @@ -32,7 +32,7 @@ public class GestureRecognizer { boolean onSingleTapUp(float x, float y); boolean onDoubleTap(float x, float y); boolean onScroll(float dx, float dy, float totalX, float totalY); - boolean onFling(float velocityX, float velocityY); + boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); boolean onScaleBegin(float focusX, float focusY); boolean onScale(float focusX, float focusY, float scale); void onScaleEnd(); @@ -94,7 +94,7 @@ public class GestureRecognizer { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - return mListener.onFling(velocityX, velocityY); + return mListener.onFling(e1, e2, velocityX, velocityY); } } diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java index f432333ce..a9eeaf89c 100644 --- a/src/com/android/gallery3d/ui/MenuExecutor.java +++ b/src/com/android/gallery3d/ui/MenuExecutor.java @@ -26,12 +26,12 @@ import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Handler; import android.os.Message; -import android.view.Menu; -import android.view.MenuItem; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.app.AbstractGalleryActivity; -import com.android.gallery3d.app.CropImage; +import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; @@ -62,6 +62,7 @@ public class MenuExecutor { private Future<?> mTask; // wait the operation to finish when we want to stop it. private boolean mWaitOnStop; + private boolean mPaused; private final AbstractGalleryActivity mActivity; private final SelectionManager mSelectionManager; @@ -113,7 +114,7 @@ public class MenuExecutor { break; } case MSG_TASK_UPDATE: { - if (mDialog != null) mDialog.setProgress(message.arg1); + if (mDialog != null && !mPaused) mDialog.setProgress(message.arg1); if (message.obj != null) { ProgressListener listener = (ProgressListener) message.obj; listener.onProgressUpdate(message.arg1); @@ -133,13 +134,23 @@ public class MenuExecutor { if (mTask != null) { if (!mWaitOnStop) mTask.cancel(); mTask.waitDone(); - mDialog.dismiss(); + if (mDialog != null && mDialog.isShowing()) mDialog.dismiss(); mDialog = null; mTask = null; } } + public void resume() { + mPaused = false; + if (mDialog != null) mDialog.show(); + } + public void pause() { + mPaused = true; + if (mDialog != null && mDialog.isShowing()) mDialog.hide(); + } + + public void destroy() { stopTaskAndDismissDialog(); } @@ -161,6 +172,7 @@ public class MenuExecutor { boolean supportRotate = (supported & MediaObject.SUPPORT_ROTATE) != 0; boolean supportCrop = (supported & MediaObject.SUPPORT_CROP) != 0; boolean supportTrim = (supported & MediaObject.SUPPORT_TRIM) != 0; + boolean supportMute = (supported & MediaObject.SUPPORT_MUTE) != 0; boolean supportShare = (supported & MediaObject.SUPPORT_SHARE) != 0; boolean supportSetAs = (supported & MediaObject.SUPPORT_SETAS) != 0; boolean supportShowOnMap = (supported & MediaObject.SUPPORT_SHOW_ON_MAP) != 0; @@ -174,6 +186,7 @@ public class MenuExecutor { setMenuItemVisible(menu, R.id.action_rotate_cw, supportRotate); setMenuItemVisible(menu, R.id.action_crop, supportCrop); setMenuItemVisible(menu, R.id.action_trim, supportTrim); + setMenuItemVisible(menu, R.id.action_mute, supportMute); // Hide panorama until call to updateMenuForPanorama corrects it setMenuItemVisible(menu, R.id.action_share_panorama, false); setMenuItemVisible(menu, R.id.action_share, supportShare); @@ -332,7 +345,7 @@ public class MenuExecutor { mDialog.show(); } MediaOperation operation = new MediaOperation(action, ids, listener); - mTask = mActivity.getThreadPool().submit(operation, null); + mTask = mActivity.getBatchServiceThreadPoolIfAvailable().submit(operation, null); mWaitOnStop = waitOnStop; } diff --git a/src/com/android/gallery3d/ui/NinePatchTexture.java b/src/com/android/gallery3d/ui/NinePatchTexture.java index fa0e9cdc3..f5c614554 100644 --- a/src/com/android/gallery3d/ui/NinePatchTexture.java +++ b/src/com/android/gallery3d/ui/NinePatchTexture.java @@ -27,8 +27,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; -import javax.microedition.khronos.opengles.GL11; - // NinePatchTexture is a texture backed by a NinePatch resource. // // getPaddings() returns paddings specified in the NinePatch. @@ -199,7 +197,9 @@ class NinePatchInstance { private ByteBuffer mIndexBuffer; // Names for buffer names: xy, uv, index. - private int[] mBufferNames; + private int mXyBufferName = -1; + private int mUvBufferName; + private int mIndexBufferName; private int mIdxCount; @@ -396,24 +396,9 @@ class NinePatchInstance { } private void prepareBuffers(GLCanvas canvas) { - mBufferNames = new int[3]; - GL11 gl = canvas.getGLInstance(); - GLId.glGenBuffers(3, mBufferNames, 0); - - gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBufferNames[0]); - gl.glBufferData(GL11.GL_ARRAY_BUFFER, - mXyBuffer.capacity() * (Float.SIZE / Byte.SIZE), - mXyBuffer, GL11.GL_STATIC_DRAW); - - gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBufferNames[1]); - gl.glBufferData(GL11.GL_ARRAY_BUFFER, - mUvBuffer.capacity() * (Float.SIZE / Byte.SIZE), - mUvBuffer, GL11.GL_STATIC_DRAW); - - gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mBufferNames[2]); - gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, - mIndexBuffer.capacity(), - mIndexBuffer, GL11.GL_STATIC_DRAW); + mXyBufferName = canvas.uploadBuffer(mXyBuffer); + mUvBufferName = canvas.uploadBuffer(mUvBuffer); + mIndexBufferName = canvas.uploadBuffer(mIndexBuffer); // These buffers are never used again. mXyBuffer = null; @@ -422,19 +407,18 @@ class NinePatchInstance { } public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) { - if (mBufferNames == null) { + if (mXyBufferName == -1) { prepareBuffers(canvas); } - canvas.drawMesh(tex, x, y, mBufferNames[0], mBufferNames[1], - mBufferNames[2], mIdxCount); + canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount); } public void recycle(GLCanvas canvas) { - if (mBufferNames != null) { - canvas.deleteBuffer(mBufferNames[0]); - canvas.deleteBuffer(mBufferNames[1]); - canvas.deleteBuffer(mBufferNames[2]); - mBufferNames = null; + if (mXyBuffer == null) { + canvas.deleteBuffer(mXyBufferName); + canvas.deleteBuffer(mUvBufferName); + canvas.deleteBuffer(mIndexBufferName); + mXyBufferName = -1; } } } diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java index 6dcae4ca4..58389e4ae 100644 --- a/src/com/android/gallery3d/ui/PhotoView.java +++ b/src/com/android/gallery3d/ui/PhotoView.java @@ -174,8 +174,9 @@ public class PhotoView extends GLView { public static final int SCREEN_NAIL_MAX = 3; // These are constants for the delete gesture. - private static final int SWIPE_ESCAPE_VELOCITY = 2500; // dp/sec - private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec + private static final int SWIPE_ESCAPE_VELOCITY = 500; // dp/sec + private static final int MAX_DISMISS_VELOCITY = 2500; // dp/sec + private static final int SWIPE_ESCAPE_DISTANCE = 150; // dp // The picture entries, the valid index is from -SCREEN_NAIL_MAX to // SCREEN_NAIL_MAX. @@ -1070,19 +1071,19 @@ public class PhotoView extends GLView { } @Override - public boolean onFling(float velocityX, float velocityY) { + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (mIgnoreSwipingGesture) return true; if (mModeChanged) return true; if (swipeImages(velocityX, velocityY)) { mIgnoreUpEvent = true; } else { - flingImages(velocityX, velocityY); + flingImages(velocityX, velocityY, Math.abs(e2.getY() - e1.getY())); } mHadFling = true; return true; } - private boolean flingImages(float velocityX, float velocityY) { + private boolean flingImages(float velocityX, float velocityY, float dY) { int vx = (int) (velocityX + 0.5f); int vy = (int) (velocityY + 0.5f); if (!mFilmMode) { @@ -1099,11 +1100,13 @@ public class PhotoView extends GLView { } int maxVelocity = GalleryUtils.dpToPixel(MAX_DISMISS_VELOCITY); int escapeVelocity = GalleryUtils.dpToPixel(SWIPE_ESCAPE_VELOCITY); + int escapeDistance = GalleryUtils.dpToPixel(SWIPE_ESCAPE_DISTANCE); int centerY = mPositionController.getPosition(mTouchBoxIndex) .centerY(); boolean fastEnough = (Math.abs(vy) > escapeVelocity) && (Math.abs(vy) > Math.abs(vx)) - && ((vy > 0) == (centerY > getHeight() / 2)); + && ((vy > 0) == (centerY > getHeight() / 2)) + && dY >= escapeDistance; if (fastEnough) { vy = Math.min(vy, maxVelocity); int duration = mPositionController.flingFilmY(mTouchBoxIndex, vy); @@ -1237,7 +1240,10 @@ public class PhotoView extends GLView { if (mFilmMode) { int xi = (int) (x + 0.5f); int yi = (int) (y + 0.5f); - mTouchBoxIndex = mPositionController.hitTest(xi, yi); + // We only care about being within the x bounds, necessary for + // handling very wide images which are otherwise very hard to fling + mTouchBoxIndex = mPositionController.hitTest(xi, getHeight() / 2); + if (mTouchBoxIndex < mPrevBound || mTouchBoxIndex > mNextBound) { mTouchBoxIndex = Integer.MAX_VALUE; } else { diff --git a/src/com/android/gallery3d/ui/PopupList.java b/src/com/android/gallery3d/ui/PopupList.java index 248f50b25..dd6269380 100644 --- a/src/com/android/gallery3d/ui/PopupList.java +++ b/src/com/android/gallery3d/ui/PopupList.java @@ -159,7 +159,7 @@ public class PopupList { R.drawable.menu_dropdown_panel_holo_dark)); mContentList = new ListView(mContext, null, - android.R.attr.dropDownListViewStyle); + com.actionbarsherlock.R.attr.dropDownListViewStyle); mContentList.setAdapter(new ItemDataAdapter()); mContentList.setOnItemClickListener(mOnItemClickListener); popup.setContentView(mContentList); diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java index 6a4bcea87..9069d5da2 100644 --- a/src/com/android/gallery3d/ui/PositionController.java +++ b/src/com/android/gallery3d/ui/PositionController.java @@ -18,10 +18,10 @@ package com.android.gallery3d.ui; import android.content.Context; import android.graphics.Rect; +import android.os.Build; import android.util.Log; import android.widget.Scroller; -import com.android.gallery3d.app.PhotoPage; import com.android.gallery3d.common.Utils; import com.android.gallery3d.ui.PhotoView.Size; import com.android.gallery3d.util.GalleryUtils; @@ -211,7 +211,11 @@ class PositionController { public PositionController(Context context, Listener listener) { mListener = listener; mPageScroller = new FlingScroller(); - mFilmScroller = new Scroller(context, null, false); + if (Build.VERSION.SDK_INT >= 11) { + mFilmScroller = new Scroller(context, null, false); + } else { + mFilmScroller = new Scroller(context, null); + } // Initialize the areas. initPlatform(); diff --git a/src/com/android/gallery3d/ui/RawTexture.java b/src/com/android/gallery3d/ui/RawTexture.java index 4c0d9d365..53aef9edc 100644 --- a/src/com/android/gallery3d/ui/RawTexture.java +++ b/src/com/android/gallery3d/ui/RawTexture.java @@ -16,15 +16,13 @@ package com.android.gallery3d.ui; +import android.opengl.GLES20; + import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11Ext; public class RawTexture extends BasicTexture { private static final String TAG = "RawTexture"; - private final static int[] sTextureId = new int[1]; - private final static float[] sCropRect = new float[4]; - private final boolean mOpaque; public RawTexture(int width, int height, boolean opaque) { @@ -38,36 +36,10 @@ public class RawTexture extends BasicTexture { } protected void prepare(GLCanvas canvas) { - GL11 gl = canvas.getGLInstance(); - - // Define a vertically flipped crop rectangle for - // OES_draw_texture. - // The four values in sCropRect are: left, bottom, width, and - // height. Negative value of width or height means flip. - sCropRect[0] = 0; - sCropRect[1] = mHeight; - sCropRect[2] = mWidth; - sCropRect[3] = -mHeight; - - // Upload the bitmap to a new texture. - GLId.glGenTextures(1, sTextureId, 0); - gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]); - gl.glTexParameterfv(GL11.GL_TEXTURE_2D, - GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - - gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, - getTextureWidth(), getTextureHeight(), - 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, null); - - mId = sTextureId[0]; + GLId glId = GLCanvas.getGLId(); + mId = glId.generateTexture(); + canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE); + canvas.setTextureParameters(this); mState = STATE_LOADED; setAssociatedCanvas(canvas); } diff --git a/src/com/android/gallery3d/ui/SlideshowView.java b/src/com/android/gallery3d/ui/SlideshowView.java index bb36c47e9..7734eb236 100644 --- a/src/com/android/gallery3d/ui/SlideshowView.java +++ b/src/com/android/gallery3d/ui/SlideshowView.java @@ -21,11 +21,10 @@ import android.graphics.PointF; import com.android.gallery3d.anim.CanvasAnimation; import com.android.gallery3d.anim.FloatAnimation; +import com.android.gallery3d.ui.GLCanvas.Blending; import java.util.Random; -import javax.microedition.khronos.opengles.GL11; - public class SlideshowView extends GLView { @SuppressWarnings("unused") private static final String TAG = "SlideshowView"; @@ -93,8 +92,8 @@ public class SlideshowView extends GLView { protected void render(GLCanvas canvas) { long animTime = AnimationTime.get(); boolean requestRender = mTransitionAnimation.calculate(animTime); - GL11 gl = canvas.getGLInstance(); - gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE); + canvas.save(GLCanvas.SAVE_FLAG_BLEND); + canvas.setBlending(Blending.Additive); float alpha = mPrevTexture == null ? 1f : mTransitionAnimation.get(); if (mPrevTexture != null && alpha != 1f) { @@ -118,7 +117,7 @@ public class SlideshowView extends GLView { canvas.restore(); } if (requestRender) invalidate(); - gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); + canvas.restore(); } private class SlideshowAnimation extends CanvasAnimation { diff --git a/src/com/android/gallery3d/ui/UploadedTexture.java b/src/com/android/gallery3d/ui/UploadedTexture.java index bb86d05ef..470ee6a98 100644 --- a/src/com/android/gallery3d/ui/UploadedTexture.java +++ b/src/com/android/gallery3d/ui/UploadedTexture.java @@ -25,7 +25,6 @@ import com.android.gallery3d.common.Utils; import java.util.HashMap; import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11Ext; // UploadedTextures use a Bitmap for the content of the texture. // @@ -194,9 +193,7 @@ abstract class UploadedTexture extends BasicTexture { Bitmap bitmap = getBitmap(); int format = GLUtils.getInternalFormat(bitmap); int type = GLUtils.getType(bitmap); - canvas.getGLInstance().glBindTexture(GL11.GL_TEXTURE_2D, mId); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder, - bitmap, format, type); + canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); freeBitmap(); mContentValid = true; } @@ -210,11 +207,7 @@ abstract class UploadedTexture extends BasicTexture { return sUploadedCount > UPLOAD_LIMIT; } - static int[] sTextureId = new int[1]; - static float[] sCropRect = new float[4]; - private void uploadToCanvas(GLCanvas canvas) { - GL11 gl = canvas.getGLInstance(); Bitmap bitmap = getBitmap(); if (bitmap != null) { @@ -228,65 +221,40 @@ abstract class UploadedTexture extends BasicTexture { Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight); - // Define a vertically flipped crop rectangle for - // OES_draw_texture. - // The four values in sCropRect are: left, bottom, width, and - // height. Negative value of width or height means flip. - sCropRect[0] = mBorder; - sCropRect[1] = mBorder + bHeight; - sCropRect[2] = bWidth; - sCropRect[3] = -bHeight; - // Upload the bitmap to a new texture. - GLId.glGenTextures(1, sTextureId, 0); - gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]); - gl.glTexParameterfv(GL11.GL_TEXTURE_2D, - GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + mId = GLCanvas.getGLId().generateTexture(); + canvas.setTextureParameters(this); if (bWidth == texWidth && bHeight == texHeight) { - GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0); + canvas.initializeTexture(this, bitmap); } else { int format = GLUtils.getInternalFormat(bitmap); int type = GLUtils.getType(bitmap); Config config = bitmap.getConfig(); - gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, - texWidth, texHeight, 0, format, type, null); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - mBorder, mBorder, bitmap, format, type); + canvas.initializeTextureSize(this, format, type); + canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); if (mBorder > 0) { // Left border Bitmap line = getBorderLine(true, config, texHeight); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - 0, 0, line, format, type); + canvas.texSubImage2D(this, 0, 0, line, format, type); // Top border line = getBorderLine(false, config, texWidth); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - 0, 0, line, format, type); + canvas.texSubImage2D(this, 0, 0, line, format, type); } // Right border if (mBorder + bWidth < texWidth) { Bitmap line = getBorderLine(true, config, texHeight); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - mBorder + bWidth, 0, line, format, type); + canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type); } // Bottom border if (mBorder + bHeight < texHeight) { Bitmap line = getBorderLine(false, config, texWidth); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - 0, mBorder + bHeight, line, format, type); + canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type); } } } finally { @@ -294,7 +262,6 @@ abstract class UploadedTexture extends BasicTexture { } // Update texture state. setAssociatedCanvas(canvas); - mId = sTextureId[0]; mState = STATE_LOADED; mContentValid = true; } else { diff --git a/src/com/android/gallery3d/util/AccessibilityUtils.java b/src/com/android/gallery3d/util/AccessibilityUtils.java new file mode 100644 index 000000000..9df8e4ece --- /dev/null +++ b/src/com/android/gallery3d/util/AccessibilityUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 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.util; + +import android.content.Context; +import android.support.v4.view.accessibility.AccessibilityRecordCompat; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import com.android.gallery3d.common.ApiHelper; + +/** + * AccessibilityUtils provides functions needed in accessibility mode. All the functions + * in this class are made compatible with gingerbread and later API's +*/ +public class AccessibilityUtils { + public static void makeAnnouncement(View view, CharSequence announcement) { + if (view == null) + return; + if (ApiHelper.HAS_ANNOUNCE_FOR_ACCESSIBILITY) { + view.announceForAccessibility(announcement); + } else { + // For API 15 and earlier, we need to construct an accessibility event + Context ctx = view.getContext(); + AccessibilityManager am = (AccessibilityManager) ctx.getSystemService( + Context.ACCESSIBILITY_SERVICE); + if (!am.isEnabled()) return; + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + AccessibilityRecordCompat arc = new AccessibilityRecordCompat(event); + arc.setSource(view); + event.setClassName(view.getClass().getName()); + event.setPackageName(view.getContext().getPackageName()); + event.setEnabled(view.isEnabled()); + event.getText().add(announcement); + am.sendAccessibilityEvent(event); + } + } +}
\ No newline at end of file diff --git a/src/com/android/gallery3d/util/IntArray.java b/src/com/android/gallery3d/util/IntArray.java index 082089a65..2c4dc2c83 100644 --- a/src/com/android/gallery3d/util/IntArray.java +++ b/src/com/android/gallery3d/util/IntArray.java @@ -31,6 +31,11 @@ public class IntArray { mData[mSize++] = value; } + public int removeLast() { + mSize--; + return mData[mSize]; + } + public int size() { return mSize; } diff --git a/src/com/android/gallery3d/util/SaveVideoFileInfo.java b/src/com/android/gallery3d/util/SaveVideoFileInfo.java new file mode 100644 index 000000000..c7e5e8568 --- /dev/null +++ b/src/com/android/gallery3d/util/SaveVideoFileInfo.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 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.util; + +import java.io.File; + +public class SaveVideoFileInfo { + public File mFile = null; + public String mFileName = null; + // This the full directory path. + public File mDirectory = null; + // This is just the folder's name. + public String mFolderName = null; + +} diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java new file mode 100644 index 000000000..c281dd3e7 --- /dev/null +++ b/src/com/android/gallery3d/util/SaveVideoFileUtils.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2012 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.util; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore.Video; +import android.provider.MediaStore.Video.VideoColumns; + +import java.io.File; +import java.sql.Date; +import java.text.SimpleDateFormat; + +public class SaveVideoFileUtils { + // Copy from SaveCopyTask.java in terms of how to handle the destination + // path and filename : querySource() and getSaveDirectory(). + public interface ContentResolverQueryCallback { + void onCursorResult(Cursor cursor); + } + + // This function can decide which folder to save the video file, and generate + // the needed information for the video file including filename. + public static SaveVideoFileInfo getDstMp4FileInfo(String fileNameFormat, + ContentResolver contentResolver, Uri uri, String defaultFolderName) { + SaveVideoFileInfo dstFileInfo = new SaveVideoFileInfo(); + // Use the default save directory if the source directory cannot be + // saved. + dstFileInfo.mDirectory = getSaveDirectory(contentResolver, uri); + if ((dstFileInfo.mDirectory == null) || !dstFileInfo.mDirectory.canWrite()) { + dstFileInfo.mDirectory = new File(Environment.getExternalStorageDirectory(), + BucketNames.DOWNLOAD); + dstFileInfo.mFolderName = defaultFolderName; + } else { + dstFileInfo.mFolderName = dstFileInfo.mDirectory.getName(); + } + dstFileInfo.mFileName = new SimpleDateFormat(fileNameFormat).format( + new Date(System.currentTimeMillis())); + + dstFileInfo.mFile = new File(dstFileInfo.mDirectory, dstFileInfo.mFileName + ".mp4"); + return dstFileInfo; + } + + private static void querySource(ContentResolver contentResolver, Uri uri, + String[] projection, ContentResolverQueryCallback callback) { + Cursor cursor = null; + try { + cursor = contentResolver.query(uri, 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(ContentResolver contentResolver, Uri uri) { + final File[] dir = new File[1]; + querySource(contentResolver, uri, + new String[] { VideoColumns.DATA }, + new ContentResolverQueryCallback() { + @Override + public void onCursorResult(Cursor cursor) { + dir[0] = new File(cursor.getString(0)).getParentFile(); + } + }); + return dir[0]; + } + + + /** + * Insert the content (saved file) with proper video properties. + */ + public static Uri insertContent(SaveVideoFileInfo mDstFileInfo, + ContentResolver contentResolver, Uri uri ) { + long nowInMs = System.currentTimeMillis(); + long nowInSec = nowInMs / 1000; + final ContentValues values = new ContentValues(12); + values.put(Video.Media.TITLE, mDstFileInfo.mFileName); + values.put(Video.Media.DISPLAY_NAME, mDstFileInfo.mFile.getName()); + values.put(Video.Media.MIME_TYPE, "video/mp4"); + values.put(Video.Media.DATE_TAKEN, nowInMs); + values.put(Video.Media.DATE_MODIFIED, nowInSec); + values.put(Video.Media.DATE_ADDED, nowInSec); + values.put(Video.Media.DATA, mDstFileInfo.mFile.getAbsolutePath()); + values.put(Video.Media.SIZE, mDstFileInfo.mFile.length()); + // Copy the data taken and location info from src. + String[] projection = new String[] { + VideoColumns.DATE_TAKEN, + VideoColumns.LATITUDE, + VideoColumns.LONGITUDE, + VideoColumns.RESOLUTION, + }; + + // Copy some info from the source file. + querySource(contentResolver, uri, projection, + new ContentResolverQueryCallback() { + @Override + public void onCursorResult(Cursor cursor) { + long timeTaken = cursor.getLong(0); + if (timeTaken > 0) { + values.put(Video.Media.DATE_TAKEN, timeTaken); + } + 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(Video.Media.LATITUDE, latitude); + values.put(Video.Media.LONGITUDE, longitude); + } + values.put(Video.Media.RESOLUTION, cursor.getString(3)); + + } + }); + + return contentResolver.insert(Video.Media.EXTERNAL_CONTENT_URI, values); + } + +} |