diff options
57 files changed, 2054 insertions, 1156 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/common/Utils.java b/gallerycommon/src/com/android/gallery3d/common/Utils.java index f5a266706..3a68745c4 100644 --- a/gallerycommon/src/com/android/gallery3d/common/Utils.java +++ b/gallerycommon/src/com/android/gallery3d/common/Utils.java @@ -76,7 +76,7 @@ public class Utils { // Throws IllegalArgumentException if the input is <= 0 or // the answer overflows. public static int nextPowerOf2(int n) { - if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException(); + if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n); n -= 1; n |= n >> 16; n |= n >> 8; diff --git a/src/com/android/gallery3d/anim/StateTransitionAnimation.java b/src/com/android/gallery3d/anim/StateTransitionAnimation.java new file mode 100644 index 000000000..ffe753db7 --- /dev/null +++ b/src/com/android/gallery3d/anim/StateTransitionAnimation.java @@ -0,0 +1,77 @@ +/* + * 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.anim; + +import android.view.animation.DecelerateInterpolator; + +import com.android.gallery3d.ui.GLCanvas; +import com.android.gallery3d.ui.GLView; +import com.android.gallery3d.ui.RawTexture; + +public class StateTransitionAnimation extends Animation { + private static final float BACKGROUND_ALPHA_FROM = 1f; + private static final float BACKGROUND_ALPHA_TO = 0f; + private static final float BACKGROUND_SCALE_FROM = 1f; + private static final float BACKGROUND_SCALE_TO = 0f; + private static final float FOREGROUND_ALPHA_FROM = 0.9f; + private static final float FOREGROUND_ALPHA_TO = 1f; + private static final float FOREGROUND_SCALE_FROM = 3f; + private static final float FOREGROUND_SCALE_TO = 1f; + + private float mCurrentForegroundScale; + private float mCurrentBackgroundScale; + private float mCurrentBackgroundAlpha; + private float mCurrentForegroundAlpha; + + public StateTransitionAnimation(int duration) { + setDuration(duration); + setInterpolator(new DecelerateInterpolator()); + } + + @Override + protected void onCalculate(float progress) { + mCurrentForegroundScale = FOREGROUND_SCALE_FROM + + (FOREGROUND_SCALE_TO - FOREGROUND_SCALE_FROM) * progress; + mCurrentForegroundAlpha = FOREGROUND_ALPHA_FROM + + (FOREGROUND_ALPHA_TO - FOREGROUND_ALPHA_FROM) * progress; + mCurrentBackgroundAlpha = BACKGROUND_ALPHA_FROM + + (BACKGROUND_ALPHA_TO - BACKGROUND_ALPHA_FROM) * progress; + mCurrentBackgroundScale = BACKGROUND_SCALE_FROM + + (BACKGROUND_SCALE_TO - BACKGROUND_SCALE_FROM) * progress; + } + + public void applyBackground(GLView view, GLCanvas canvas, RawTexture fadeTexture) { + canvas.clearBuffer(view.getBackgroundColor()); + canvas.save(); + canvas.setAlpha(mCurrentBackgroundAlpha); + int xOffset = view.getWidth() / 2; + int yOffset = view.getHeight() / 2; + canvas.translate(xOffset, yOffset); + canvas.scale(mCurrentBackgroundScale, mCurrentBackgroundScale, 1); + fadeTexture.draw(canvas, -xOffset, -yOffset); + canvas.restore(); + } + + public void applyForegroundTransformation(GLView view, GLCanvas canvas) { + int xOffset = view.getWidth() / 2; + int yOffset = view.getHeight() / 2; + canvas.translate(xOffset, yOffset); + canvas.scale(mCurrentForegroundScale, mCurrentForegroundScale, 1); + canvas.translate(-xOffset, -yOffset); + canvas.setAlpha(mCurrentForegroundAlpha); + } +} diff --git a/src/com/android/gallery3d/app/ActivityState.java b/src/com/android/gallery3d/app/ActivityState.java index 75327e477..3ea6896cb 100644 --- a/src/com/android/gallery3d/app/ActivityState.java +++ b/src/com/android/gallery3d/app/ActivityState.java @@ -36,6 +36,8 @@ import android.view.WindowManager; import com.android.gallery3d.R; import com.android.gallery3d.ui.GLView; +import com.android.gallery3d.ui.PreparePageFadeoutTexture; +import com.android.gallery3d.ui.RawTexture; import com.android.gallery3d.util.GalleryUtils; abstract public class ActivityState { @@ -66,11 +68,24 @@ abstract public class ActivityState { private boolean mPlugged = false; boolean mIsFinishing = false; + private static final String KEY_TRANSITION_IN = "transition-in"; + + private RawTexture mFadeOutTexture; + private GLView mContentPane; + private boolean mWantFadeOut = false; + private boolean mTransitionIn; + protected ActivityState() { } protected void setContentPane(GLView content) { - mActivity.getGLRoot().setContentPane(content); + mContentPane = content; + if (mTransitionIn) { + mContentPane.setFadeOutTexture(mFadeOutTexture); + mFadeOutTexture = null; + } + mContentPane.setBackgroundColor(getBackgroundColor()); + mActivity.getGLRoot().setContentPane(mContentPane); } void initialize(AbstractGalleryActivity activity, Bundle data) { @@ -84,6 +99,9 @@ abstract public class ActivityState { } protected void onBackPressed() { + if (mActivity.getStateManager().getStateCount() > 1) { + fadeOutOnNextPause(); + } mActivity.getStateManager().finishState(this); } @@ -157,10 +175,19 @@ abstract public class ActivityState { win.setAttributes(params); } + protected void fadeOutOnNextPause() { + mWantFadeOut = true; + } + protected void onPause() { if (0 != (mFlags & FLAG_SCREEN_ON_WHEN_PLUGGED)) { ((Activity) mActivity).unregisterReceiver(mPowerIntentReceiver); } + if (mWantFadeOut) { + mWantFadeOut = false; + mActivity.getTransitionStore().put(KEY_TRANSITION_IN, true); + PreparePageFadeoutTexture.prepareFadeOutTexture(mActivity, mContentPane); + } } // should only be called by StateManager @@ -214,6 +241,9 @@ abstract public class ActivityState { // a subclass of ActivityState should override the method to resume itself protected void onResume() { + mFadeOutTexture = mActivity.getTransitionStore().get( + PreparePageFadeoutTexture.KEY_FADE_TEXTURE); + mTransitionIn = mActivity.getTransitionStore().get(KEY_TRANSITION_IN, false); } protected boolean onCreateActionBar(Menu menu) { diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java index c956bc969..edaf8caa7 100644 --- a/src/com/android/gallery3d/app/AlbumPage.java +++ b/src/com/android/gallery3d/app/AlbumPage.java @@ -47,7 +47,6 @@ import com.android.gallery3d.ui.GLCanvas; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLView; import com.android.gallery3d.ui.PhotoFallbackEffect; -import com.android.gallery3d.ui.PreparePageFadeoutTexture; import com.android.gallery3d.ui.RelativePosition; import com.android.gallery3d.ui.SelectionManager; import com.android.gallery3d.ui.SlotView; @@ -139,11 +138,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster private final float mMatrix[] = new float[16]; @Override - protected void renderBackground(GLCanvas view) { - view.clearBuffer(getBackgroundColor()); - } - - @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom) { @@ -254,8 +248,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster } else { // Render transition in pressed state mAlbumView.setPressedIndex(slotIndex); - PreparePageFadeoutTexture.prepareFadeOutTexture(mActivity, mRootPane); - mAlbumView.setPressedIndex(-1); pickPhoto(slotIndex); } @@ -300,8 +292,12 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster data.putBoolean(PhotoPage.KEY_START_IN_FILMSTRIP, startInFilmstrip); data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, mMediaSet.isCameraRoll()); - mActivity.getStateManager().startStateForResult( - PhotoPage.class, REQUEST_PHOTO, data); + if (startInFilmstrip) { + mActivity.getStateManager().switchState(this, PhotoPage.class, data); + } else { + mActivity.getStateManager().startStateForResult( + PhotoPage.class, REQUEST_PHOTO, data); + } } } @@ -373,15 +369,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mLaunchedFromPhotoPage = mActivity.getStateManager().hasStateClass(PhotoPage.class); mInCameraApp = data.getBoolean(PhotoPage.KEY_APP_BRIDGE, false); - - // Don't show animation if it is restored or switched from filmstrip - if (!mLaunchedFromPhotoPage && restoreState == null && data != null) { - int[] center = data.getIntArray(KEY_SET_CENTER); - if (center != null) { - mOpenCenter.setAbsolutePosition(center[0], center[1]); - mSlotView.startScatteringAnimation(mOpenCenter); - } - } } @Override @@ -411,6 +398,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mAlbumDataAdapter.resume(); mAlbumView.resume(); + mAlbumView.setPressedIndex(-1); mActionModeHandler.resume(); if (!mInitialSynced) { setLoadingBit(BIT_LOADING_SYNC); @@ -561,7 +549,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster if (mAlbumDataAdapter == null || !mAlbumDataAdapter.isActive(slotIndex)) return; MediaItem item = mAlbumDataAdapter.get(slotIndex); if (item == null) return; - PreparePageFadeoutTexture.prepareFadeOutTexture(mActivity, mRootPane); TransitionStore transitions = mActivity.getTransitionStore(); transitions.put(PhotoPage.KEY_INDEX_HINT, slotIndex); transitions.put(PhotoPage.KEY_OPEN_ANIMATION_RECT, diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java index 1719b89f9..49ab683be 100644 --- a/src/com/android/gallery3d/app/AlbumSetPage.java +++ b/src/com/android/gallery3d/app/AlbumSetPage.java @@ -52,7 +52,6 @@ import com.android.gallery3d.ui.FadeTexture; import com.android.gallery3d.ui.GLCanvas; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLView; -import com.android.gallery3d.ui.PreparePageFadeoutTexture; import com.android.gallery3d.ui.SelectionManager; import com.android.gallery3d.ui.SlotView; import com.android.gallery3d.ui.SynchronizedHandler; @@ -130,11 +129,6 @@ public class AlbumSetPage extends ActivityState implements private final float mMatrix[] = new float[16]; @Override - protected void renderBackground(GLCanvas view) { - view.clearBuffer(getBackgroundColor()); - } - - @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom) { mEyePosition.resetPosition(); @@ -273,7 +267,6 @@ public class AlbumSetPage extends ActivityState implements & MediaObject.SUPPORT_IMPORT) != 0) { data.putBoolean(AlbumPage.KEY_AUTO_SELECT_ALL, true); } else if (!mGetContent && albumShouldOpenInFilmstrip(targetSet)) { - PreparePageFadeoutTexture.prepareFadeOutTexture(mActivity, mRootPane); data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT, mSlotView.getSlotRect(slotIndex, mRootPane)); data.putInt(PhotoPage.KEY_INDEX_HINT, 0); @@ -365,6 +358,7 @@ public class AlbumSetPage extends ActivityState implements } private boolean setupCameraButton() { + if (!GalleryUtils.isCameraAvailable(mActivity)) return false; RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity) .findViewById(R.id.gallery_root); if (galleryRoot == null) return false; diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java index 2b7450441..89ca63d44 100644 --- a/src/com/android/gallery3d/app/CropImage.java +++ b/src/com/android/gallery3d/app/CropImage.java @@ -61,6 +61,7 @@ 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; @@ -151,6 +152,7 @@ public class CropImage extends AbstractGalleryActivity { private BitmapRegionDecoder mRegionDecoder; private Bitmap mBitmapInIntent; private boolean mUseRegionDecoder = false; + private BitmapScreenNail mBitmapScreenNail; private ProgressDialog mProgressDialog; private Future<BitmapRegionDecoder> mLoadTask; @@ -813,8 +815,14 @@ public class CropImage extends AbstractGalleryActivity { BitmapUtils.UNCONSTRAINED, BACKUP_PIXEL_COUNT); mBitmap = regionDecoder.decodeRegion( new Rect(0, 0, width, height), options); - mCropView.setDataModel(new TileImageViewAdapter( - mBitmap, regionDecoder), mMediaItem.getFullImageRotation()); + + 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 { @@ -976,6 +984,15 @@ public class CropImage extends AbstractGalleryActivity { } } + @Override + protected void onDestroy() { + super.onDestroy(); + if (mBitmapScreenNail != null) { + mBitmapScreenNail.recycle(); + mBitmapScreenNail = null; + } + } + private void dismissProgressDialogIfShown() { if (mProgressDialog != null) { mProgressDialog.dismiss(); diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java index 66f2874f7..20f15be10 100644 --- a/src/com/android/gallery3d/app/PhotoDataAdapter.java +++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java @@ -30,11 +30,12 @@ import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.Path; -import com.android.gallery3d.ui.BitmapScreenNail; import com.android.gallery3d.ui.PhotoView; import com.android.gallery3d.ui.ScreenNail; import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.ui.TileImageViewAdapter; +import com.android.gallery3d.ui.TiledScreenNail; +import com.android.gallery3d.ui.TiledTexture; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.FutureListener; import com.android.gallery3d.util.MediaSetUtils; @@ -59,8 +60,8 @@ public class PhotoDataAdapter implements PhotoPage.Model { private static final int MSG_RUN_OBJECT = 3; private static final int MSG_UPDATE_IMAGE_REQUESTS = 4; - private static final int MIN_LOAD_COUNT = 8; - private static final int DATA_CACHE_SIZE = 32; + private static final int MIN_LOAD_COUNT = 16; + private static final int DATA_CACHE_SIZE = 256; private static final int SCREEN_NAIL_MAX = PhotoView.SCREEN_NAIL_MAX; private static final int IMAGE_CACHE_SIZE = 2 * SCREEN_NAIL_MAX + 1; @@ -162,6 +163,7 @@ public class PhotoDataAdapter implements PhotoPage.Model { private DataListener mDataListener; private final SourceListener mSourceListener = new SourceListener(); + private final TiledTexture.Uploader mUploader; // The path of the current viewing item will be stored in mItemPath. // If mItemPath is not null, mCurrentIndex is only a hint for where we @@ -183,6 +185,8 @@ public class PhotoDataAdapter implements PhotoPage.Model { Arrays.fill(mChanges, MediaObject.INVALID_DATA_VERSION); + mUploader = new TiledTexture.Uploader(activity.getGLRoot()); + mMainHandler = new SynchronizedHandler(activity.getGLRoot()) { @SuppressWarnings("unchecked") @Override @@ -301,8 +305,8 @@ public class PhotoDataAdapter implements PhotoPage.Model { entry.screenNailTask = null; // Combine the ScreenNails if we already have a BitmapScreenNail - if (entry.screenNail instanceof BitmapScreenNail) { - BitmapScreenNail original = (BitmapScreenNail) entry.screenNail; + if (entry.screenNail instanceof TiledScreenNail) { + TiledScreenNail original = (TiledScreenNail) entry.screenNail; screenNail = original.combine(screenNail); } @@ -321,6 +325,7 @@ public class PhotoDataAdapter implements PhotoPage.Model { } } updateImageRequests(); + updateScreenNailUploadQueue(); } private void updateFullImage(Path path, Future<BitmapRegionDecoder> future) { @@ -345,6 +350,8 @@ public class PhotoDataAdapter implements PhotoPage.Model { @Override public void resume() { mIsActive = true; + TiledTexture.prepareResources(); + mSource.addContentListener(mSourceListener); updateImageCache(); updateImageRequests(); @@ -371,6 +378,9 @@ public class PhotoDataAdapter implements PhotoPage.Model { } mImageCache.clear(); mTileProvider.clear(); + + mUploader.clear(); + TiledTexture.freeResources(); } private MediaItem getItem(int index) { @@ -402,6 +412,32 @@ public class PhotoDataAdapter implements PhotoPage.Model { fireDataChange(); } + private void uploadScreenNail(int offset) { + int index = mCurrentIndex + offset; + if (index < mActiveStart || index >= mActiveEnd) return; + + MediaItem item = getItem(index); + if (item == null) return; + + ImageEntry e = mImageCache.get(item.getPath()); + if (e == null) return; + + ScreenNail s = e.screenNail; + if (s instanceof TiledScreenNail) { + TiledTexture t = ((TiledScreenNail) s).getTexture(); + if (t != null && !t.isReady()) mUploader.addTexture(t); + } + } + + private void updateScreenNailUploadQueue() { + mUploader.clear(); + uploadScreenNail(0); + for (int i = 1; i < IMAGE_CACHE_SIZE; ++i) { + uploadScreenNail(i); + uploadScreenNail(-i); + } + } + @Override public void moveTo(int index) { updateCurrentIndex(index); @@ -680,7 +716,7 @@ public class PhotoDataAdapter implements PhotoPage.Model { bitmap = BitmapUtils.rotateBitmap(bitmap, mItem.getRotation() - mItem.getFullImageRotation(), true); } - return bitmap == null ? null : new BitmapScreenNail(bitmap); + return bitmap == null ? null : new TiledScreenNail(bitmap); } } @@ -729,7 +765,7 @@ public class PhotoDataAdapter implements PhotoPage.Model { private ScreenNail newPlaceholderScreenNail(MediaItem item) { int width = item.getWidth(); int height = item.getHeight(); - return new BitmapScreenNail(width, height); + return new TiledScreenNail(width, height); } // Returns the task if we started the task or the task is already started. @@ -791,8 +827,8 @@ public class PhotoDataAdapter implements PhotoPage.Model { if (entry.requestedScreenNail != item.getDataVersion()) { // This ScreenNail is outdated, we want to update it if it's // still a placeholder. - if (entry.screenNail instanceof BitmapScreenNail) { - BitmapScreenNail s = (BitmapScreenNail) entry.screenNail; + if (entry.screenNail instanceof TiledScreenNail) { + TiledScreenNail s = (TiledScreenNail) entry.screenNail; s.updatePlaceholderSize( item.getWidth(), item.getHeight()); } @@ -810,6 +846,8 @@ public class PhotoDataAdapter implements PhotoPage.Model { if (entry.screenNailTask != null) entry.screenNailTask.cancel(); if (entry.screenNail != null) entry.screenNail.recycle(); } + + updateScreenNailUploadQueue(); } private class FullImageListener diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index db7f3d9f1..ba9bf8c42 100644 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -34,12 +34,10 @@ import android.os.Message; import android.os.SystemClock; import android.view.Menu; import android.view.MenuItem; -import android.view.animation.AccelerateInterpolator; import android.widget.RelativeLayout; import android.widget.Toast; import com.android.gallery3d.R; -import com.android.gallery3d.anim.FloatAnimation; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.ComboAlbum; @@ -61,7 +59,6 @@ import com.android.gallery3d.data.SnailItem; import com.android.gallery3d.data.SnailSource; import com.android.gallery3d.picasasource.PicasaSource; import com.android.gallery3d.ui.AnimationTime; -import com.android.gallery3d.ui.BitmapScreenNail; import com.android.gallery3d.ui.DetailsHelper; import com.android.gallery3d.ui.DetailsHelper.CloseListener; import com.android.gallery3d.ui.DetailsHelper.DetailsSource; @@ -73,8 +70,6 @@ import com.android.gallery3d.ui.ImportCompleteListener; import com.android.gallery3d.ui.MenuExecutor; import com.android.gallery3d.ui.PhotoFallbackEffect; import com.android.gallery3d.ui.PhotoView; -import com.android.gallery3d.ui.PreparePageFadeoutTexture; -import com.android.gallery3d.ui.RawTexture; import com.android.gallery3d.ui.SelectionManager; import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.GalleryUtils; @@ -94,8 +89,9 @@ public class PhotoPage extends ActivityState implements private static final int MSG_ON_CAMERA_CENTER = 9; private static final int MSG_ON_PICTURE_CENTER = 10; private static final int MSG_REFRESH_IMAGE = 11; - private static final int MSG_UPDATE_DEFERRED = 12; + private static final int MSG_UPDATE_PHOTO_UI = 12; private static final int MSG_UPDATE_PROGRESS = 13; + private static final int MSG_UPDATE_DEFERRED = 14; private static final int HIDE_BARS_TIMEOUT = 3500; private static final int UNFREEZE_GLROOT_TIMEOUT = 250; @@ -165,7 +161,6 @@ public class PhotoPage extends ActivityState implements private boolean mTreatBackAsUp; private boolean mStartInFilmstrip; private boolean mInCameraRoll; - private boolean mStartedFromAlbumPage; private boolean mRecenterCameraOnResume = true; private long mCameraSwitchCutoff = 0; @@ -176,9 +171,7 @@ public class PhotoPage extends ActivityState implements private boolean mDeferredUpdateWaiting = false; private long mDeferUpdateUntil = Long.MAX_VALUE; - private RawTexture mFadeOutTexture; private Rect mOpenAnimationRect; - public static final int ANIM_TIME_OPENING = 300; // The item that is deleted (but it can still be undeleted before commiting) private Path mDeletePath; @@ -193,8 +186,15 @@ public class PhotoPage extends ActivityState implements private SupportedOperationsListener mSupportedOperationsListener = new SupportedOperationsListener() { @Override - public void onChange(int operations) { - mHandler.sendEmptyMessage(MSG_REFRESH_IMAGE); + public void onChange(MediaObject item, int operations) { + if (item == mCurrentPhoto) { + if (mPhotoView.getFilmMode() + && SystemClock.uptimeMillis() < mDeferUpdateUntil) { + requestDeferredUpdate(); + } else { + mHandler.sendEmptyMessage(MSG_UPDATE_PHOTO_UI); + } + } } }; @@ -213,13 +213,6 @@ public class PhotoPage extends ActivityState implements } } - private static class BackgroundFadeOut extends FloatAnimation { - public BackgroundFadeOut() { - super(1f, 0f, ANIM_TIME_OPENING); - setInterpolator(new AccelerateInterpolator(2f)); - } - } - private class UpdateProgressListener implements StitchingChangeListener { @Override @@ -246,8 +239,6 @@ public class PhotoPage extends ActivityState implements } }; - private final FloatAnimation mBackgroundFade = new BackgroundFadeOut(); - @Override protected int getBackgroundColorId() { return R.color.photo_background; @@ -255,28 +246,6 @@ public class PhotoPage extends ActivityState implements private final GLView mRootPane = new GLView() { @Override - protected void renderBackground(GLCanvas view) { - if (mFadeOutTexture != null) { - if (mBackgroundFade.calculate(AnimationTime.get())) invalidate(); - if (!mBackgroundFade.isActive()) { - mFadeOutTexture = null; - mOpenAnimationRect = null; - BitmapScreenNail.enableDrawPlaceholder(); - } else { - float fadeAlpha = mBackgroundFade.get(); - if (fadeAlpha < 1f) { - view.clearBuffer(getBackgroundColor()); - view.setAlpha(fadeAlpha); - } - mFadeOutTexture.draw(view, 0, 0); - view.setAlpha(1f - fadeAlpha); - return; - } - } - view.clearBuffer(getBackgroundColor()); - } - - @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom) { mPhotoView.layout(0, 0, right - left, bottom - top); @@ -366,6 +335,12 @@ public class PhotoPage extends ActivityState implements break; } case MSG_REFRESH_IMAGE: { + final MediaItem photo = mCurrentPhoto; + mCurrentPhoto = null; + updateCurrentPhoto(photo); + break; + } + case MSG_UPDATE_PHOTO_UI: { updateUIForCurrentPhoto(); break; } @@ -388,9 +363,6 @@ public class PhotoPage extends ActivityState implements mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false); mStartInFilmstrip = data.getBoolean(KEY_START_IN_FILMSTRIP, false); mInCameraRoll = data.getBoolean(KEY_IN_CAMERA_ROLL, false); - mStartedFromAlbumPage = - data.getInt(KEY_ALBUMPAGE_TRANSITION, - MSG_ALBUMPAGE_NONE) == MSG_ALBUMPAGE_STARTED; mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0); if (mSetPathString != null) { mShowSpinner = true; @@ -548,6 +520,9 @@ public class PhotoPage extends ActivityState implements mProgressBar = new PhotoPageProgressBar(mActivity, galleryRoot); mProgressListener = new UpdateProgressListener(); progressManager.addChangeListener(mProgressListener); + if (mSecureAlbum != null) { + progressManager.addChangeListener(mSecureAlbum); + } } } } @@ -953,11 +928,10 @@ public class PhotoPage extends ActivityState implements }; private void switchToGrid() { - if (mStartedFromAlbumPage) { + if (mActivity.getStateManager().hasStateClass(AlbumPage.class)) { onUpPressed(); } else { if (mOriginalSetPathString == null) return; - preparePhotoFallbackView(); Bundle data = new Bundle(getData()); data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, @@ -976,7 +950,11 @@ public class PhotoPage extends ActivityState implements mActivity.getTransitionStore().put(KEY_RETURN_INDEX_HINT, mAppBridge != null ? mCurrentIndex - 1 : mCurrentIndex); - mActivity.getStateManager().startState(AlbumPage.class, data); + if (mInCameraRoll && mAppBridge != null) { + mActivity.getStateManager().startState(AlbumPage.class, data); + } else { + mActivity.getStateManager().switchState(this, AlbumPage.class, data); + } } } @@ -1287,7 +1265,6 @@ public class PhotoPage extends ActivityState implements // Hide the detail dialog on exit if (mShowDetails) hideDetails(); if (mModel != null) { - if (isFinishing()) preparePhotoFallbackView(); mModel.pause(); } mPhotoView.pause(); @@ -1351,18 +1328,6 @@ public class PhotoPage extends ActivityState implements } else if (albumPageTransition == MSG_ALBUMPAGE_PICKED) { mPhotoView.setFilmMode(false); } - - mFadeOutTexture = transitions.get(PreparePageFadeoutTexture.KEY_FADE_TEXTURE); - if (mFadeOutTexture != null) { - mBackgroundFade.start(); - BitmapScreenNail.disableDrawPlaceholder(); - mOpenAnimationRect = - albumPageTransition == MSG_ALBUMPAGE_NONE ? - (Rect) mData.getParcelable(KEY_OPEN_ANIMATION_RECT) : - (Rect) transitions.get(KEY_OPEN_ANIMATION_RECT); - mPhotoView.setOpenAnimationRect(mOpenAnimationRect); - mBackgroundFade.start(); - } } @Override diff --git a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java index 2f6f16f3a..00f2fe78f 100644 --- a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java +++ b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java @@ -27,6 +27,7 @@ import com.android.gallery3d.common.BitmapUtils; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.Path; +import com.android.gallery3d.ui.BitmapScreenNail; import com.android.gallery3d.ui.PhotoView; import com.android.gallery3d.ui.ScreenNail; import com.android.gallery3d.ui.SynchronizedHandler; @@ -50,6 +51,7 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter private PhotoView mPhotoView; private ThreadPool mThreadPool; private int mLoadingState = LOADING_INIT; + private BitmapScreenNail mBitmapScreenNail; public SinglePhotoDataAdapter( AbstractGalleryActivity activity, PhotoView view, MediaItem item) { @@ -113,6 +115,11 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter return false; } + private void setScreenNail(Bitmap bitmap, int width, int height) { + mBitmapScreenNail = new BitmapScreenNail(bitmap); + setScreenNail(mBitmapScreenNail, width, height); + } + private void onDecodeLargeComplete(ImageBundle bundle) { try { setScreenNail(bundle.backupImage, @@ -162,6 +169,10 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter if (task.get() == null) { mTask = null; } + if (mBitmapScreenNail != null) { + mBitmapScreenNail.recycle(); + mBitmapScreenNail = null; + } } @Override diff --git a/src/com/android/gallery3d/app/StateManager.java b/src/com/android/gallery3d/app/StateManager.java index c041b0e6f..10de5d201 100644 --- a/src/com/android/gallery3d/app/StateManager.java +++ b/src/com/android/gallery3d/app/StateManager.java @@ -57,6 +57,7 @@ public class StateManager { } if (!mStack.isEmpty()) { ActivityState top = getTopState(); + top.fadeOutOnNextPause(); if (mIsResumed) top.onPause(); } state.initialize(mActivity, data); @@ -81,6 +82,7 @@ public class StateManager { if (!mStack.isEmpty()) { ActivityState as = getTopState(); + as.fadeOutOnNextPause(); as.mReceivedResults = state.mResult; if (mIsResumed) as.onPause(); } else { @@ -207,6 +209,10 @@ public class StateManager { } // Remove the top state. mStack.pop(); + if (!data.containsKey(PhotoPage.KEY_APP_BRIDGE)) { + // Do not do the fade out stuff when we are switching camera modes + oldState.fadeOutOnNextPause(); + } if (mIsResumed) oldState.onPause(); oldState.onDestroy(); diff --git a/src/com/android/gallery3d/app/TrimVideo.java b/src/com/android/gallery3d/app/TrimVideo.java index 09a2abdf0..01fe462c2 100644 --- a/src/com/android/gallery3d/app/TrimVideo.java +++ b/src/com/android/gallery3d/app/TrimVideo.java @@ -268,9 +268,8 @@ public class TrimVideo extends Activity implements return; } if (Math.abs(mVideoView.getDuration() - delta) < 100) { - Toast.makeText(getApplicationContext(), - getString(R.string.trim_too_long), - Toast.LENGTH_SHORT).show(); + // If no change has been made, go back + onBackPressed(); return; } // Use the default save directory if the source directory cannot be diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java index 3d2c0c2f0..408a24b13 100644 --- a/src/com/android/gallery3d/data/DataManager.java +++ b/src/com/android/gallery3d/data/DataManager.java @@ -85,9 +85,6 @@ public class DataManager { private static final String TOP_LOCAL_VIDEO_SET_PATH = "/local/video"; - private static final String ACTION_DELETE_PICTURE = - "com.android.gallery3d.action.DELETE_PICTURE"; - public static final Comparator<MediaItem> sDateTakenComparator = new DateTakenComparator(); @@ -335,15 +332,6 @@ public class DataManager { } } - // Sends a local broadcast if a local image or video is deleted. This is - // used to update the thumbnail shown in the camera app. - public void broadcastLocalDeletion() { - LocalBroadcastManager manager = LocalBroadcastManager.getInstance( - mApplication.getAndroidContext()); - Intent intent = new Intent(ACTION_DELETE_PICTURE); - manager.sendBroadcast(intent); - } - private static class NotifyBroker extends ContentObserver { private WeakHashMap<ChangeNotifier, Object> mNotifiers = new WeakHashMap<ChangeNotifier, Object>(); diff --git a/src/com/android/gallery3d/data/EmptyAlbumImage.java b/src/com/android/gallery3d/data/EmptyAlbumImage.java index dbbc01a33..6f8c37c6b 100644 --- a/src/com/android/gallery3d/data/EmptyAlbumImage.java +++ b/src/com/android/gallery3d/data/EmptyAlbumImage.java @@ -24,7 +24,7 @@ public class EmptyAlbumImage extends ActionImage { private static final String TAG = "EmptyAlbumImage"; public EmptyAlbumImage(Path path, GalleryApp application) { - super(path, application, R.drawable.ic_menu_revert_holo_dark); + super(path, application, R.drawable.placeholder_empty); } @Override diff --git a/src/com/android/gallery3d/data/LocalAlbum.java b/src/com/android/gallery3d/data/LocalAlbum.java index 4682e7792..8bbc364ec 100644 --- a/src/com/android/gallery3d/data/LocalAlbum.java +++ b/src/com/android/gallery3d/data/LocalAlbum.java @@ -267,7 +267,6 @@ public class LocalAlbum extends MediaSet { GalleryUtils.assertNotInRenderThread(); mResolver.delete(mBaseUri, mWhereClause, new String[]{String.valueOf(mBucketId)}); - mApplication.getDataManager().broadcastLocalDeletion(); } @Override diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java index dba6b68eb..61961d87a 100644 --- a/src/com/android/gallery3d/data/LocalImage.java +++ b/src/com/android/gallery3d/data/LocalImage.java @@ -288,8 +288,6 @@ public class LocalImage extends LocalMediaItem { @Override public void setSupportedOperationsListener(SupportedOperationsListener l) { synchronized (mLock) { - if (mPanoramaMetadataInitialized) return; // no more updates - if (l == null) { if (mGetPanoMetadataTask != null) { mGetPanoMetadataTask.cancel(); @@ -308,7 +306,7 @@ public class LocalImage extends LocalMediaItem { mPanoramaMetadata = future.get(); mPanoramaMetadataInitialized = true; if (mListener != null) { - mListener.onChange(getSupportedOperations()); + mListener.onChange(LocalImage.this, getSupportedOperations()); } } }); @@ -324,7 +322,6 @@ public class LocalImage extends LocalMediaItem { Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; mApplication.getContentResolver().delete(baseUri, "_id=?", new String[]{String.valueOf(id)}); - mApplication.getDataManager().broadcastLocalDeletion(); } private static String getExifOrientation(int orientation) { diff --git a/src/com/android/gallery3d/data/LocalVideo.java b/src/com/android/gallery3d/data/LocalVideo.java index 5b6ee4b33..c876d81b1 100644 --- a/src/com/android/gallery3d/data/LocalVideo.java +++ b/src/com/android/gallery3d/data/LocalVideo.java @@ -187,7 +187,6 @@ public class LocalVideo extends LocalMediaItem { Uri baseUri = Video.Media.EXTERNAL_CONTENT_URI; mApplication.getContentResolver().delete(baseUri, "_id=?", new String[]{String.valueOf(id)}); - mApplication.getDataManager().broadcastLocalDeletion(); } @Override diff --git a/src/com/android/gallery3d/data/MediaObject.java b/src/com/android/gallery3d/data/MediaObject.java index 382a5c792..14cd5242a 100644 --- a/src/com/android/gallery3d/data/MediaObject.java +++ b/src/com/android/gallery3d/data/MediaObject.java @@ -50,7 +50,7 @@ public abstract class MediaObject { public static final int SUPPORT_ALL = 0xffffffff; public static interface SupportedOperationsListener { - public void onChange(int operations); + public void onChange(MediaObject item, int operations); } // These are the bits returned from getMediaType(): @@ -100,7 +100,7 @@ public abstract class MediaObject { } public int getSupportedOperations(boolean getAll) { - return 0; + return getSupportedOperations(); } public void setSupportedOperationsListener(SupportedOperationsListener l) { diff --git a/src/com/android/gallery3d/data/SecureAlbum.java b/src/com/android/gallery3d/data/SecureAlbum.java index c666bdc75..0a8c5a827 100644 --- a/src/com/android/gallery3d/data/SecureAlbum.java +++ b/src/com/android/gallery3d/data/SecureAlbum.java @@ -24,12 +24,13 @@ import android.provider.MediaStore.MediaColumns; import android.provider.MediaStore.Video; import com.android.gallery3d.app.GalleryApp; +import com.android.gallery3d.app.StitchingChangeListener; import com.android.gallery3d.util.MediaSetUtils; import java.util.ArrayList; // This class lists all media items added by the client. -public class SecureAlbum extends MediaSet { +public class SecureAlbum extends MediaSet implements StitchingChangeListener { @SuppressWarnings("unused") private static final String TAG = "SecureAlbum"; private static final String[] PROJECTION = {MediaColumns._ID}; @@ -183,4 +184,18 @@ public class SecureAlbum extends MediaSet { public boolean isLeafAlbum() { return true; } + + @Override + public void onStitchingQueued(Uri uri) { + int id = Integer.parseInt(uri.getLastPathSegment()); + addMediaItem(false, id); + } + + @Override + public void onStitchingResult(Uri uri) { + } + + @Override + public void onStitchingProgress(Uri uri, final int progress) { + } } diff --git a/src/com/android/gallery3d/data/UriImage.java b/src/com/android/gallery3d/data/UriImage.java index 5fab667b8..aaa36a917 100644 --- a/src/com/android/gallery3d/data/UriImage.java +++ b/src/com/android/gallery3d/data/UriImage.java @@ -255,8 +255,6 @@ public class UriImage extends MediaItem { @Override public void setSupportedOperationsListener(SupportedOperationsListener l) { synchronized (mLock) { - if (mPanoramaMetadataInitialized) return; // no more updates - if (l != null) { if (mGetPanoMetadataTask != null) { mGetPanoMetadataTask.cancel(); @@ -275,7 +273,7 @@ public class UriImage extends MediaItem { mPanoramaMetadata = future.get(); mPanoramaMetadataInitialized = true; if (mListener != null) { - mListener.onChange(getSupportedOperations()); + mListener.onChange(UriImage.this, getSupportedOperations()); } } }); diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index f2b817c17..f76179d8b 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -31,6 +31,7 @@ import android.widget.ListView; import android.widget.SeekBar; import android.widget.ShareActionProvider; import android.widget.ShareActionProvider.OnShareTargetSelectedListener; +import android.widget.Toast; import com.android.gallery3d.R; import com.android.gallery3d.filtershow.cache.ImageLoader; @@ -60,7 +61,9 @@ import com.android.gallery3d.filtershow.imageshow.ImageZoom; import com.android.gallery3d.filtershow.presets.ImagePreset; import com.android.gallery3d.filtershow.provider.SharedImageProvider; import com.android.gallery3d.filtershow.tools.SaveCopyTask; +import com.android.gallery3d.filtershow.ui.ImageButtonTitle; import com.android.gallery3d.filtershow.ui.ImageCurves; +import com.android.gallery3d.filtershow.ui.Spline; import java.io.File; import java.lang.ref.WeakReference; @@ -77,7 +80,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, private ImageBorder mImageBorders = null; private ImageStraighten mImageStraighten = null; private ImageZoom mImageZoom = null; - private final ImageCrop mImageCrop = null; + private ImageCrop mImageCrop = null; private ImageRotate mImageRotate = null; private ImageFlip mImageFlip = null; @@ -96,6 +99,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, private static final int SELECT_PICTURE = 1; private static final String LOGTAG = "FilterShowActivity"; protected static final boolean ANIMATE_PANELS = true; + private static int mImageBorderSize = 40; private boolean mShowingHistoryPanel = false; private boolean mShowingImageStatePanel = false; @@ -117,6 +121,19 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, ImageFilterRS.setRenderScriptContext(this); + // TODO: get those values from XML. + ImageShow.setTextSize((int) getPixelsFromDip(12)); + ImageShow.setTextPadding((int) getPixelsFromDip(10)); + ImageButtonTitle.setTextSize((int) getPixelsFromDip(12)); + ImageButtonTitle.setTextPadding((int) getPixelsFromDip(10)); + ImageSmallFilter.setMargin((int) getPixelsFromDip(6)); + ImageSmallFilter.setTextMargin((int) getPixelsFromDip(4)); + mImageBorderSize = (int) getPixelsFromDip(20); + Drawable curveHandle = getResources().getDrawable(R.drawable.camera_crop_holo); + int curveHandleSize = (int) getResources().getDimension(R.dimen.crop_indicator_size); + Spline.setCurveHandle(curveHandle, curveHandleSize); + Spline.setCurveWidth((int) getPixelsFromDip(3)); + setContentView(R.layout.filtershow_activity); ActionBar actionBar = getActionBar(); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); @@ -129,7 +146,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, } }); - mImageLoader = new ImageLoader(getApplicationContext()); + mImageLoader = new ImageLoader(this, getApplicationContext()); LinearLayout listFilters = (LinearLayout) findViewById(R.id.listFilters); LinearLayout listBorders = (LinearLayout) findViewById(R.id.listBorders); @@ -140,8 +157,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageBorders = (ImageBorder) findViewById(R.id.imageBorder); mImageStraighten = (ImageStraighten) findViewById(R.id.imageStraighten); mImageZoom = (ImageZoom) findViewById(R.id.imageZoom); - // TODO: implement crop - // mImageCrop = (ImageCrop) findViewById(R.id.imageCrop); + mImageCrop = (ImageCrop) findViewById(R.id.imageCrop); mImageRotate = (ImageRotate) findViewById(R.id.imageRotate); mImageFlip = (ImageFlip) findViewById(R.id.imageFlip); @@ -150,8 +166,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageViews.add(mImageBorders); mImageViews.add(mImageStraighten); mImageViews.add(mImageZoom); - // TODO: implement crop - // mImageViews.add(mImageCrop); + mImageViews.add(mImageCrop); mImageViews.add(mImageRotate); mImageViews.add(mImageFlip); @@ -180,9 +195,8 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageStraighten.setMaster(mImageShow); mImageZoom.setImageLoader(mImageLoader); mImageZoom.setMaster(mImageShow); - // TODO: implement crop - // mImageCrop.setImageLoader(mImageLoader); - // mImageCrop.setMaster(mImageShow); + mImageCrop.setImageLoader(mImageLoader); + mImageCrop.setMaster(mImageShow); mImageRotate.setImageLoader(mImageLoader); mImageRotate.setMaster(mImageShow); mImageFlip.setImageLoader(mImageLoader); @@ -192,8 +206,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mPanelController.addImageView(findViewById(R.id.imageCurves)); mPanelController.addImageView(findViewById(R.id.imageBorder)); mPanelController.addImageView(findViewById(R.id.imageStraighten)); - // TODO: implement crop - // mPanelController.addImageView(findViewById(R.id.imageCrop)); + mPanelController.addImageView(findViewById(R.id.imageCrop)); mPanelController.addImageView(findViewById(R.id.imageRotate)); mPanelController.addImageView(findViewById(R.id.imageFlip)); mPanelController.addImageView(findViewById(R.id.imageZoom)); @@ -203,8 +216,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mPanelController.addPanel(mGeometryButton, mListGeometry, 2); mPanelController.addComponent(mGeometryButton, findViewById(R.id.straightenButton)); - // TODO: implement crop -// mPanelController.addComponent(mGeometryButton, findViewById(R.id.cropButton)); + mPanelController.addComponent(mGeometryButton, findViewById(R.id.cropButton)); mPanelController.addComponent(mGeometryButton, findViewById(R.id.rotateButton)); mPanelController.addComponent(mGeometryButton, findViewById(R.id.flipButton)); @@ -298,7 +310,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mPanelController.addComponent(mColorsButton, findViewById(R.id.shadowRecoveryButton)); mPanelController.addView(findViewById(R.id.applyEffect)); - + mPanelController.addView(findViewById(R.id.pickCurvesChannel)); findViewById(R.id.resetOperationsButton).setOnClickListener( createOnClickResetOperationsButton()); @@ -523,8 +535,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mCurrentImageSmallFilter = filter; filter.setPreviousImageSmallFilter(null); - preset.setIsFx(true); - filter.setImagePreset(preset); + filter.setImageFilter(new ImageFilterFx(null,ImageFilterFx.ORIG)); filter.setController(this); filter.setImageLoader(mImageLoader); @@ -565,10 +576,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, borders[p++] = new ImageFilterBorder(npd1); Drawable npd2 = getResources().getDrawable(R.drawable.filtershow_border_brush); borders[p++] = new ImageFilterBorder(npd2); - borders[p++] = new ImageFilterParametricBorder(Color.BLACK, 100, 0); - borders[p++] = new ImageFilterParametricBorder(Color.BLACK, 100, 100); - borders[p++] = new ImageFilterParametricBorder(Color.WHITE, 100, 0); - borders[p++] = new ImageFilterParametricBorder(Color.WHITE, 100, 100); + borders[p++] = new ImageFilterParametricBorder(Color.BLACK, mImageBorderSize, 0); + borders[p++] = new ImageFilterParametricBorder(Color.BLACK, mImageBorderSize, mImageBorderSize); + borders[p++] = new ImageFilterParametricBorder(Color.WHITE, mImageBorderSize, 0); + borders[p++] = new ImageFilterParametricBorder(Color.WHITE, mImageBorderSize, mImageBorderSize); ImageSmallFilter previousFilter = null; for (int i = 0; i < p; i++) { @@ -699,6 +710,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, adapter.reset(); ImagePreset original = new ImagePreset(adapter.getItem(0)); mImageShow.setImagePreset(original); + mPanelController.resetParameters(); invalidateViews(); } @@ -712,6 +724,20 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, }; } + @Override + public void onBackPressed() { + if (mPanelController.onBackPressed()) { + finish(); + } + } + + public void cannotLoadImage() { + CharSequence text = getString(R.string.cannot_load_image); + Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT); + toast.show(); + finish(); + } + // ////////////////////////////////////////////////////////////////////////////// public float getPixelsFromDip(float value) { diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java index f13cdd497..a21bb4fe1 100644 --- a/src/com/android/gallery3d/filtershow/PanelController.java +++ b/src/com/android/gallery3d/filtershow/PanelController.java @@ -131,12 +131,12 @@ public class PanelController implements OnClickListener { public void setEffectName(String effectName) { mEffectName = effectName; - showParameter(true); - updateText(); + setShowParameter(true); } - public void showParameter(boolean s) { + public void setShowParameter(boolean s) { mShowParameterValue = s; + updateText(); } public void updateText() { @@ -229,12 +229,26 @@ public class PanelController implements OnClickListener { imageShow.setPanelController(this); } + public void resetParameters() { + mCurrentImage.resetParameter(); + showPanel(mCurrentPanel); + mCurrentImage.select(); + } + + public boolean onBackPressed() { + if (mUtilityPanel == null || !mUtilityPanel.selected()) { + return true; + } + resetParameters(); + return false; + } + public void onNewValue(int value) { mUtilityPanel.onNewValue(value); } public void showParameter(boolean s) { - mUtilityPanel.showParameter(s); + mUtilityPanel.setShowParameter(s); } public void setCurrentPanel(View panel) { @@ -379,6 +393,12 @@ public class PanelController implements OnClickListener { } } + if (view.getId() == R.id.pickCurvesChannel) { + ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves); + curves.nextChannel(); + return; + } + if (mCurrentImage != null) { mCurrentImage.unselect(); } @@ -390,16 +410,13 @@ public class PanelController implements OnClickListener { mUtilityPanel.setEffectName(ename); break; } - /* - // TODO: implement crop case R.id.cropButton: { mCurrentImage = showImageView(R.id.imageCrop); String ename = mCurrentImage.getContext().getString(R.string.crop); mUtilityPanel.setEffectName(ename); - mUtilityPanel.showParameter(false); + mUtilityPanel.setShowParameter(false); break; } - */ case R.id.rotateButton: { mCurrentImage = showImageView(R.id.imageRotate); String ename = mCurrentImage.getContext().getString(R.string.rotate); @@ -410,7 +427,7 @@ public class PanelController implements OnClickListener { mCurrentImage = showImageView(R.id.imageFlip); String ename = mCurrentImage.getContext().getString(R.string.flip); mUtilityPanel.setEffectName(ename); - mUtilityPanel.showParameter(false); + mUtilityPanel.setShowParameter(false); break; } case R.id.vignetteButton: { @@ -422,11 +439,9 @@ public class PanelController implements OnClickListener { } case R.id.curvesButtonRGB: { ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves); - String ename = mCurrentImage.getContext().getString(R.string.curvesRGB); + String ename = curves.getContext().getString(R.string.curvesRGB); mUtilityPanel.setEffectName(ename); - curves.setUseRed(true); - curves.setUseGreen(true); - curves.setUseBlue(true); + mUtilityPanel.setShowParameter(false); curves.reloadCurve(); mCurrentImage = curves; break; @@ -456,6 +471,7 @@ public class PanelController implements OnClickListener { mCurrentImage = showImageView(R.id.imageShow).setShowControls(false); String ename = mCurrentImage.getContext().getString(R.string.wbalance); mUtilityPanel.setEffectName(ename); + mUtilityPanel.setShowParameter(false); ensureFilter("WBalance"); break; } diff --git a/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java b/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java index 67bd49b1c..25d1db414 100644 --- a/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java +++ b/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java @@ -62,7 +62,7 @@ public class DirectPresetCache implements Cache { private CachedPreset getCachedPreset(ImagePreset preset) { for (int i = 0; i < mCache.size(); i++) { CachedPreset cache = mCache.elementAt(i); - if (cache.mPreset == preset && !cache.mBusy) { + if (cache.mPreset == preset) { return cache; } } @@ -73,7 +73,7 @@ public class DirectPresetCache implements Cache { public Bitmap get(ImagePreset preset) { // Log.v(LOGTAG, "get preset " + preset.name() + " : " + preset); CachedPreset cache = getCachedPreset(preset); - if (cache != null) { + if (cache != null && !cache.mBusy) { return cache.mBitmap; } // Log.v(LOGTAG, "didn't find preset " + preset.name() + " : " + preset @@ -138,18 +138,21 @@ public class DirectPresetCache implements Cache { public void prepare(ImagePreset preset) { // Log.v(LOGTAG, "prepare preset " + preset.name() + " : " + preset); CachedPreset cache = getCachedPreset(preset); - if (cache == null) { - if (mCache.size() < mCacheSize) { - cache = new CachedPreset(); - mCache.add(cache); - } else { - cache = getOldestCachedPreset(); + if (cache == null || (cache.mBitmap == null && !cache.mBusy)) { + if (cache == null) { + if (mCache.size() < mCacheSize) { + cache = new CachedPreset(); + mCache.add(cache); + } else { + cache = getOldestCachedPreset(); + } } if (cache != null) { cache.mPreset = preset; + willCompute(cache); } } - willCompute(cache); + } } diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java index e00a1b77d..eeba4da41 100644 --- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java +++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java @@ -48,14 +48,25 @@ public class ImageLoader { private int mOrientation = 0; private HistoryAdapter mAdapter = null; - private static int PORTRAIT_ORIENTATION = 6; + + private FilterShowActivity mActivity = null; + + private static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL; + private static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90; + private static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180; + private static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270; + private static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL; + private static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL; + private static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE; + private static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE; private Context mContext = null; private Uri mUri = null; private Rect mOriginalBounds = null; - public ImageLoader(Context context) { + public ImageLoader(FilterShowActivity activity, Context context) { + mActivity = activity; mContext = context; mCache = new DelayedPresetCache(this, 30); mHiresCache = new DelayedPresetCache(this, 2); @@ -66,6 +77,10 @@ public class ImageLoader { mOrientation = getOrientation(uri); mOriginalBitmapSmall = loadScaledBitmap(uri, 160); + if (mOriginalBitmapSmall == null) { + // Couldn't read the bitmap, let's exit + mActivity.cannotLoadImage(); + } mOriginalBitmapLarge = loadScaledBitmap(uri, size); updateBitmaps(); } @@ -90,7 +105,20 @@ public class ImageLoader { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); - return cursor.moveToNext() ? cursor.getInt(0) : -1; + 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){ return ExifInterface.ORIENTATION_UNDEFINED; } finally { @@ -111,18 +139,55 @@ public class ImageLoader { } private void updateBitmaps() { + if (mOrientation > 1) { + mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall,mOrientation); + mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge,mOrientation); + } mCache.setOriginalBitmap(mOriginalBitmapSmall); mHiresCache.setOriginalBitmap(mOriginalBitmapLarge); - if (mOrientation == PORTRAIT_ORIENTATION) { - mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall); - mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge); - } warnListeners(); } - private Bitmap rotateToPortrait(Bitmap bitmap) { - Matrix matrix = new Matrix(); - matrix.postRotate(90); + private 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_NORMAL: + 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; + default: + } + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java index 01b280b6e..3e8d298a3 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java @@ -9,12 +9,7 @@ public class ImageFilterCurves extends ImageFilter { private static final String LOGTAG = "ImageFilterCurves"; - private final float[] mCurve = new float[256]; - - private boolean mUseRed = true; - private boolean mUseGreen = true; - private boolean mUseBlue = true; - private Spline mSpline = null; + private final Spline[] mSplines = new Spline[4]; public ImageFilterCurves() { mName = "Curves"; @@ -23,29 +18,12 @@ public class ImageFilterCurves extends ImageFilter { @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterCurves filter = (ImageFilterCurves) super.clone(); - filter.setCurve(mCurve); - filter.setSpline(new Spline(mSpline)); - return filter; - } - - public void setUseRed(boolean value) { - mUseRed = value; - } - - public void setUseGreen(boolean value) { - mUseGreen = value; - } - - public void setUseBlue(boolean value) { - mUseBlue = value; - } - - public void setCurve(float[] curve) { - for (int i = 0; i < curve.length; i++) { - if (i < 256) { - mCurve[i] = curve[i]; + for (int i = 0; i < 4; i++) { + if (mSplines[i] != null) { + filter.setSpline(new Spline(mSplines[i]), i); } } + return filter; } @Override @@ -55,36 +33,48 @@ public class ImageFilterCurves extends ImageFilter { return false; } ImageFilterCurves curve = (ImageFilterCurves) filter; - for (int i = 0; i < 256; i++) { - if (curve.mCurve[i] != mCurve[i]) { + for (int i = 0; i < 4; i++) { + if (mSplines[i] != curve.mSplines[i]) { return false; } } return true; } - public void populateArray(int[] array) { + public void populateArray(int[] array, int curveIndex) { + Spline spline = mSplines[curveIndex]; + if (spline == null) { + return; + } + float[] curve = spline.getAppliedCurve(); for (int i = 0; i < 256; i++) { - array[i] = (int) (mCurve[i]); + array[i] = (int) (curve[i] * 255); } } @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { + if (!mSplines[Spline.RGB].isOriginal()) { + int[] rgbGradient = new int[256]; + populateArray(rgbGradient, Spline.RGB); + nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(), + rgbGradient, rgbGradient, rgbGradient); + } + int[] redGradient = null; - if (mUseRed) { + if (!mSplines[Spline.RED].isOriginal()) { redGradient = new int[256]; - populateArray(redGradient); + populateArray(redGradient, Spline.RED); } int[] greenGradient = null; - if (mUseGreen) { + if (!mSplines[Spline.GREEN].isOriginal()) { greenGradient = new int[256]; - populateArray(greenGradient); + populateArray(greenGradient, Spline.GREEN); } int[] blueGradient = null; - if (mUseBlue) { + if (!mSplines[Spline.BLUE].isOriginal()) { blueGradient = new int[256]; - populateArray(blueGradient); + populateArray(blueGradient, Spline.BLUE); } nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(), @@ -92,11 +82,11 @@ public class ImageFilterCurves extends ImageFilter { return bitmap; } - public void setSpline(Spline spline) { - mSpline = spline; + public void setSpline(Spline spline, int splineIndex) { + mSplines[splineIndex] = spline; } - public Spline getSpline() { - return mSpline; + public Spline getSpline(int splineIndex) { + return mSplines[splineIndex]; } } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java index 1575b18bb..7d8f41537 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java @@ -12,7 +12,7 @@ import java.util.Arrays; public class ImageFilterFx extends ImageFilter { private static final String TAG = "ImageFilterFx"; Bitmap fxBitmap; - + public static final String ORIG = "Original"; public ImageFilterFx(Bitmap fxBitmap,String name) { setFilterType(TYPE_FX); mName = name; @@ -29,6 +29,8 @@ public class ImageFilterFx extends ImageFilter { native protected void nativeApplyFilter(Bitmap bitmap, int w, int h,Bitmap fxBitmap, int fxw, int fxh); public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { + if (fxBitmap==null) + return bitmap; int w = bitmap.getWidth(); int h = bitmap.getHeight(); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java index 08c09fb8e..3d48b7e53 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java @@ -20,6 +20,8 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; @@ -61,23 +63,19 @@ public class ImageFilterGeometry extends ImageFilter { native protected void nativeApplyFilterStraighten(Bitmap src, int srcWidth, int srcHeight, Bitmap dst, int dstWidth, int dstHeight, float straightenAngle); - public Matrix buildMatrix(Bitmap bitmap, boolean rotated) { - Matrix drawMatrix = new Matrix(); - float dx = bitmap.getWidth() / 2.0f; - float dy = bitmap.getHeight() / 2.0f; - - Matrix flipper = mGeometry.getFlipMatrix(bitmap.getWidth(), bitmap.getHeight()); - drawMatrix.postConcat(flipper); - drawMatrix.postTranslate(-dx, -dy); - drawMatrix.postScale(1.0f / mGeometry.getScaleFactor(), 1.0f / mGeometry.getScaleFactor()); - float angle = (mGeometry.getRotation() + mGeometry.getStraightenRotation()); - drawMatrix.postRotate(angle); - if (rotated) { - drawMatrix.postTranslate(dy, dx); - } else { - drawMatrix.postTranslate(dx, dy); + public Matrix buildMatrix(RectF r) { + float dx = r.width()/2; + float dy = r.height()/2; + if(mGeometry.hasSwitchedWidthHeight()){ + float temp = dx; + dx = dy; + dy = temp; } - return drawMatrix; + float w = r.left * 2 + r.width(); + float h = r.top * 2 + r.height(); + Matrix m = mGeometry.buildGeometryMatrix(w, h, 1f, dx, dy, false); + + return m; } @Override @@ -85,18 +83,18 @@ 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?) - + Rect cropBounds = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + RectF crop = mGeometry.getCropBounds(bitmap); + if(crop.width() > 0 && crop.height() > 0) + crop.roundOut(cropBounds); Bitmap temp = null; - float rotation = mGeometry.getRotation(); - boolean rotated = false; - if (rotation == 0 || rotation % 180 == 0) { - temp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), mConfig); + if (mGeometry.hasSwitchedWidthHeight()) { + temp = Bitmap.createBitmap(cropBounds.height(), cropBounds.width(), mConfig); } else { - temp = Bitmap.createBitmap(bitmap.getHeight(), bitmap.getWidth(), mConfig); - rotated = true; + temp = Bitmap.createBitmap(cropBounds.width(), cropBounds.height(), mConfig); } - Matrix drawMatrix = buildMatrix(bitmap, rotated); + Matrix drawMatrix = buildMatrix(crop); Canvas canvas = new Canvas(temp); canvas.drawBitmap(bitmap, drawMatrix, new Paint()); return temp; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java index 9d9c7e548..5e613ebce 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java @@ -18,7 +18,6 @@ public class ImageFilterWBalance extends ImageFilter { public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); - Log.v(TAG,"White Balance Call"); nativeApplyFilter(bitmap, w, h, -1,-1); return bitmap; } diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java index 0eb2e22aa..352fa5bf3 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java @@ -22,13 +22,6 @@ import android.graphics.RectF; import com.android.gallery3d.filtershow.filters.ImageFilterGeometry; -/** - * This class holds metadata about an image's geometry. Specifically: rotation, - * scaling, cropping, and image boundaries. It maintains the invariant that the - * cropping boundaries are within or equal to the image boundaries (before - * rotation) WHEN mSafe is true. - */ - public class GeometryMetadata { // Applied in order: rotate, crop, scale. // Do not scale saved image (presumably?). @@ -39,9 +32,6 @@ public class GeometryMetadata { private final RectF mCropBounds = new RectF(); private final RectF mPhotoBounds = new RectF(); private FLIP mFlip = FLIP.NONE; - private boolean mSafe = false; - - private Matrix mMatrix = new Matrix(); private RectF mBounds = new RectF(); @@ -56,13 +46,12 @@ public class GeometryMetadata { set(g); } - public Bitmap apply(Bitmap original, float scaleFactor, boolean highQuality){ + public Bitmap apply(Bitmap original, float scaleFactor, boolean highQuality) { mImageFilter.setGeometryMetadata(this); Bitmap m = mImageFilter.apply(original, scaleFactor, highQuality); return m; } - // Safe as long as invariant holds. public void set(GeometryMetadata g) { mScaleFactor = g.mScaleFactor; mRotation = g.mRotation; @@ -70,8 +59,6 @@ public class GeometryMetadata { mCropBounds.set(g.mCropBounds); mPhotoBounds.set(g.mPhotoBounds); mFlip = g.mFlip; - mSafe = g.mSafe; - mMatrix = g.mMatrix; mBounds = g.mBounds; } @@ -87,10 +74,19 @@ public class GeometryMetadata { return mStraightenRotation; } - public RectF getCropBounds() { + public RectF getPreviewCropBounds() { return new RectF(mCropBounds); } + public RectF getCropBounds(Bitmap bitmap) { + float scale = 1.0f; + if (mPhotoBounds.width() > 0) { + scale = bitmap.getWidth() / mPhotoBounds.width(); + } + return new RectF(mCropBounds.left * scale, mCropBounds.top * scale, + mCropBounds.right * scale, mCropBounds.bottom * scale); + } + public FLIP getFlipType() { return mFlip; } @@ -99,10 +95,6 @@ public class GeometryMetadata { return new RectF(mPhotoBounds); } - public boolean safe() { - return mSafe; - } - public void setScaleFactor(float scale) { mScaleFactor = scale; } @@ -119,41 +111,12 @@ public class GeometryMetadata { mStraightenRotation = straighten; } - /** - * Sets crop bounds to be the intersection of mPhotoBounds and the new crop - * bounds. If there was no intersection, returns false and does not set crop - * bounds - */ - public boolean safeSetCropBounds(RectF newCropBounds) { - if (mCropBounds.setIntersect(newCropBounds, mPhotoBounds)) { - mSafe = true; - return true; - } - return false; - } - public void setCropBounds(RectF newCropBounds) { mCropBounds.set(newCropBounds); - mSafe = false; - } - - /** - * Sets mPhotoBounds to be the new photo bounds and sets mCropBounds to be - * the intersection of the new photo bounds and the old crop bounds. Sets - * the crop bounds to mPhotoBounds if there is no intersection. - */ - - public void safeSetPhotoBounds(RectF newPhotoBounds) { - mPhotoBounds.set(newPhotoBounds); - if (!mCropBounds.intersect(mPhotoBounds)) { - mCropBounds.set(mPhotoBounds); - } - mSafe = true; } public void setPhotoBounds(RectF newPhotoBounds) { mPhotoBounds.set(newPhotoBounds); - mSafe = false; } public boolean cropFitsInPhoto(RectF cropBounds) { @@ -171,7 +134,7 @@ public class GeometryMetadata { return (mScaleFactor == d.mScaleFactor && mRotation == d.mRotation && mStraightenRotation == d.mStraightenRotation && - mFlip == d.mFlip && mSafe == d.mSafe && + mFlip == d.mFlip && mCropBounds.equals(d.mCropBounds) && mPhotoBounds.equals(d.mPhotoBounds)); } @@ -184,15 +147,13 @@ public class GeometryMetadata { result = 31 * result + mFlip.hashCode(); result = 31 * result + mCropBounds.hashCode(); result = 31 * result + mPhotoBounds.hashCode(); - result = 31 * result + (mSafe ? 1 : 0); return result; } @Override public String toString() { return getClass().getName() + "[" + "scale=" + mScaleFactor - + ",rotation=" + mRotation + ",flip=" + mFlip + ",safe=" - + (mSafe ? "true" : "false") + ",straighten=" + + ",rotation=" + mRotation + ",flip=" + mFlip + ",straighten=" + mStraightenRotation + ",cropRect=" + mCropBounds.toShortString() + ",photoRect=" + mPhotoBounds.toShortString() + "]"; } @@ -228,7 +189,34 @@ public class GeometryMetadata { } } - public Matrix getMatrix() { - return mMatrix; + public boolean hasSwitchedWidthHeight() { + return (((int) (mRotation / 90)) % 2) != 0; + } + + 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; + } + + 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); + } + + 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); } } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java index 90d36e942..4d171bf4c 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java @@ -22,12 +22,10 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; -import android.graphics.Path; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; - import com.android.gallery3d.R; public class ImageCrop extends ImageGeometry { @@ -40,19 +38,14 @@ public class ImageCrop extends ImageGeometry { private static final float MIN_CROP_WIDTH_HEIGHT = 0.1f; private static final int TOUCH_TOLERANCE = 30; - private static final int SHADOW_ALPHA = 160; - private final float mAspectWidth = 4; - private final float mAspectHeight = 3; - private final boolean mFixAspectRatio = false; // not working yet + private boolean mFirstDraw = true; + private float mAspectWidth = 1; + private float mAspectHeight = 1; + private boolean mFixAspectRatio = false; private final Paint borderPaint; - private float mCropOffsetX = 0; - private float mCropOffsetY = 0; - private float mPrevOffsetX = 0; - private float mPrevOffsetY = 0; - private int movingEdges; private final Drawable cropIndicator; private final int indicatorSize; @@ -86,22 +79,21 @@ public class ImageCrop extends ImageGeometry { } private float getScaledMinWidthHeight() { - RectF disp = getLocalDisplayBounds(); + RectF disp = new RectF(0, 0, getWidth(), getHeight()); float scaled = Math.min(disp.width(), disp.height()) * MIN_CROP_WIDTH_HEIGHT - / getLocalScale(); + / computeScale(getWidth(), getHeight()); return scaled; } - protected static Matrix getCropRotationMatrix(float rotation, RectF localImage) { - Matrix m = new Matrix(); - m.setRotate(rotation, localImage.centerX(), localImage.centerY()); + 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; } - @Override protected RectF getCropBoundsDisplayed() { RectF bounds = getLocalCropBounds(); RectF crop = new RectF(bounds); @@ -115,7 +107,7 @@ public class ImageCrop extends ImageGeometry { m.mapRect(crop); } m = new Matrix(); - float zoom = getLocalScale(); + float zoom = computeScale(getWidth(), getHeight()); m.setScale(zoom, zoom, mCenterX, mCenterY); m.preTranslate(mXOffset, mYOffset); m.mapRect(crop); @@ -137,6 +129,44 @@ public class ImageCrop extends ImageGeometry { return crop; } + 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 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; + } + /** * Sets cropped bounds; modifies the bounds if it's smaller than the allowed * dimensions. @@ -145,16 +175,33 @@ public class ImageCrop extends ImageGeometry { // Avoid cropping smaller than minimum width or height. 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; + } + } float newWidth = cbounds.width(); float newHeight = cbounds.height(); - if (newWidth < minWidthHeight) { - newWidth = minWidthHeight; - } - if (newHeight < minWidthHeight) { - newHeight = minWidthHeight; + 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; + } } - RectF pbounds = getLocalPhotoBounds(); if (pbounds.width() < minWidthHeight) { newWidth = pbounds.width(); @@ -164,18 +211,14 @@ public class ImageCrop extends ImageGeometry { } cbounds.set(cbounds.left, cbounds.top, cbounds.left + newWidth, cbounds.top + newHeight); - RectF snappedCrop = findCropBoundForRotatedImg(cbounds, pbounds, getLocalStraighten(), - mCenterX - mXOffset, mCenterY - mYOffset); - - RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten()); - snappedCrop.intersect(straightenBounds); - + RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), + getLocalStraighten()); + cbounds.intersect(straightenBounds); if (mFixAspectRatio) { - // TODO: add aspect ratio stuff - fixAspectRatio(snappedCrop, mAspectWidth, mAspectHeight); + fixAspectRatio(cbounds, aw, ah); } - setLocalCropBounds(snappedCrop); + setLocalCropBounds(cbounds); invalidate(); } @@ -202,33 +245,88 @@ public class ImageCrop extends ImageGeometry { else if (bottom <= TOUCH_TOLERANCE) { movingEdges |= MOVE_BOTTOM; } + // Check inside block. + if (cropped.contains(x, y) && (movingEdges == 0)) { + movingEdges = MOVE_BLOCK; + } invalidate(); } private void moveEdges(float dX, float dY) { RectF cropped = getRotatedCropBounds(); float minWidthHeight = getScaledMinWidthHeight(); - float scale = getLocalScale(); + float scale = computeScale(getWidth(), getHeight()); float deltaX = dX / scale; float deltaY = dY / scale; - if (movingEdges == MOVE_BLOCK) { - // TODO + int select = movingEdges; + if (mFixAspectRatio && (select != MOVE_BLOCK)) { + if ((select & MOVE_LEFT) != 0) { + select &= ~MOVE_BOTTOM; + select |= MOVE_TOP; + deltaY = getNewHeightForWidthAspect(deltaX, mAspectWidth, mAspectHeight); + } + if ((select & MOVE_TOP) != 0) { + select &= ~MOVE_RIGHT; + select |= MOVE_LEFT; + deltaX = getNewWidthForHeightAspect(deltaY, mAspectWidth, mAspectHeight); + } + if ((select & MOVE_RIGHT) != 0) { + select &= ~MOVE_TOP; + select |= MOVE_BOTTOM; + deltaY = getNewHeightForWidthAspect(deltaX, mAspectWidth, mAspectHeight); + } + if ((select & MOVE_BOTTOM) != 0) { + select &= ~MOVE_LEFT; + select |= MOVE_RIGHT; + deltaX = getNewWidthForHeightAspect(deltaY, mAspectWidth, mAspectHeight); + } + } + + 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); } else { - if ((movingEdges & MOVE_LEFT) != 0) { - cropped.left = Math.min(cropped.left + deltaX, cropped.right - minWidthHeight); - fixRectAspectW(cropped); + float dx = 0; + float dy = 0; + if ((select & MOVE_LEFT) != 0) { + dx = Math.min(cropped.left + deltaX, cropped.right - minWidthHeight) - cropped.left; + } + if ((select & MOVE_TOP) != 0) { + dy = Math.min(cropped.top + deltaY, cropped.bottom - minWidthHeight) - cropped.top; + } + if ((select & MOVE_RIGHT) != 0) { + dx = Math.max(cropped.right + deltaX, cropped.left + minWidthHeight) + - cropped.right; } - if ((movingEdges & MOVE_TOP) != 0) { - cropped.top = Math.min(cropped.top + deltaY, cropped.bottom - minWidthHeight); - fixRectAspectH(cropped); + if ((select & MOVE_BOTTOM) != 0) { + dy = Math.max(cropped.bottom + deltaY, cropped.top + minWidthHeight) + - cropped.bottom; } - if ((movingEdges & MOVE_RIGHT) != 0) { - cropped.right = Math.max(cropped.right + deltaX, cropped.left + minWidthHeight); - fixRectAspectW(cropped); + + if (mFixAspectRatio) { + if (dx < dy) { + dy = getNewHeightForWidthAspect(dx, mAspectWidth, mAspectHeight); + } else { + dx = getNewWidthForHeightAspect(dy, mAspectWidth, mAspectHeight); + } + } + + if ((select & MOVE_LEFT) != 0) { + cropped.left += dx; } - if ((movingEdges & MOVE_BOTTOM) != 0) { - cropped.bottom = Math.max(cropped.bottom + deltaY, cropped.top + minWidthHeight); - fixRectAspectH(cropped); + if ((select & MOVE_TOP) != 0) { + cropped.top += dy; + } + if ((select & MOVE_RIGHT) != 0) { + cropped.right += dx; + } + if ((select & MOVE_BOTTOM) != 0) { + cropped.bottom += dy; } } Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); @@ -244,24 +342,6 @@ public class ImageCrop extends ImageGeometry { setCropBounds(cropped); } - private void fixRectAspectH(RectF cropped) { - if (mFixAspectRatio) { - float half = getNewWidthForHeightAspect(cropped.height(), mAspectWidth, mAspectHeight) / 2; - float mid = (cropped.right - cropped.left) / 2; - cropped.left = mid - half; - cropped.right = mid + half; - } - } - - private void fixRectAspectW(RectF cropped) { - if (mFixAspectRatio) { - float half = getNewHeightForWidthAspect(cropped.width(), mAspectWidth, mAspectHeight) / 2; - float mid = (cropped.bottom - cropped.top) / 2; - cropped.top = mid - half; - cropped.bottom = mid + half; - } - } - private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) { int left = (int) centerX - indicatorSize / 2; int top = (int) centerY - indicatorSize / 2; @@ -273,81 +353,111 @@ public class ImageCrop extends ImageGeometry { protected void setActionDown(float x, float y) { super.setActionDown(x, y); detectMovingEdges(x, y); - if (movingEdges == 0) { - mPrevOffsetX = mCropOffsetX; - mPrevOffsetY = mCropOffsetY; - } + } + + @Override + protected void setActionUp() { + super.setActionUp(); + movingEdges = 0; } @Override protected void setActionMove(float x, float y) { - if (movingEdges != 0) { + if (movingEdges != 0) moveEdges(x - mCurrentX, y - mCurrentY); + + super.setActionMove(x, y); + } + + private void cropSetup() { + if (mFixAspectRatio) { + RectF cb = getRotatedCropBounds(); + fixAspectRatio(cb, mAspectWidth, mAspectHeight); + RectF cb0 = getUnrotatedCropBounds(cb); + setCropBounds(cb0); } else { - float dx = x - mTouchCenterX; - float dy = y - mTouchCenterY; - mCropOffsetX = dx + mPrevOffsetX; - mCropOffsetY = dy + mPrevOffsetY; + setCropBounds(getLocalCropBounds()); } - super.setActionMove(x, y); } @Override protected void gainedVisibility() { - setCropBounds(getLocalCropBounds()); - super.gainedVisibility(); + cropSetup(); + mFirstDraw = true; } - protected RectF drawCrop(Canvas canvas, Paint p, RectF cropBounds, float scale, - float rotation, float centerX, float centerY, float offsetX, float offsetY) { - RectF crop = new RectF(cropBounds); - Matrix m = new Matrix(); - m.preTranslate(offsetX, offsetY); - m.mapRect(crop); + @Override + public void resetParameter() { + super.resetParameter(); + cropSetup(); + } - m.setRotate(rotation, centerX, centerY); - if (!m.rectStaysRect()) { - float[] corners = getCornersFromRect(crop); - m.mapPoints(corners); - drawClosedPath(canvas, p, corners); - } else { - RectF crop2 = new RectF(crop); - m.mapRect(crop2); - Path path = new Path(); - path.addRect(crop2, Path.Direction.CCW); - canvas.drawPath(path, p); - } - return crop; + @Override + protected void lostVisibility() { } @Override protected void drawShape(Canvas canvas, Bitmap image) { + // TODO: move style to xml gPaint.setAntiAlias(true); gPaint.setFilterBitmap(true); gPaint.setDither(true); gPaint.setARGB(255, 255, 255, 255); - drawTransformedBitmap(canvas, image, gPaint, false); - float scale = getLocalScale(); + if (mFirstDraw) { + cropSetup(); + mFirstDraw = false; + } float rotation = getLocalRotation(); - - RectF scaledCrop = drawCrop(canvas, gPaint, getLocalCropBounds(), scale, - rotation, mCenterX, mCenterY, mXOffset, - mYOffset); - - boolean notMoving = movingEdges == 0; - if (((movingEdges & MOVE_TOP) != 0) || notMoving) { + drawTransformedBitmap(canvas, image, gPaint, true); + + gPaint.setARGB(255, 125, 255, 128); + gPaint.setStrokeWidth(3); + gPaint.setStyle(Paint.Style.STROKE); + drawStraighten(canvas, gPaint); + RectF scaledCrop = unrotatedCropBounds(); + int decoded_moving = decoder(movingEdges, rotation); + canvas.save(); + canvas.rotate(rotation, mCenterX, mCenterY); + boolean notMoving = decoded_moving == 0; + if (((decoded_moving & MOVE_TOP) != 0) || notMoving) { drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); } - if (((movingEdges & MOVE_BOTTOM) != 0) || notMoving) { + if (((decoded_moving & MOVE_BOTTOM) != 0) || notMoving) { drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); } - if (((movingEdges & MOVE_LEFT) != 0) || notMoving) { + if (((decoded_moving & MOVE_LEFT) != 0) || notMoving) { drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); } - if (((movingEdges & MOVE_RIGHT) != 0) || notMoving) { + if (((decoded_moving & MOVE_RIGHT) != 0) || notMoving) { drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); } + 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; + } + } +}
\ No newline at end of file diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java index 98f892e8c..68df702ea 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java @@ -26,7 +26,6 @@ import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.RectF; import android.util.AttributeSet; -import android.util.Log; import android.view.MotionEvent; import android.view.View; @@ -48,10 +47,8 @@ public abstract class ImageGeometry extends ImageSlave { protected float mTouchCenterX; protected float mTouchCenterY; - private Matrix mLocalMatrix = null; - // Local geometry data - private GeometryMetadata mLocalGeoMetadata = null; + private GeometryMetadata mLocalGeometry = null; private RectF mLocalDisplayBounds = null; protected float mXOffset = 0; protected float mYOffset = 0; @@ -77,9 +74,9 @@ public abstract class ImageGeometry extends ImageSlave { calculateLocalScalingFactorAndOffset(); } - private float computeScale(float width, float height) { - float imageWidth = mLocalGeoMetadata.getPhotoBounds().width(); - float imageHeight = mLocalGeoMetadata.getPhotoBounds().height(); + protected float computeScale(float width, float height) { + float imageWidth = mLocalGeometry.getPhotoBounds().width(); + float imageHeight = mLocalGeometry.getPhotoBounds().height(); float zoom = width / imageWidth; if (imageHeight > imageWidth) { zoom = height / imageHeight; @@ -88,9 +85,9 @@ public abstract class ImageGeometry extends ImageSlave { } private void calculateLocalScalingFactorAndOffset() { - if (mLocalGeoMetadata == null || mLocalDisplayBounds == null) + if (mLocalGeometry == null || mLocalDisplayBounds == null) return; - RectF imageBounds = mLocalGeoMetadata.getPhotoBounds(); + RectF imageBounds = mLocalGeometry.getPhotoBounds(); float imageWidth = imageBounds.width(); float imageHeight = imageBounds.height(); float displayWidth = mLocalDisplayBounds.width(); @@ -100,9 +97,7 @@ public abstract class ImageGeometry extends ImageSlave { mCenterY = displayHeight / 2; mYOffset = (displayHeight - imageHeight) / 2.0f; mXOffset = (displayWidth - imageWidth) / 2.0f; - - float zoom = computeScale(mLocalDisplayBounds.width(), mLocalDisplayBounds.height()); - mLocalGeoMetadata.setScaleFactor(zoom); + updateScale(); } @Override @@ -118,21 +113,16 @@ public abstract class ImageGeometry extends ImageSlave { // Overwrites local with master protected void syncLocalToMasterGeometry() { - mLocalGeoMetadata = getMaster().getGeometry(); + mLocalGeometry = getMaster().getGeometry(); calculateLocalScalingFactorAndOffset(); - mLocalMatrix = mLocalGeoMetadata.getMatrix(); - } - - public Matrix getLocalMatrix() { - return mLocalMatrix; } protected RectF getLocalPhotoBounds() { - return mLocalGeoMetadata.getPhotoBounds(); + return mLocalGeometry.getPhotoBounds(); } protected RectF getLocalCropBounds() { - return mLocalGeoMetadata.getCropBounds(); + return mLocalGeometry.getPreviewCropBounds(); } protected RectF getLocalDisplayBounds() { @@ -140,59 +130,62 @@ public abstract class ImageGeometry extends ImageSlave { } protected float getLocalScale() { - return mLocalGeoMetadata.getScaleFactor(); + return mLocalGeometry.getScaleFactor(); } protected float getLocalRotation() { - return mLocalGeoMetadata.getRotation(); + return mLocalGeometry.getRotation(); } protected float getLocalStraighten() { - return mLocalGeoMetadata.getStraightenRotation(); + return mLocalGeometry.getStraightenRotation(); } protected void setLocalScale(float s) { - mLocalGeoMetadata.setScaleFactor(s); + mLocalGeometry.setScaleFactor(s); } - protected void updateMatrix() { - RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeoMetadata.getPhotoBounds(), + protected void updateScale() { + RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeometry.getPhotoBounds(), getLocalStraighten()); float zoom = computeScale(bounds.width(), bounds.height()); setLocalScale(zoom); - float w = mLocalGeoMetadata.getPhotoBounds().width(); - float h = mLocalGeoMetadata.getPhotoBounds().height(); - float ratio = h / w; - float rcenterx = 0.5f; - float rcentery = 0.5f * ratio; - Matrix flipper = mLocalGeoMetadata.getFlipMatrix(1.0f, ratio); - mLocalMatrix.reset(); - mLocalMatrix.postConcat(flipper); - mLocalMatrix.postRotate(getTotalLocalRotation(), rcenterx, rcentery); - invalidate(); } protected void setLocalRotation(float r) { - mLocalGeoMetadata.setRotation(r); - updateMatrix(); + mLocalGeometry.setRotation(r); + updateScale(); + } + + /** + * Constrains rotation to be in [0, 90, 180, 270]. + */ + protected int constrainedRotation(float rotation) { + int r = (int) ((rotation % 360) / 90); + r = (r < 0) ? (r + 4) : r; + return r * 90; + } + + protected Matrix getLocalGeoFlipMatrix(float width, float height) { + return mLocalGeometry.getFlipMatrix(width, height); } protected void setLocalStraighten(float r) { - mLocalGeoMetadata.setStraightenRotation(r); - updateMatrix(); + mLocalGeometry.setStraightenRotation(r); + updateScale(); } protected void setLocalCropBounds(RectF c) { - mLocalGeoMetadata.setCropBounds(c); + mLocalGeometry.setCropBounds(c); + updateScale(); } protected FLIP getLocalFlip() { - return mLocalGeoMetadata.getFlipType(); + return mLocalGeometry.getFlipType(); } protected void setLocalFlip(FLIP flip) { - mLocalGeoMetadata.setFlipType(flip); - updateMatrix(); + mLocalGeometry.setFlipType(flip); } protected float getTotalLocalRotation() { @@ -208,38 +201,18 @@ public abstract class ImageGeometry extends ImageSlave { protected static float[] getCornersFromRect(RectF r) { // Order is: // 0------->1 - // ^ | - // | v + // ^ | + // | 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 + r.left, r.bottom // 3 }; return corners; } - // Returns maximal rectangular crop bound that still fits within - // the image bound after the image has been rotated. - protected static RectF findCropBoundForRotatedImg(RectF cropBound, - RectF imageBound, - float rotation, - float centerX, - float centerY) { - Matrix m = new Matrix(); - float[] cropEdges = getCornersFromRect(cropBound); - m.setRotate(rotation, centerX, centerY); - Matrix m0 = new Matrix(); - if (!m.invert(m0)) - return null; - m0.mapPoints(cropEdges); - getEdgePoints(imageBound, cropEdges); - m.mapPoints(cropEdges); - return trapToRect(cropEdges); - } - // 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) { @@ -251,24 +224,6 @@ public abstract class ImageGeometry extends ImageSlave { } } - protected static RectF trapToRect(float[] array) { - float dx0 = array[4] - array[0]; - float dy0 = array[5] - array[1]; - float dx1 = array[6] - array[2]; - float dy1 = array[7] - array[3]; - float l0 = dx0 * dx0 + dy0 * dy0; - float l1 = dx1 * dx1 + dy1 * dy1; - if (l0 > l1) { - RectF n = new RectF(array[2], array[3], array[6], array[7]); - n.sort(); - return n; - } else { - RectF n = new RectF(array[0], array[1], array[4], array[5]); - n.sort(); - return n; - } - } - protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) { Path crop = new Path(); crop.moveTo(points[0], points[1]); @@ -280,26 +235,14 @@ public abstract class ImageGeometry extends ImageSlave { return crop; } - 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]; - float xdelt = x2 - x1; - float ydelt = y2 - y1; - if (xdelt == 0 && ydelt == 0) - return null; - float u = ((point[0] - x1) * xdelt + (point[1] - y1) * ydelt) - / (xdelt * xdelt + ydelt * ydelt); - float[] ret = { - (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1)) - }; - return ret; - } - protected static void fixAspectRatio(RectF r, float w, float h) { float scale = Math.min(r.width() / w, r.height() / h); - r.set(r.left, r.top, scale * w, scale * 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) { @@ -310,22 +253,17 @@ public abstract class ImageGeometry extends ImageSlave { return height * w / h; } - protected void logMasterGeo() { - Log.v(LOGTAG, getMaster().getGeometry().toString()); - } - @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (visibility == View.VISIBLE) { mVisibilityGained = true; syncLocalToMasterGeometry(); + updateScale(); gainedVisibility(); - logMasterGeo(); } else { if (mVisibilityGained == true && mHasDrawn == true) { lostVisibility(); - logMasterGeo(); } mVisibilityGained = false; mHasDrawn = false; @@ -334,7 +272,6 @@ public abstract class ImageGeometry extends ImageSlave { protected void gainedVisibility() { // TODO: Override this stub. - updateMatrix(); } protected void lostVisibility() { @@ -356,8 +293,6 @@ public abstract class ImageGeometry extends ImageSlave { case (MotionEvent.ACTION_UP): setActionUp(); saveAndSetPreset(); - Log.v(LOGTAG, "up action"); - logMasterGeo(); break; case (MotionEvent.ACTION_MOVE): setActionMove(event.getX(), event.getY()); @@ -405,13 +340,12 @@ public abstract class ImageGeometry extends ImageSlave { protected void saveAndSetPreset() { ImagePreset copy = new ImagePreset(getImagePreset()); - copy.setGeometry(mLocalGeoMetadata); + copy.setGeometry(mLocalGeometry); copy.setHistoryName("Geometry"); copy.setIsFx(false); setImagePreset(copy); } - // protected static float clamp(float i, float low, float high) { return Math.max(Math.min(i, high), low); } @@ -440,100 +374,123 @@ public abstract class ImageGeometry extends ImageSlave { return new RectF(left, top, right, bottom); } - protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds, - float rotation, float centerX, float centerY) { + protected Matrix getGeoMatrix(RectF r, boolean onlyRotate) { + float scale = computeScale(getWidth(), getHeight()); + 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.rotate(rotation, centerX, centerY); - float dWidth = outerBounds.width(); - float dHeight = outerBounds.height(); - canvas.drawRect(0, 0, dWidth, innerBounds.top, p); - canvas.drawRect(0, innerBounds.bottom, dWidth, dHeight, p); - canvas.drawRect(0, innerBounds.top, innerBounds.left, innerBounds.bottom, - p); - canvas.drawRect(innerBounds.right, innerBounds.top, dWidth, - innerBounds.bottom, p); - canvas.rotate(-rotation, centerX, centerY); + canvas.drawBitmap(bitmap, m, paint); canvas.restore(); } - public Matrix computeBoundsMatrix(Bitmap bitmap) { - Matrix boundsMatrix = new Matrix(); - boundsMatrix.setTranslate((getWidth() - bitmap.getWidth()) / 2.0f, - (getHeight() - bitmap.getHeight()) / 2.0f); - boundsMatrix.postRotate(getLocalRotation(), getWidth() / 2.0f, getHeight() / 2.0f); - return boundsMatrix; + 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); } - public RectF cropBounds(Bitmap bitmap) { - Matrix boundsMatrix = computeBoundsMatrix(bitmap); + protected RectF straightenBounds() { RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten()); - RectF transformedBounds = new RectF(bounds); - boundsMatrix.mapRect(transformedBounds); - return transformedBounds; + Matrix m = getGeoMatrix(bounds, true); + m.mapRect(bounds); + return bounds; } - protected void drawTransformedBitmap(Canvas canvas, Bitmap bitmap, Paint paint, boolean clip) { - Matrix boundsMatrix = computeBoundsMatrix(bitmap); - RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), - getLocalStraighten()); - RectF transformedBounds = new RectF(bounds); - boundsMatrix.mapRect(transformedBounds); - + protected void drawStraighten(Canvas canvas, Paint paint) { + RectF bounds = straightenBounds(); canvas.save(); - Matrix matrix = getLocalMatrix(); - canvas.translate((getWidth() - bitmap.getWidth()) / 2.0f, - (getHeight() - bitmap.getHeight()) / 2.0f); - paint.setARGB(255, 0, 0, 0); - Matrix drawMatrix = new Matrix(); - float w = bitmap.getWidth(); - drawMatrix.preScale(1.0f/w, 1.0f/w); - drawMatrix.postConcat(matrix); - drawMatrix.postScale(w, w); - canvas.drawBitmap(bitmap, drawMatrix, paint); + 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); + return bounds; + } + + // Fails for non-90 degree + protected void drawCrop(Canvas canvas, Paint paint) { + RectF bounds = cropBounds(); canvas.save(); - canvas.setMatrix(boundsMatrix); - paint.setColor(Color.WHITE); - paint.setStyle(Style.STROKE); - paint.setStrokeWidth(2); canvas.drawRect(bounds, paint); canvas.restore(); + } - if (!clip) { // we display the rest of the bitmap grayed-out - drawShadows(canvas, transformedBounds, new RectF(0, 0, getWidth(), getHeight()), paint); + 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 RectF getCropBoundsDisplayed() { - return getCropBoundsDisplayed(getLocalCropBounds()); + 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.setARGB(128, 0, 0, 0); + paint.setStyle(Paint.Style.FILL); + drawShadows(canvas, paint, unrotatedCropBounds()); } - protected RectF getCropBoundsDisplayed(RectF bounds) { - RectF crop = new RectF(bounds); - Matrix m = new Matrix(); - float zoom = getLocalScale(); - m.setScale(zoom, zoom, mCenterX, mCenterY); - m.preTranslate(mXOffset, mYOffset); - m.mapRect(crop); - return crop; + 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 void drawShadows(Canvas canvas, RectF innerBounds, RectF outerBounds, Paint p) { - float dWidth = outerBounds.width(); - float dHeight = outerBounds.height(); - - // TODO: move style to xml - p.setARGB(128, 0, 0, 0); - p.setStyle(Paint.Style.FILL); + protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds, + float rotation, float centerX, float centerY) { + canvas.save(); + canvas.rotate(rotation, centerX, centerY); - canvas.drawRect(0, 0, dWidth, innerBounds.top, p); - canvas.drawRect(0, innerBounds.bottom, dWidth, dHeight, p); - canvas.drawRect(0, innerBounds.top, innerBounds.left, innerBounds.bottom, + float x = (outerBounds.left - outerBounds.right); + float y = (outerBounds.top - outerBounds.bottom); + float longest = (float) Math.sqrt(x * x + y * y) / 2; + float minX = centerX - longest; + float maxX = centerX + longest; + float minY = centerY - longest; + float maxY = centerY + longest; + canvas.drawRect(minX, minY, innerBounds.right, innerBounds.top, p); + canvas.drawRect(minX, innerBounds.top, innerBounds.left, maxY, p); + canvas.drawRect(innerBounds.left, innerBounds.bottom, maxX, maxY, p); - canvas.drawRect(innerBounds.right, innerBounds.top, dWidth, + canvas.drawRect(innerBounds.right, minY, maxX, innerBounds.bottom, p); + canvas.rotate(-rotation, centerX, centerY); + canvas.restore(); } @Override diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index f4a218472..3fd6d4f85 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java @@ -88,6 +88,14 @@ public class ImageShow extends View implements SliderListener, OnSeekBarChangeLi private SeekBar mSeekBar = null; private PanelController mController = null; + public static void setTextSize(int value) { + mTextSize = value; + } + + public static void setTextPadding(int value) { + mTextPadding = value; + } + private final Handler mHandler = new Handler(); public void select() { @@ -278,7 +286,7 @@ public class ImageShow extends View implements SliderListener, OnSeekBarChangeLi canvas.drawRect(textRect, mPaint); mPaint.setARGB(255, 200, 200, 200); canvas.drawText(getImagePreset().name(), mTextPadding, - 10 + mTextPadding, mPaint); + 1.5f * mTextPadding, mPaint); } if (showControls()) { diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallBorder.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallBorder.java index d0c67f783..90986f912 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallBorder.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallBorder.java @@ -18,7 +18,7 @@ public class ImageSmallBorder extends ImageSmallFilter { protected final int mSelectedBackgroundColor = Color.WHITE; protected final int mInnerBorderColor = Color.BLACK; protected final int mInnerBorderWidth = 2; - protected final float mImageScaleFactor = 2.5f; + protected final float mImageScaleFactor = 3.5f; public ImageSmallBorder(Context context) { super(context); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java index a358e0c9b..a5d99a098 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java @@ -27,12 +27,20 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener private ImageSmallFilter mPreviousImageSmallFilter = null; // TODO: move this to xml. - protected final int mMargin = 12; - protected final int mTextMargin = 8; + protected static int mMargin = 12; + protected static int mTextMargin = 8; protected final int mBackgroundColor = Color.argb(255, 30, 32, 40); protected final int mSelectedBackgroundColor = Color.WHITE; protected final int mTextColor = Color.WHITE; + public static void setMargin(int value) { + mMargin = value; + } + + public static void setTextMargin(int value) { + mTextMargin = value; + } + public ImageSmallFilter(Context context, AttributeSet attrs) { super(context, attrs); setOnClickListener(this); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java index a94d6292f..2fd6b9b35 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java @@ -24,6 +24,8 @@ import android.graphics.Path; import android.graphics.RectF; import android.util.AttributeSet; +import com.android.gallery3d.filtershow.imageshow.ImageGeometry.MODES; + public class ImageStraighten extends ImageGeometry { private float mBaseAngle = 0; @@ -46,11 +48,17 @@ public class ImageStraighten extends ImageGeometry { mBaseAngle = mAngle = getLocalStraighten(); } + private void setCropToStraighten(){ + setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), + getLocalStraighten())); + } + @Override protected void setActionMove(float x, float y) { super.setActionMove(x, y); computeValue(); setLocalStraighten(mAngle); + setCropToStraighten(); } private float angleFor(float dx, float dy) { @@ -80,6 +88,17 @@ public class ImageStraighten extends ImageGeometry { } @Override + protected void gainedVisibility(){ + setCropToStraighten(); + } + + @Override + protected void setActionUp() { + super.setActionUp(); + setCropToStraighten(); + } + + @Override public void onNewValue(int value) { setLocalStraighten(clamp(value, MIN_STRAIGHTEN_ANGLE, MAX_STRAIGHTEN_ANGLE)); if (getPanelController() != null) { @@ -98,7 +117,7 @@ public class ImageStraighten extends ImageGeometry { drawTransformedBitmap(canvas, image, gPaint, false); // Draw the grid - RectF bounds = cropBounds(image); + 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/ui/ControlPoint.java b/src/com/android/gallery3d/filtershow/ui/ControlPoint.java index 68b799a9e..0c08e76fd 100644 --- a/src/com/android/gallery3d/filtershow/ui/ControlPoint.java +++ b/src/com/android/gallery3d/filtershow/ui/ControlPoint.java @@ -2,26 +2,19 @@ package com.android.gallery3d.filtershow.ui; public class ControlPoint implements Comparable { + public float x; + public float y; + public ControlPoint(float px, float py) { x = px; y = py; } - public ControlPoint multiply(float m) { - return new ControlPoint(x * m, y * m); - } - - public ControlPoint add(ControlPoint v) { - return new ControlPoint(x + v.x, y + v.y); + public ControlPoint(ControlPoint point) { + x = point.x; + y = point.y; } - public ControlPoint sub(ControlPoint v) { - return new ControlPoint(x - v.x, y - v.y); - } - - public float x; - public float y; - public ControlPoint copy() { return new ControlPoint(x, y); } diff --git a/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java b/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java index 7f0b0437d..51ed7fb20 100644 --- a/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java +++ b/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java @@ -1,16 +1,15 @@ package com.android.gallery3d.filtershow.ui; -import com.android.gallery3d.R; - import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; -import android.util.Log; import android.widget.ImageButton; +import com.android.gallery3d.R; + public class ImageButtonTitle extends ImageButton { private static final String LOGTAG = "ImageButtonTitle"; private String mText = null; @@ -18,6 +17,14 @@ public class ImageButtonTitle extends ImageButton { private static int mTextPadding = 20; private static Paint gPaint = new Paint(); + public static void setTextSize(int value) { + mTextSize = value; + } + + public static void setTextPadding(int value) { + mTextPadding = value; + } + public ImageButtonTitle(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = getContext().obtainStyledAttributes( @@ -26,6 +33,7 @@ public class ImageButtonTitle extends ImageButton { mText = a.getString(R.styleable.ImageButtonTitle_android_text); } + @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); if (mText != null) { diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java index 660a4fadd..a8445b830 100644 --- a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java +++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java @@ -2,17 +2,17 @@ package com.android.gallery3d.filtershow.ui; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.os.AsyncTask; import android.util.AttributeSet; -import android.view.MenuItem; import android.view.MotionEvent; -import android.view.View; -import android.widget.PopupMenu; -import android.widget.Toast; -import com.android.gallery3d.R; import com.android.gallery3d.filtershow.filters.ImageFilterCurves; import com.android.gallery3d.filtershow.imageshow.ImageSlave; import com.android.gallery3d.filtershow.presets.ImagePreset; @@ -21,15 +21,18 @@ public class ImageCurves extends ImageSlave { private static final String LOGTAG = "ImageCurves"; Paint gPaint = new Paint(); - Spline mSpline = null; + Spline[] mSplines = new Spline[4]; Path gPathSpline = new Path(); - float[] mAppliedCurve = new float[256]; + + private int mCurrentCurveIndex = 0; private boolean mDidAddPoint = false; private boolean mDidDelete = false; private ControlPoint mCurrentControlPoint = null; - private boolean mUseRed = true; - private boolean mUseGreen = true; - private boolean mUseBlue = true; + private ImagePreset mLastPreset = null; + int[] redHistogram = new int[256]; + int[] greenHistogram = new int[256]; + int[] blueHistogram = new int[256]; + Path gHistoPath = new Path(); public ImageCurves(Context context) { super(context); @@ -41,23 +44,16 @@ public class ImageCurves extends ImageSlave { resetCurve(); } + public void nextChannel() { + mCurrentCurveIndex = ((mCurrentCurveIndex + 1) % 4); + invalidate(); + } + @Override public boolean showTitle() { return false; } - public void setUseRed(boolean value) { - mUseRed = value; - } - - public void setUseGreen(boolean value) { - mUseGreen = value; - } - - public void setUseBlue(boolean value) { - mUseBlue = value; - } - public void reloadCurve() { if (getMaster() != null) { String filterName = getFilterName(); @@ -67,7 +63,12 @@ public class ImageCurves extends ImageSlave { resetCurve(); return; } - mSpline = new Spline(filter.getSpline()); + for (int i = 0; i < 4; i++) { + Spline spline = filter.getSpline(i); + if (spline != null) { + mSplines[i] = new Spline(spline); + } + } applyNewCurve(); } } @@ -76,13 +77,19 @@ public class ImageCurves extends ImageSlave { public void resetParameter() { super.resetParameter(); resetCurve(); + mLastPreset = null; + invalidate(); } public void resetCurve() { - mSpline = new Spline(); + Spline spline = new Spline(); - mSpline.addPoint(0.0f, 1.0f); - mSpline.addPoint(1.0f, 0.0f); + spline.addPoint(0.0f, 1.0f); + spline.addPoint(1.0f, 0.0f); + + for (int i = 0; i < 4; i++) { + mSplines[i] = new Spline(spline); + } if (getMaster() != null) { applyNewCurve(); } @@ -93,86 +100,48 @@ public class ImageCurves extends ImageSlave { super.onDraw(canvas); gPaint.setAntiAlias(true); - gPaint.setFilterBitmap(true); - gPaint.setDither(true); - - drawGrid(canvas); - drawSpline(canvas); - - drawToast(canvas); - } - - private void drawGrid(Canvas canvas) { - float w = getWidth(); - float h = getHeight(); - // Grid - gPaint.setARGB(128, 150, 150, 150); - gPaint.setStrokeWidth(1); - - float stepH = h / 9; - float stepW = w / 9; - - // central diagonal - gPaint.setARGB(255, 100, 100, 100); - gPaint.setStrokeWidth(2); - canvas.drawLine(0, h, w, 0, gPaint); - - gPaint.setARGB(128, 200, 200, 200); - gPaint.setStrokeWidth(4); - stepH = h / 3; - stepW = w / 3; - for (int j = 1; j < 3; j++) { - canvas.drawLine(0, j * stepH, w, j * stepH, gPaint); - canvas.drawLine(j * stepW, 0, j * stepW, h, gPaint); + if (getImagePreset() != mLastPreset) { + new ComputeHistogramTask().execute(mFilteredImage); + mLastPreset = getImagePreset(); } - } - - private void drawSpline(Canvas canvas) { - float w = getWidth(); - float h = getHeight(); - gPathSpline.reset(); - for (int x = 0; x < w; x += 11) { - float fx = x / w; - ControlPoint drawPoint = mSpline.getPoint(fx); - float newX = drawPoint.x * w; - float newY = drawPoint.y * h; - if (x == 0) { - gPathSpline.moveTo(newX, newY); - } else { - gPathSpline.lineTo(newX, newY); + if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.RED) { + drawHistogram(canvas, redHistogram, Color.RED, PorterDuff.Mode.SCREEN); + } + if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.GREEN) { + drawHistogram(canvas, greenHistogram, Color.GREEN, PorterDuff.Mode.SCREEN); + } + if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.BLUE) { + drawHistogram(canvas, blueHistogram, Color.BLUE, PorterDuff.Mode.SCREEN); + } + // We only display the other channels curves when showing the RGB curve + if (mCurrentCurveIndex == Spline.RGB) { + for (int i = 0; i < 4; i++) { + Spline spline = mSplines[i]; + if (i != mCurrentCurveIndex && !spline.isOriginal()) { + // And we only display a curve if it has more than two + // points + spline.draw(canvas, Spline.colorForCurve(i), getWidth(), getHeight(), false); + } } } + // ...but we always display the current curve. + mSplines[mCurrentCurveIndex] + .draw(canvas, Spline.colorForCurve(mCurrentCurveIndex), getWidth(), getHeight(), + true); + drawToast(canvas); - gPaint.setStrokeWidth(10); - gPaint.setStyle(Paint.Style.STROKE); - gPaint.setARGB(255, 50, 50, 50); - canvas.drawPath(gPathSpline, gPaint); - gPaint.setStrokeWidth(5); - gPaint.setARGB(255, 150, 150, 150); - canvas.drawPath(gPathSpline, gPaint); - - gPaint.setARGB(255, 150, 150, 150); - for (int j = 1; j < mSpline.getNbPoints() - 1; j++) { - ControlPoint point = mSpline.getPoint(j); - gPaint.setStrokeWidth(10); - gPaint.setARGB(255, 50, 50, 100); - canvas.drawCircle(point.x * w, point.y * h, 30, gPaint); - gPaint.setStrokeWidth(5); - gPaint.setARGB(255, 150, 150, 200); - canvas.drawCircle(point.x * w, point.y * h, 30, gPaint); - } } private int pickControlPoint(float x, float y) { int pick = 0; - float px = mSpline.getPoint(0).x; - float py = mSpline.getPoint(0).y; + float px = mSplines[mCurrentCurveIndex].getPoint(0).x; + float py = mSplines[mCurrentCurveIndex].getPoint(0).y; double delta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y)); - for (int i = 1; i < mSpline.getNbPoints(); i++) { - px = mSpline.getPoint(i).x; - py = mSpline.getPoint(i).y; + for (int i = 1; i < mSplines[mCurrentCurveIndex].getNbPoints(); i++) { + px = mSplines[mCurrentCurveIndex].getPoint(i).x; + py = mSplines[mCurrentCurveIndex].getPoint(i).y; double currentDelta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y)); if (currentDelta < delta) { @@ -182,77 +151,35 @@ public class ImageCurves extends ImageSlave { } if (!mDidAddPoint && (delta * getWidth() > 100) - && (mSpline.getNbPoints() < 10)) { + && (mSplines[mCurrentCurveIndex].getNbPoints() < 10)) { return -1; } - return pick;// mSpline.getPoint(pick); - } - - public void showPopupMenu(View v) { - // TODO: sort out the popup menu UI for curves - final Context context = v.getContext(); - PopupMenu popupMenu = new PopupMenu(v.getContext(), v); - popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves, - popupMenu.getMenu()); - - popupMenu - .setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - - @Override - public boolean onMenuItemClick(MenuItem item) { - Toast.makeText(context, item.toString(), - Toast.LENGTH_LONG).show(); - return true; - } - }); - - popupMenu.show(); + return pick; } private String getFilterName() { - String filterName = "Curves"; - if (mUseRed && !mUseGreen && !mUseBlue) { - filterName = "CurvesRed"; - } else if (!mUseRed && mUseGreen && !mUseBlue) { - filterName = "CurvesGreen"; - } else if (!mUseRed && !mUseGreen && mUseBlue) { - filterName = "CurvesBlue"; - } - return filterName; + return "Curves"; } @Override public synchronized boolean onTouchEvent(MotionEvent e) { float posX = e.getX() / getWidth(); - float posY = e.getY() / getHeight(); - - /* - * if (true) { showPopupMenu(this); return true; } - */ - - // ControlPoint point = null; - - // Log.v(LOGTAG, "onTouchEvent - " + e + " action masked : " + - // e.getActionMasked()); + float posY = e.getY(); + float margin = Spline.curveHandleSize() / 2; + if (posY < margin) { + posY = margin; + } + if (posY > getHeight() - margin) { + posY = getHeight() - margin; + } + posY = (posY - margin) / (getHeight() - 2 * margin); if (e.getActionMasked() == MotionEvent.ACTION_UP) { applyNewCurve(); - // Log.v(LOGTAG, "ACTION UP, mCurrentControlPoint set to null!"); mCurrentControlPoint = null; - String name = null; - if (mUseRed && mUseGreen && mUseBlue) { - name = "Curves (RGB)"; - } else if (mUseRed) { - name = "Curves (Red)"; - } else if (mUseGreen) { - name = "Curves (Green)"; - } else if (mUseBlue) { - name = "Curves (Blue)"; - } - - - ImagePreset copy = new ImagePreset(getImagePreset(),name); + String name = "Curves"; + ImagePreset copy = new ImagePreset(getImagePreset(), name); copy.setIsFx(false); mImageLoader.getHistory().insert(copy, 0); @@ -268,103 +195,31 @@ public class ImageCurves extends ImageSlave { if (mDidDelete) { return true; } - // Log.v(LOGTAG, "ACTION DOWN, mCurrentControlPoint is " + - // mCurrentControlPoint); int pick = pickControlPoint(posX, posY); - // Log.v(LOGTAG, "ACTION DOWN, pick is " + pick); if (mCurrentControlPoint == null) { if (pick == -1) { mCurrentControlPoint = new ControlPoint(posX, posY); - mSpline.addPoint(mCurrentControlPoint); + mSplines[mCurrentCurveIndex].addPoint(mCurrentControlPoint); mDidAddPoint = true; - // Log.v(LOGTAG, "ACTION DOWN - 2, added a new control point! " - // + mCurrentControlPoint); - } else { - mCurrentControlPoint = mSpline.getPoint(pick); - // Log.v(LOGTAG, "ACTION DOWN - 2, picking up control point " + - // mCurrentControlPoint + " at pick " + pick); + mCurrentControlPoint = mSplines[mCurrentCurveIndex].getPoint(pick); } } - // Log.v(LOGTAG, "ACTION DOWN - 3, pick is " + pick); - - if (!((mCurrentControlPoint.x == 0 && mCurrentControlPoint.y == 1) || (mCurrentControlPoint.x == 1 && mCurrentControlPoint.y == 0))) { - if (mSpline.isPointContained(posX, pick)) { - mCurrentControlPoint.x = posX; - mCurrentControlPoint.y = posY; - // Log.v(LOGTAG, "ACTION DOWN - 4, move control point " + - // mCurrentControlPoint); - } else if (pick != -1) { - // Log.v(LOGTAG, "ACTION DOWN - 4, delete pick " + pick); - mSpline.deletePoint(pick); - mDidDelete = true; - } + + if (mSplines[mCurrentCurveIndex].isPointContained(posX, pick)) { + mCurrentControlPoint.x = posX; + mCurrentControlPoint.y = posY; + } else if (pick != -1) { + mSplines[mCurrentCurveIndex].deletePoint(pick); + mDidDelete = true; } - // Log.v(LOGTAG, "ACTION DOWN - 5, DONE"); applyNewCurve(); invalidate(); return true; } public synchronized void applyNewCurve() { - ControlPoint[] points = new ControlPoint[256]; - for (int i = 0; i < 256; i++) { - float v = i / 255.0f; - ControlPoint p = mSpline.getPoint(v); - points[i] = p; - } - for (int i = 0; i < 256; i++) { - mAppliedCurve[i] = -1; - } - for (int i = 0; i < 256; i++) { - int index = (int) (points[i].x * 255); - if (index >= 0 && index <= 255) { - float v = 1.0f - points[i].y; - if (v < 0) { - v = 0; - } - if (v > 1.0f) { - v = 1.0f; - } - mAppliedCurve[index] = v; - } - } - float prev = 0; - for (int i = 0; i < 256; i++) { - if (mAppliedCurve[i] == -1) { - // need to interpolate... - int j = i + 1; - if (j > 255) { - j = 255; - } - for (; j < 256; j++) { - if (mAppliedCurve[j] != -1) { - break; - } - } - if (j > 255) { - j = 255; - } - // interpolate linearly between i and j - 1 - float start = prev; - float end = mAppliedCurve[j]; - float delta = (end - start) / (j - i + 1); - for (int k = i; k < j; k++) { - start = start + delta; - mAppliedCurve[k] = start; - } - i = j; - } - prev = mAppliedCurve[i]; - } - for (int i = 0; i < 256; i++) { - mAppliedCurve[i] = mAppliedCurve[i] * 255; - } - float[] appliedCurve = new float[256]; - for (int i = 0; i < 256; i++) { - appliedCurve[i] = mAppliedCurve[i]; - } // update image if (getImagePreset() != null) { String filterName = getFilterName(); @@ -379,15 +234,92 @@ public class ImageCurves extends ImageSlave { } if (filter != null) { - filter.setSpline(new Spline(mSpline)); - filter.setCurve(appliedCurve); - filter.setUseRed(mUseRed); - filter.setUseGreen(mUseGreen); - filter.setUseBlue(mUseBlue); + for (int i = 0; i < 4; i++) { + filter.setSpline(new Spline(mSplines[i]), i); + } } mImageLoader.resetImageForPreset(getImagePreset(), this); invalidate(); } } + class ComputeHistogramTask extends AsyncTask<Bitmap, Void, int[]> { + @Override + protected int[] doInBackground(Bitmap... params) { + int[] histo = new int[256 * 3]; + Bitmap bitmap = params[0]; + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + int[] pixels = new int[w * h]; + bitmap.getPixels(pixels, 0, w, 0, 0, w, h); + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + int index = j * w + i; + int r = Color.red(pixels[index]); + int g = Color.green(pixels[index]); + int b = Color.blue(pixels[index]); + histo[r]++; + histo[256 + g]++; + histo[512 + b]++; + } + } + return histo; + } + + @Override + protected void onPostExecute(int[] result) { + System.arraycopy(result, 0, redHistogram, 0, 256); + System.arraycopy(result, 256, greenHistogram, 0, 256); + System.arraycopy(result, 512, blueHistogram, 0, 256); + invalidate(); + } + } + + private void drawHistogram(Canvas canvas, int[] histogram, int color, PorterDuff.Mode mode) { + int max = 0; + for (int i = 0; i < histogram.length; i++) { + if (histogram[i] > max) { + max = histogram[i]; + } + } + float w = getWidth(); + float h = getHeight(); + float wl = w / histogram.length; + float wh = (0.3f * h) / max; + Paint paint = new Paint(); + paint.setARGB(100, 255, 255, 255); + paint.setStrokeWidth((int) Math.ceil(wl)); + + Paint paint2 = new Paint(); + paint2.setColor(color); + paint2.setStrokeWidth(6); + paint2.setXfermode(new PorterDuffXfermode(mode)); + gHistoPath.reset(); + gHistoPath.moveTo(0, h); + boolean firstPointEncountered = false; + float prev = 0; + float last = 0; + for (int i = 0; i < histogram.length; i++) { + float x = i * wl; + float l = histogram[i] * wh; + if (l != 0) { + float v = h - (l + prev) / 2.0f; + if (!firstPointEncountered) { + gHistoPath.lineTo(x, h); + firstPointEncountered = true; + } + gHistoPath.lineTo(x, v); + prev = l; + last = x; + } + } + gHistoPath.lineTo(last, h); + gHistoPath.lineTo(w, h); + gHistoPath.close(); + canvas.drawPath(gHistoPath, paint2); + paint2.setStrokeWidth(2); + paint2.setStyle(Paint.Style.STROKE); + paint2.setARGB(255, 200, 200, 200); + canvas.drawPath(gHistoPath, paint2); + } } diff --git a/src/com/android/gallery3d/filtershow/ui/Spline.java b/src/com/android/gallery3d/filtershow/ui/Spline.java index a272d288f..b5c79747d 100644 --- a/src/com/android/gallery3d/filtershow/ui/Spline.java +++ b/src/com/android/gallery3d/filtershow/ui/Spline.java @@ -1,10 +1,28 @@ package com.android.gallery3d.filtershow.ui; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.drawable.Drawable; + import java.util.Collections; import java.util.Vector; public class Spline { + private final Vector<ControlPoint> mPoints; + private static Drawable mCurveHandle; + private static int mCurveHandleSize; + private static int mCurveWidth; + + public static final int RGB = 0; + public static final int RED = 1; + public static final int GREEN = 2; + public static final int BLUE = 3; + + private final Paint gPaint = new Paint(); + public Spline() { mPoints = new Vector<ControlPoint>(); } @@ -13,73 +31,290 @@ public class Spline { mPoints = new Vector<ControlPoint>(); for (int i = 0; i < spline.mPoints.size(); i++) { ControlPoint p = spline.mPoints.elementAt(i); - mPoints.add(p); + mPoints.add(new ControlPoint(p)); } Collections.sort(mPoints); - delta_t = 1.0f / mPoints.size(); } - public ControlPoint interpolate(float t, ControlPoint p1, - ControlPoint p2, ControlPoint p3, ControlPoint p4) { + public static void setCurveHandle(Drawable drawable, int size) { + mCurveHandle = drawable; + mCurveHandleSize = size; + } - float t3 = t * t * t; - float t2 = t * t; - float b1 = 0.5f * (-t3 + 2 * t2 - t); - float b2 = 0.5f * (3 * t3 - 5 * t2 + 2); - float b3 = 0.5f * (-3 * t3 + 4 * t2 + t); - float b4 = 0.5f * (t3 - t2); + public static void setCurveWidth(int width) { + mCurveWidth = width; + } - ControlPoint b1p1 = p1.multiply(b1); - ControlPoint b2p2 = p2.multiply(b2); - ControlPoint b3p3 = p3.multiply(b3); - ControlPoint b4p4 = p4.multiply(b4); + public static int curveHandleSize() { + return mCurveHandleSize; + } - return b1p1.add(b2p2.add(b3p3.add(b4p4))); + public static int colorForCurve(int curveIndex) { + switch (curveIndex) { + case Spline.RED: + return Color.RED; + case GREEN: + return Color.GREEN; + case BLUE: + return Color.BLUE; + } + return Color.WHITE; } - public void addPoint(float x, float y) { - addPoint(new ControlPoint(x, y)); + public boolean isOriginal() { + if (this.getNbPoints() > 2) { + return false; + } + if (mPoints.elementAt(0).x != 0 || mPoints.elementAt(0).y != 1) { + return false; + } + if (mPoints.elementAt(1).x != 1 || mPoints.elementAt(1).y != 0) { + return false; + } + return true; } - public void addPoint(ControlPoint v) { - mPoints.add(v); - Collections.sort(mPoints); - delta_t = 1.0f / mPoints.size(); + private void drawHandles(Canvas canvas, Drawable indicator, float centerX, float centerY) { + int left = (int) centerX - mCurveHandleSize / 2; + int top = (int) centerY - mCurveHandleSize / 2; + indicator.setBounds(left, top, left + mCurveHandleSize, top + mCurveHandleSize); + indicator.draw(canvas); + } + + public float[] getAppliedCurve() { + float[] curve = new float[256]; + ControlPoint[] points = new ControlPoint[mPoints.size()]; + for (int i = 0; i < mPoints.size(); i++) { + ControlPoint p = mPoints.get(i); + points[i] = new ControlPoint(p.x, p.y); + } + double[] derivatives = solveSystem(points); + int start = 0; + if (points[0].x != 0) { + start = (int) (points[0].x * 256); + } + for (int i = 0; i < start; i++) { + curve[i] = 1.0f - points[0].y; + } + for (int i = start; i < 256; i++) { + ControlPoint cur = null; + ControlPoint next = null; + double x = i / 256.0; + int pivot = 0; + for (int j = 0; j < points.length - 1; j++) { + if (x >= points[j].x && x <= points[j + 1].x) { + pivot = j; + } + } + cur = points[pivot]; + next = points[pivot + 1]; + if (x <= next.x) { + double x1 = cur.x; + double x2 = next.x; + double y1 = cur.y; + double y2 = next.y; + + // Use the second derivatives to apply the cubic spline + // equation: + double delta = (x2 - x1); + double delta2 = delta * delta; + double b = (x - x1) / delta; + double a = 1 - b; + double ta = a * y1; + double tb = b * y2; + double tc = (a * a * a - a) * derivatives[pivot]; + double td = (b * b * b - b) * derivatives[pivot + 1]; + double y = ta + tb + (delta2 / 6) * (tc + td); + if (y > 1.0f) { + y = 1.0f; + } + if (y < 0) { + y = 0; + } + curve[i] = (float) (1.0f - y); + } else { + curve[i] = 1.0f - next.y; + } + } + return curve; + } + + private void drawGrid(Canvas canvas, float w, float h) { + // Grid + gPaint.setARGB(128, 150, 150, 150); + gPaint.setStrokeWidth(1); + + float stepH = h / 9; + float stepW = w / 9; + + // central diagonal + gPaint.setARGB(255, 100, 100, 100); + gPaint.setStrokeWidth(2); + canvas.drawLine(0, h, w, 0, gPaint); + + gPaint.setARGB(128, 200, 200, 200); + gPaint.setStrokeWidth(4); + stepH = h / 3; + stepW = w / 3; + for (int j = 1; j < 3; j++) { + canvas.drawLine(0, j * stepH, w, j * stepH, gPaint); + canvas.drawLine(j * stepW, 0, j * stepW, h, gPaint); + } + canvas.drawLine(0, 0, 0, h, gPaint); + canvas.drawLine(w, 0, w, h, gPaint); + canvas.drawLine(0, 0, w, 0, gPaint); + canvas.drawLine(0, h, w, h, gPaint); } - public ControlPoint getPoint(float t) { - int p = (int) (t / delta_t); - int p0 = p - 1; - int max = mPoints.size() - 1; + public void draw(Canvas canvas, int color, int canvasWidth, int canvasHeight, + boolean showHandles) { + float w = canvasWidth; + float h = canvasHeight - mCurveHandleSize; + float dx = 0; + float dy = mCurveHandleSize / 2; - if (p0 < 0) { - p0 = 0; - } else if (p0 >= max) { - p0 = max; + // The cubic spline equation is (from numerical recipes in C): + // y = a(y_i) + b(y_i+1) + c(y"_i) + d(y"_i+1) + // + // with c(y"_i) and d(y"_i+1): + // c(y"_i) = 1/6 (a^3 - a) delta^2 (y"_i) + // d(y"_i_+1) = 1/6 (b^3 - b) delta^2 (y"_i+1) + // + // and delta: + // delta = x_i+1 - x_i + // + // To find the second derivatives y", we can rearrange the equation as: + // A(y"_i-1) + B(y"_i) + C(y"_i+1) = D + // + // With the coefficients A, B, C, D: + // A = 1/6 (x_i - x_i-1) + // B = 1/3 (x_i+1 - x_i-1) + // C = 1/6 (x_i+1 - x_i) + // D = (y_i+1 - y_i)/(x_i+1 - x_i) - (y_i - y_i-1)/(x_i - x_i-1) + // + // We can now easily solve the equation to find the second derivatives: + ControlPoint[] points = new ControlPoint[mPoints.size()]; + for (int i = 0; i < mPoints.size(); i++) { + ControlPoint p = mPoints.get(i); + points[i] = new ControlPoint(p.x * w, p.y * h); } - int p1 = p; - if (p1 < 0) { - p1 = 0; - } else if (p1 >= max) { - p1 = max; + double[] derivatives = solveSystem(points); + + Path path = new Path(); + path.moveTo(0, points[0].y); + for (int i = 0; i < points.length - 1; i++) { + double x1 = points[i].x; + double x2 = points[i + 1].x; + double y1 = points[i].y; + double y2 = points[i + 1].y; + + for (double x = x1; x < x2; x += 20) { + // Use the second derivatives to apply the cubic spline + // equation: + double delta = (x2 - x1); + double delta2 = delta * delta; + double b = (x - x1) / delta; + double a = 1 - b; + double ta = a * y1; + double tb = b * y2; + double tc = (a * a * a - a) * derivatives[i]; + double td = (b * b * b - b) * derivatives[i + 1]; + double y = ta + tb + (delta2 / 6) * (tc + td); + if (y > h) { + y = h; + } + if (y < 0) { + y = 0; + } + path.lineTo((float) x, (float) y); + } } - int p2 = p + 1; - if (p2 < 0) { - p2 = 0; - } else if (p2 >= max) { - p2 = max; + canvas.save(); + canvas.translate(dx, dy); + drawGrid(canvas, w, h); + ControlPoint lastPoint = points[points.length - 1]; + path.lineTo(lastPoint.x, lastPoint.y); + path.lineTo(w, lastPoint.y); + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setFilterBitmap(true); + paint.setDither(true); + paint.setStyle(Paint.Style.STROKE); + int curveWidth = mCurveWidth; + if (showHandles) { + curveWidth *= 1.5; } - int p3 = p + 2; - if (p3 < 0) { - p3 = 0; - } else if (p3 >= max) { - p3 = max; + paint.setStrokeWidth(curveWidth + 2); + paint.setColor(Color.BLACK); + canvas.drawPath(path, paint); + paint.setStrokeWidth(curveWidth); + paint.setColor(color); + canvas.drawPath(path, paint); + if (showHandles) { + for (int i = 0; i < points.length; i++) { + float x = points[i].x; + float y = points[i].y; + drawHandles(canvas, mCurveHandle, x, y); + } + } + canvas.restore(); + } + + double[] solveSystem(ControlPoint[] points) { + int n = points.length; + double[][] system = new double[n][3]; + double[] result = new double[n]; // d + double[] solution = new double[n]; // returned coefficients + system[0][1] = 1; + system[n - 1][1] = 1; + double d6 = 1.0 / 6.0; + double d3 = 1.0 / 3.0; + + // let's create a tridiagonal matrix representing the + // system, and apply the TDMA algorithm to solve it + // (see http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm) + for (int i = 1; i < n - 1; i++) { + double deltaPrevX = points[i].x - points[i - 1].x; + double deltaX = points[i + 1].x - points[i - 1].x; + double deltaNextX = points[i + 1].x - points[i].x; + double deltaNextY = points[i + 1].y - points[i].y; + double deltaPrevY = points[i].y - points[i - 1].y; + system[i][0] = d6 * deltaPrevX; // a_i + system[i][1] = d3 * deltaX; // b_i + system[i][2] = d6 * deltaNextX; // c_i + result[i] = (deltaNextY / deltaNextX) - (deltaPrevY / deltaPrevX); // d_i } - float lt = (t - delta_t * (float) p) / delta_t; - return interpolate(lt, mPoints.elementAt(p0), - mPoints.elementAt(p1), mPoints.elementAt(p2), - mPoints.elementAt(p3)); + // Forward sweep + for (int i = 1; i < n; i++) { + // m = a_i/b_i-1 + double m = system[i][0] / system[i - 1][1]; + // b_i = b_i - m(c_i-1) + system[i][1] = system[i][1] - m * system[i - 1][2]; + // d_i = d_i - m(d_i-1) + result[i] = result[i] - m * result[i - 1]; + } + + // Back substitution + solution[n - 1] = result[n - 1] / system[n - 1][1]; + for (int i = n - 2; i >= 0; --i) { + solution[i] = (result[i] - system[i][2] * solution[i + 1]) / system[i][1]; + } + return solution; + } + + public void addPoint(float x, float y) { + addPoint(new ControlPoint(x, y)); + } + + public void addPoint(ControlPoint v) { + mPoints.add(v); + Collections.sort(mPoints); + } + + public void deletePoint(int n) { + mPoints.remove(n); + Collections.sort(mPoints); } public int getNbPoints() { @@ -106,15 +341,6 @@ public class Spline { return true; } - public void deletePoint(int n) { - mPoints.remove(n); - Collections.sort(mPoints); - delta_t = 1.0f / (mPoints.size() - 1f); - } - - private Vector<ControlPoint> mPoints; - private float delta_t; - public Spline copy() { Spline spline = new Spline(); for (int i = 0; i < mPoints.size(); i++) { @@ -123,4 +349,5 @@ public class Spline { } return spline; } + } diff --git a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java index d5337f00d..9b3f29f2b 100644 --- a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java +++ b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java @@ -71,7 +71,7 @@ public class AlbumSetSlotRenderer extends AbstractSlotRenderer { mWaitLoadingTexture = new ColorTexture(mPlaceholderColor); mWaitLoadingTexture.setSize(1, 1); mCameraOverlay = new ResourceTexture(activity, - R.drawable.frame_overlay_gallery_camera); + R.drawable.ic_cameraalbum_overlay); } public void setPressedIndex(int index) { diff --git a/src/com/android/gallery3d/ui/BasicTexture.java b/src/com/android/gallery3d/ui/BasicTexture.java index 7b8e30de4..99cf0571c 100644 --- a/src/com/android/gallery3d/ui/BasicTexture.java +++ b/src/com/android/gallery3d/ui/BasicTexture.java @@ -42,8 +42,8 @@ abstract class BasicTexture implements Texture { protected int mWidth = UNSPECIFIED; protected int mHeight = UNSPECIFIED; - private int mTextureWidth; - private int mTextureHeight; + protected int mTextureWidth; + protected int mTextureHeight; private boolean mHasBorder; diff --git a/src/com/android/gallery3d/ui/BitmapScreenNail.java b/src/com/android/gallery3d/ui/BitmapScreenNail.java index bf31bcbdf..741eefbe3 100644 --- a/src/com/android/gallery3d/ui/BitmapScreenNail.java +++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java @@ -19,209 +19,40 @@ package com.android.gallery3d.ui; import android.graphics.Bitmap; import android.graphics.RectF; -import com.android.gallery3d.common.Utils; -import com.android.gallery3d.data.BitmapPool; -import com.android.gallery3d.data.MediaItem; - -// This is a ScreenNail wraps a Bitmap. There are some extra functions: -// -// - If we need to draw before the bitmap is available, we draw a rectange of -// placeholder color (gray). -// -// - When the the bitmap is available, and we have drawn the placeholder color -// before, we will do a fade-in animation. public class BitmapScreenNail implements ScreenNail { - @SuppressWarnings("unused") - private static final String TAG = "BitmapScreenNail"; - - // The duration of the fading animation in milliseconds - private static final int DURATION = 180; - - private static int sMaxSide = 640; - - // These are special values for mAnimationStartTime - private static final long ANIMATION_NOT_NEEDED = -1; - private static final long ANIMATION_NEEDED = -2; - private static final long ANIMATION_DONE = -3; - - private int mWidth; - private int mHeight; - private Bitmap mBitmap; - private BitmapTexture mTexture; - private long mAnimationStartTime = ANIMATION_NOT_NEEDED; + private final BitmapTexture mBitmapTexture; public BitmapScreenNail(Bitmap bitmap) { - mWidth = bitmap.getWidth(); - mHeight = bitmap.getHeight(); - mBitmap = bitmap; - // We create mTexture lazily, so we don't incur the cost if we don't - // actually need it. - } - - public BitmapScreenNail(int width, int height) { - setSize(width, height); - } - - // This gets overridden by bitmap_screennail_placeholder - // in GalleryUtils.initialize - private static int mPlaceholderColor = 0xFF222222; - private static boolean mDrawPlaceholder = true; - - public static void setPlaceholderColor(int color) { - mPlaceholderColor = color; - } - - private void setSize(int width, int height) { - if (width == 0 || height == 0) { - width = sMaxSide; - height = sMaxSide * 3 / 4; - } - float scale = Math.min(1, (float) sMaxSide / Math.max(width, height)); - mWidth = Math.round(scale * width); - mHeight = Math.round(scale * height); - } - - private static void recycleBitmap(BitmapPool pool, Bitmap bitmap) { - if (pool == null || bitmap == null) return; - pool.recycle(bitmap); - } - - // Combines the two ScreenNails. - // Returns the used one and recycle the unused one. - public ScreenNail combine(ScreenNail other) { - if (other == null) { - return this; - } - - if (!(other instanceof BitmapScreenNail)) { - recycle(); - return other; - } - - // Now both are BitmapScreenNail. Move over the information about width, - // height, and Bitmap, then recycle the other. - BitmapScreenNail newer = (BitmapScreenNail) other; - mWidth = newer.mWidth; - mHeight = newer.mHeight; - if (newer.mBitmap != null) { - recycleBitmap(MediaItem.getThumbPool(), mBitmap); - mBitmap = newer.mBitmap; - newer.mBitmap = null; - - if (mTexture != null) { - mTexture.recycle(); - mTexture = null; - } - } - - newer.recycle(); - return this; - } - - public void updatePlaceholderSize(int width, int height) { - if (mBitmap != null) return; - if (width == 0 || height == 0) return; - setSize(width, height); + mBitmapTexture = new BitmapTexture(bitmap); } @Override public int getWidth() { - return mWidth; + return mBitmapTexture.getWidth(); } @Override public int getHeight() { - return mHeight; + return mBitmapTexture.getHeight(); } @Override - public void noDraw() { + public void draw(GLCanvas canvas, int x, int y, int width, int height) { + mBitmapTexture.draw(canvas, x, y, width, height); } @Override - public void recycle() { - if (mTexture != null) { - mTexture.recycle(); - mTexture = null; - } - recycleBitmap(MediaItem.getThumbPool(), mBitmap); - mBitmap = null; - } - - public static void disableDrawPlaceholder() { - mDrawPlaceholder = false; - } - - public static void enableDrawPlaceholder() { - mDrawPlaceholder = true; + public void noDraw() { + // do nothing } @Override - public void draw(GLCanvas canvas, int x, int y, int width, int height) { - if (mBitmap == null) { - if (mAnimationStartTime == ANIMATION_NOT_NEEDED) { - mAnimationStartTime = ANIMATION_NEEDED; - } - if(mDrawPlaceholder) { - canvas.fillRect(x, y, width, height, mPlaceholderColor); - } - return; - } - - if (mTexture == null) { - mTexture = new BitmapTexture(mBitmap); - } - - if (mAnimationStartTime == ANIMATION_NEEDED) { - mAnimationStartTime = now(); - } - - if (isAnimating()) { - canvas.drawMixed(mTexture, mPlaceholderColor, getRatio(), x, y, - width, height); - } else { - mTexture.draw(canvas, x, y, width, height); - } + public void recycle() { + mBitmapTexture.recycle(); } @Override public void draw(GLCanvas canvas, RectF source, RectF dest) { - if (mBitmap == null) { - canvas.fillRect(dest.left, dest.top, dest.width(), dest.height(), - mPlaceholderColor); - return; - } - - if (mTexture == null) { - mTexture = new BitmapTexture(mBitmap); - } - - canvas.drawTexture(mTexture, source, dest); - } - - public boolean isAnimating() { - if (mAnimationStartTime < 0) return false; - if (now() - mAnimationStartTime >= DURATION) { - mAnimationStartTime = ANIMATION_DONE; - return false; - } - return true; - } - - private static long now() { - return AnimationTime.get(); - } - - private float getRatio() { - float r = (float)(now() - mAnimationStartTime) / DURATION; - return Utils.clamp(1.0f - r, 0.0f, 1.0f); - } - - public boolean isShowingPlaceholder() { - return (mBitmap == null) || isAnimating(); - } - - public static void setMaxSide(int size) { - sMaxSide = size; + canvas.drawTexture(mBitmapTexture, source, dest); } } diff --git a/src/com/android/gallery3d/ui/GLCanvas.java b/src/com/android/gallery3d/ui/GLCanvas.java index e3a32ef08..6f8baef7e 100644 --- a/src/com/android/gallery3d/ui/GLCanvas.java +++ b/src/com/android/gallery3d/ui/GLCanvas.java @@ -99,6 +99,13 @@ public interface GLCanvas { public 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, + 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(); diff --git a/src/com/android/gallery3d/ui/GLCanvasImpl.java b/src/com/android/gallery3d/ui/GLCanvasImpl.java index d83daf3e4..45903b3cd 100644 --- a/src/com/android/gallery3d/ui/GLCanvasImpl.java +++ b/src/com/android/gallery3d/ui/GLCanvasImpl.java @@ -415,7 +415,7 @@ public class GLCanvasImpl implements GLCanvas { // 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 void convertCoordinate(RectF source, RectF target, + private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) { int width = texture.getWidth(); @@ -465,23 +465,7 @@ public class GLCanvasImpl implements GLCanvas { color[3] = alpha; } - private void drawMixed(BasicTexture from, int toColor, - float ratio, int x, int y, int width, int height, float alpha) { - // change from 0 to 0.01f to prevent getting divided by zero below - if (ratio <= 0.01f) { - drawTexture(from, x, y, width, height, alpha); - return; - } else if (ratio >= 1) { - fillRect(x, y, width, height, toColor); - return; - } - - mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() - || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA)); - - final GL11 gl = mGL; - if (!bindTexture(from)) return; - + private void setMixedColor(int toColor, float ratio, float alpha) { // // The formula we want: // alpha * ((1 - ratio) * from + ratio * to) @@ -495,9 +479,6 @@ public class GLCanvasImpl implements GLCanvas { float combo = alpha * (1 - ratio); float scale = alpha * ratio / (1 - combo); - // Interpolate the RGB and alpha values between both textures. - mGLState.setTexEnvMode(GL11.GL_COMBINE); - // Specify the interpolation factor via the alpha component of // GL_TEXTURE_ENV_COLORs. // RGB component are get from toColor and will used as SRC1 @@ -505,6 +486,7 @@ public class GLCanvasImpl implements GLCanvas { setTextureColor(((toColor >>> 16) & 0xff) * colorScale, ((toColor >>> 8) & 0xff) * colorScale, (toColor & 0xff) * colorScale, combo); + GL11 gl = mGL; gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE); @@ -522,6 +504,64 @@ public class GLCanvasImpl implements GLCanvas { gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA); + } + + @Override + public void drawMixed(BasicTexture from, int toColor, float ratio, + RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) return; + + if (ratio <= 0.01f) { + drawTexture(from, source, target); + return; + } else if (ratio >= 1) { + fillRect(target.left, target.top, target.width(), target.height(), toColor); + return; + } + + float alpha = mAlpha; + + // Copy the input to avoid changing it. + mDrawTextureSourceRect.set(source); + mDrawTextureTargetRect.set(target); + source = mDrawTextureSourceRect; + target = mDrawTextureTargetRect; + + mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() + || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA)); + + if (!bindTexture(from)) return; + + // Interpolate the RGB and alpha values between both textures. + mGLState.setTexEnvMode(GL11.GL_COMBINE); + setMixedColor(toColor, ratio, alpha); + convertCoordinate(source, target, from); + setTextureCoords(source); + textureRect(target.left, target.top, target.width(), target.height()); + mGLState.setTexEnvMode(GL11.GL_REPLACE); + } + + private void drawMixed(BasicTexture from, int toColor, + float ratio, int x, int y, int width, int height, float alpha) { + // change from 0 to 0.01f to prevent getting divided by zero below + if (ratio <= 0.01f) { + drawTexture(from, x, y, width, height, alpha); + return; + } else if (ratio >= 1) { + fillRect(x, y, width, height, toColor); + return; + } + + mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() + || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA)); + + final GL11 gl = mGL; + if (!bindTexture(from)) return; + + // Interpolate the RGB and alpha values between both textures. + mGLState.setTexEnvMode(GL11.GL_COMBINE); + setMixedColor(toColor, ratio, alpha); + drawBoundTexture(from, x, y, width, height); mGLState.setTexEnvMode(GL11.GL_REPLACE); } diff --git a/src/com/android/gallery3d/ui/GLRoot.java b/src/com/android/gallery3d/ui/GLRoot.java index 1651b4361..13b610b23 100644 --- a/src/com/android/gallery3d/ui/GLRoot.java +++ b/src/com/android/gallery3d/ui/GLRoot.java @@ -16,6 +16,7 @@ package com.android.gallery3d.ui; +import android.content.Context; import android.graphics.Matrix; import com.android.gallery3d.anim.CanvasAnimation; @@ -45,4 +46,6 @@ public interface GLRoot { public void freeze(); public void unfreeze(); public void setLightsOutMode(boolean enabled); + + public Context getContext(); } diff --git a/src/com/android/gallery3d/ui/GLView.java b/src/com/android/gallery3d/ui/GLView.java index 3924c6e9d..b91b71212 100644 --- a/src/com/android/gallery3d/ui/GLView.java +++ b/src/com/android/gallery3d/ui/GLView.java @@ -21,6 +21,7 @@ import android.os.SystemClock; import android.view.MotionEvent; import com.android.gallery3d.anim.CanvasAnimation; +import com.android.gallery3d.anim.StateTransitionAnimation; import com.android.gallery3d.common.Utils; import java.util.ArrayList; @@ -77,6 +78,11 @@ public class GLView { protected int mScrollHeight = 0; protected int mScrollWidth = 0; + public static final int ANIM_TIME_OPENING = 400; + private RawTexture mFadeOutTexture; + private float [] mBackgroundColor; + private StateTransitionAnimation mTransition = new StateTransitionAnimation(ANIM_TIME_OPENING); + public void startAnimation(CanvasAnimation animation) { GLRoot root = getGLRoot(); if (root == null) throw new IllegalStateException(); @@ -217,13 +223,46 @@ public class GLView { } protected void render(GLCanvas canvas) { + if (mTransition.calculate(AnimationTime.get())) invalidate(); + canvas.save(); renderBackground(canvas); + if (mTransition.isActive()) mTransition.applyForegroundTransformation(this, canvas); for (int i = 0, n = getComponentCount(); i < n; ++i) { renderChild(canvas, getComponent(i)); } + canvas.restore(); + } + + public void setFadeOutTexture(RawTexture texture) { + mFadeOutTexture = texture; + if (mFadeOutTexture != null) { + TiledScreenNail.disableDrawPlaceholder(); + } + mTransition.start(); + } + + public float [] getBackgroundColor() { + return mBackgroundColor; + } + + public void setBackgroundColor(float [] color) { + mBackgroundColor = color; } protected void renderBackground(GLCanvas view) { + if (mBackgroundColor != null) { + view.clearBuffer(mBackgroundColor); + } + if (mFadeOutTexture != null) { + if (!mTransition.isActive()) { + mFadeOutTexture.recycle(); + mFadeOutTexture = null; + TiledScreenNail.enableDrawPlaceholder(); + } else { + mTransition.applyBackground(this, view, mFadeOutTexture); + return; + } + } } protected void renderChild(GLCanvas canvas, GLView component) { diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java index 5978159e4..edd7c72e3 100644 --- a/src/com/android/gallery3d/ui/PhotoView.java +++ b/src/com/android/gallery3d/ui/PhotoView.java @@ -17,6 +17,7 @@ package com.android.gallery3d.ui; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Rect; @@ -546,10 +547,20 @@ public class PhotoView extends GLView { } private int getPanoramaRotation() { - // Panorama only support rotations of 0 and 90, so if it is greater - // than that flip the output surface texture to compensate - if (mDisplayRotation > 180) + // This function is magic + // The issue here is that Pano makes bad assumptions about rotation and + // orientation. The first is it assumes only two rotations are possible, + // 0 and 90. Thus, if display rotation is >= 180, we invert the output. + // The second is that it assumes landscape is a 90 rotation from portrait, + // however on landscape devices this is not true. Thus, if we are in portrait + // on a landscape device, we need to invert the output + int orientation = getGLRoot().getContext().getResources().getConfiguration().orientation; + boolean invertPortrait = (orientation == Configuration.ORIENTATION_PORTRAIT + && (mDisplayRotation == 90 || mDisplayRotation == 270)); + boolean invert = (mDisplayRotation >= 180); + if (invert != invertPortrait) { return (mCompensation + 180) % 360; + } return mCompensation; } @@ -839,8 +850,8 @@ public class PhotoView extends GLView { } private boolean isScreenNailAnimating() { - return (mScreenNail instanceof BitmapScreenNail) - && ((BitmapScreenNail) mScreenNail).isAnimating(); + return (mScreenNail instanceof TiledScreenNail) + && ((TiledScreenNail) mScreenNail).isAnimating(); } @Override @@ -1781,8 +1792,8 @@ public class PhotoView extends GLView { MediaItem item = mModel.getMediaItem(i); if (item == null) continue; ScreenNail sc = mModel.getScreenNail(i); - if (!(sc instanceof BitmapScreenNail) - || ((BitmapScreenNail) sc).isShowingPlaceholder()) continue; + if (!(sc instanceof TiledScreenNail) + || ((TiledScreenNail) sc).isShowingPlaceholder()) continue; // Now, sc is BitmapScreenNail and is not showing placeholder Rect rect = new Rect(getPhotoRect(i)); diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java index fffa7e038..0111847eb 100644 --- a/src/com/android/gallery3d/ui/PositionController.java +++ b/src/com/android/gallery3d/ui/PositionController.java @@ -67,7 +67,7 @@ class PositionController { SNAPBACK_ANIMATION_TIME, // ANIM_KIND_SNAPBACK 400, // ANIM_KIND_SLIDE 300, // ANIM_KIND_ZOOM - PhotoPage.ANIM_TIME_OPENING, // ANIM_KIND_OPENING + GLView.ANIM_TIME_OPENING, // ANIM_KIND_OPENING 0, // ANIM_KIND_FLING (the duration is calculated dynamically) 0, // ANIM_KIND_FLING_X (see the comment above) 0, // ANIM_KIND_DELETE (the duration is calculated dynamically) diff --git a/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java b/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java index 812e831e1..7cab753c3 100644 --- a/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java +++ b/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java @@ -6,7 +6,7 @@ import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; public class PreparePageFadeoutTexture implements OnGLIdleListener { - private static final long TIMEOUT = FadeTexture.DURATION; + private static final long TIMEOUT = 500; public static final String KEY_FADE_TEXTURE = "fade_texture"; private RawTexture mTexture; diff --git a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java index 1930e3877..7cb894845 100644 --- a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java +++ b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java @@ -109,6 +109,7 @@ public abstract class SurfaceTextureScreenNail implements ScreenNail, canvas.translate(cx, cy); canvas.scale(1, -1, 1); canvas.translate(-cx, -cy); + updateTransformMatrix(mTransform); canvas.drawTexture(mExtTexture, mTransform, x, y, width, height); canvas.restore(); } @@ -119,6 +120,8 @@ public abstract class SurfaceTextureScreenNail implements ScreenNail, throw new UnsupportedOperationException(); } + protected void updateTransformMatrix(float[] matrix) {} + @Override abstract public void noDraw(); diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java index 5ce06bec4..8f26981fe 100644 --- a/src/com/android/gallery3d/ui/TileImageView.java +++ b/src/com/android/gallery3d/ui/TileImageView.java @@ -462,8 +462,8 @@ public class TileImageView extends GLView { } private boolean isScreenNailAnimating() { - return (mScreenNail instanceof BitmapScreenNail) - && ((BitmapScreenNail) mScreenNail).isAnimating(); + return (mScreenNail instanceof TiledScreenNail) + && ((TiledScreenNail) mScreenNail).isAnimating(); } private void uploadBackgroundTiles(GLCanvas canvas) { @@ -575,8 +575,10 @@ public class TileImageView extends GLView { } if (tile == null) break; if (!tile.isContentValid()) { + boolean hasBeenLoaded = tile.isLoaded(); Utils.assertTrue(tile.mTileState == STATE_DECODED); tile.updateContent(canvas); + if (!hasBeenLoaded) tile.draw(canvas, 0, 0); --quota; } } @@ -621,7 +623,6 @@ public class TileImageView extends GLView { } } - // TODO: avoid drawing the unused part of the textures. static boolean drawTile( Tile tile, GLCanvas canvas, RectF source, RectF target) { while (true) { diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java index 08d337921..45e2ce218 100644 --- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java +++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java @@ -40,51 +40,25 @@ public class TileImageViewAdapter implements TileImageView.Model { public TileImageViewAdapter() { } - public TileImageViewAdapter( - Bitmap bitmap, BitmapRegionDecoder regionDecoder) { - Utils.checkNotNull(bitmap); - updateScreenNail(new BitmapScreenNail(bitmap), true); - mRegionDecoder = regionDecoder; - mImageWidth = regionDecoder.getWidth(); - mImageHeight = regionDecoder.getHeight(); - mLevelCount = calculateLevelCount(); - } - public synchronized void clear() { - updateScreenNail(null, false); + mScreenNail = null; mImageWidth = 0; mImageHeight = 0; mLevelCount = 0; mRegionDecoder = null; } - public synchronized void setScreenNail(Bitmap bitmap, int width, int height) { - Utils.checkNotNull(bitmap); - updateScreenNail(new BitmapScreenNail(bitmap), true); - mImageWidth = width; - mImageHeight = height; - mRegionDecoder = null; - mLevelCount = 0; - } - + // Caller is responsible to recycle the ScreenNail public synchronized void setScreenNail( ScreenNail screenNail, int width, int height) { Utils.checkNotNull(screenNail); - updateScreenNail(screenNail, false); + mScreenNail = screenNail; mImageWidth = width; mImageHeight = height; mRegionDecoder = null; mLevelCount = 0; } - private void updateScreenNail(ScreenNail screenNail, boolean own) { - if (mScreenNail != null && mOwnScreenNail) { - mScreenNail.recycle(); - } - mScreenNail = screenNail; - mOwnScreenNail = own; - } - public synchronized void setRegionDecoder(BitmapRegionDecoder decoder) { mRegionDecoder = Utils.checkNotNull(decoder); mImageWidth = decoder.getWidth(); diff --git a/src/com/android/gallery3d/ui/TiledScreenNail.java b/src/com/android/gallery3d/ui/TiledScreenNail.java new file mode 100644 index 000000000..d2b34e3bf --- /dev/null +++ b/src/com/android/gallery3d/ui/TiledScreenNail.java @@ -0,0 +1,216 @@ +/* + * 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 com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.BitmapPool; +import com.android.gallery3d.data.MediaItem; + +// This is a ScreenNail wraps a Bitmap. There are some extra functions: +// +// - If we need to draw before the bitmap is available, we draw a rectange of +// placeholder color (gray). +// +// - When the the bitmap is available, and we have drawn the placeholder color +// before, we will do a fade-in animation. +public class TiledScreenNail implements ScreenNail { + @SuppressWarnings("unused") + private static final String TAG = "TiledScreenNail"; + + // The duration of the fading animation in milliseconds + private static final int DURATION = 180; + + private static int sMaxSide = 640; + + // These are special values for mAnimationStartTime + private static final long ANIMATION_NOT_NEEDED = -1; + private static final long ANIMATION_NEEDED = -2; + private static final long ANIMATION_DONE = -3; + + private int mWidth; + private int mHeight; + private long mAnimationStartTime = ANIMATION_NOT_NEEDED; + + private Bitmap mBitmap; + private TiledTexture mTexture; + + public TiledScreenNail(Bitmap bitmap) { + mWidth = bitmap.getWidth(); + mHeight = bitmap.getHeight(); + mBitmap = bitmap; + mTexture = new TiledTexture(bitmap); + } + + public TiledScreenNail(int width, int height) { + setSize(width, height); + } + + // This gets overridden by bitmap_screennail_placeholder + // in GalleryUtils.initialize + private static int mPlaceholderColor = 0xFF222222; + private static boolean mDrawPlaceholder = true; + + public static void setPlaceholderColor(int color) { + mPlaceholderColor = color; + } + + private void setSize(int width, int height) { + if (width == 0 || height == 0) { + width = sMaxSide; + height = sMaxSide * 3 / 4; + } + float scale = Math.min(1, (float) sMaxSide / Math.max(width, height)); + mWidth = Math.round(scale * width); + mHeight = Math.round(scale * height); + } + + private static void recycleBitmap(BitmapPool pool, Bitmap bitmap) { + if (pool == null || bitmap == null) return; + pool.recycle(bitmap); + } + + // Combines the two ScreenNails. + // Returns the used one and recycle the unused one. + public ScreenNail combine(ScreenNail other) { + if (other == null) { + return this; + } + + if (!(other instanceof TiledScreenNail)) { + recycle(); + return other; + } + + // Now both are TiledScreenNail. Move over the information about width, + // height, and Bitmap, then recycle the other. + TiledScreenNail newer = (TiledScreenNail) other; + mWidth = newer.mWidth; + mHeight = newer.mHeight; + if (newer.mTexture != null) { + recycleBitmap(MediaItem.getThumbPool(), mBitmap); + if (mTexture != null) mTexture.recycle(); + mBitmap = newer.mBitmap; + mTexture = newer.mTexture; + newer.mBitmap = null; + newer.mTexture = null; + } + newer.recycle(); + return this; + } + + public void updatePlaceholderSize(int width, int height) { + if (mBitmap != null) return; + if (width == 0 || height == 0) return; + setSize(width, height); + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } + + @Override + public void noDraw() { + } + + @Override + public void recycle() { + if (mTexture != null) { + mTexture.recycle(); + mTexture = null; + } + recycleBitmap(MediaItem.getThumbPool(), mBitmap); + mBitmap = null; + } + + public static void disableDrawPlaceholder() { + mDrawPlaceholder = false; + } + + public static void enableDrawPlaceholder() { + mDrawPlaceholder = true; + } + + @Override + public void draw(GLCanvas canvas, int x, int y, int width, int height) { + if (mTexture == null || !mTexture.isReady()) { + if (mAnimationStartTime == ANIMATION_NOT_NEEDED) { + mAnimationStartTime = ANIMATION_NEEDED; + } + if(mDrawPlaceholder) { + canvas.fillRect(x, y, width, height, mPlaceholderColor); + } + return; + } + + if (mAnimationStartTime == ANIMATION_NEEDED) { + mAnimationStartTime = AnimationTime.get(); + } + + if (isAnimating()) { + mTexture.drawMixed(canvas, mPlaceholderColor, getRatio(), x, y, + width, height); + } else { + mTexture.draw(canvas, x, y, width, height); + } + } + + @Override + public void draw(GLCanvas canvas, RectF source, RectF dest) { + if (mTexture == null || !mTexture.isReady()) { + canvas.fillRect(dest.left, dest.top, dest.width(), dest.height(), + mPlaceholderColor); + return; + } + + mTexture.draw(canvas, source, dest); + } + + public boolean isAnimating() { + if (mAnimationStartTime < 0) return false; + if (AnimationTime.get() - mAnimationStartTime >= DURATION) { + mAnimationStartTime = ANIMATION_DONE; + return false; + } + return true; + } + + private float getRatio() { + float r = (float) (AnimationTime.get() - mAnimationStartTime) / DURATION; + return Utils.clamp(1.0f - r, 0.0f, 1.0f); + } + + public boolean isShowingPlaceholder() { + return (mBitmap == null) || isAnimating(); + } + + public TiledTexture getTexture() { + return mTexture; + } + + public static void setMaxSide(int size) { + sMaxSide = size; + } +} diff --git a/src/com/android/gallery3d/ui/TiledTexture.java b/src/com/android/gallery3d/ui/TiledTexture.java new file mode 100644 index 000000000..6e9ad9ea8 --- /dev/null +++ b/src/com/android/gallery3d/ui/TiledTexture.java @@ -0,0 +1,298 @@ +/* + * 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.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; + +import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; + +import java.util.ArrayDeque; +import java.util.ArrayList; + +// This class is similar to BitmapTexture, except the bitmap is +// split into tiles. By doing so, we may increase the time required to +// upload the whole bitmap but we reduce the time of uploading each tile +// so it make the animation more smooth and prevents jank. +public class TiledTexture { + private static final int CONTENT_SIZE = 254; + private static final int BORDER_SIZE = 1; + private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE; + private static final int INIT_CAPACITY = 8; + + private static Tile sFreeTileHead = null; + private static final Object sFreeTileLock = new Object(); + + private static Bitmap sUploadBitmap; + private static Canvas sCanvas; + private static Paint sPaint; + + private int mUploadIndex = 0; + + private final Tile[] mTiles; + private final int mWidth; + private final int mHeight; + private final RectF mSrcRect = new RectF(); + private final RectF mDestRect = new RectF(); + + public static class Uploader implements OnGLIdleListener { + private final ArrayDeque<TiledTexture> mTextures = + new ArrayDeque<TiledTexture>(INIT_CAPACITY); + + private final GLRoot mGlRoot; + private boolean mIsQueued = false; + + public Uploader(GLRoot glRoot) { + mGlRoot = glRoot; + } + + public synchronized void clear() { + mTextures.clear(); + } + + public synchronized void addTexture(TiledTexture t) { + if (t.isReady()) return; + mTextures.addLast(t); + + if (mIsQueued) return; + mIsQueued = true; + mGlRoot.addOnGLIdleListener(this); + } + + + @Override + public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { + ArrayDeque<TiledTexture> deque = mTextures; + synchronized (this) { + if (!deque.isEmpty()) { + TiledTexture t = deque.peekFirst(); + if (t.uploadNextTile(canvas)) { + deque.removeFirst(); + mGlRoot.requestRender(); + } + } + mIsQueued = !mTextures.isEmpty(); + + // return true to keep this listener in the queue + return mIsQueued; + } + } + } + + private static class Tile extends UploadedTexture { + public int offsetX; + public int offsetY; + public Bitmap bitmap; + public Tile nextFreeTile; + public int contentWidth; + public int contentHeight; + + @Override + public void setSize(int width, int height) { + contentWidth = width; + contentHeight = height; + mWidth = width + 2 * BORDER_SIZE; + mHeight = height + 2 * BORDER_SIZE; + mTextureWidth = TILE_SIZE; + mTextureHeight = TILE_SIZE; + } + + @Override + protected Bitmap onGetBitmap() { + int x = BORDER_SIZE - offsetX; + int y = BORDER_SIZE - offsetY; + int r = bitmap.getWidth() - x; + int b = bitmap.getHeight() - y ; + sCanvas.drawBitmap(bitmap, x, y, null); + bitmap = null; + + // draw borders if need + if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint); + if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint); + if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint); + if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint); + + return sUploadBitmap; + } + + @Override + protected void onFreeBitmap(Bitmap bitmap) { + // do nothing + } + } + + private static void freeTile(Tile tile) { + tile.invalidateContent(); + tile.bitmap = null; + synchronized (sFreeTileLock) { + tile.nextFreeTile = sFreeTileHead; + sFreeTileHead = tile; + } + } + + private static Tile obtainTile() { + synchronized (sFreeTileLock) { + Tile result = sFreeTileHead; + if (result == null) return new Tile(); + sFreeTileHead = result.nextFreeTile; + result.nextFreeTile = null; + return result; + } + } + + private boolean uploadNextTile(GLCanvas canvas) { + if (mUploadIndex == mTiles.length) return true; + Tile next = mTiles[mUploadIndex++]; + boolean hasBeenLoad = next.isLoaded(); + next.updateContent(canvas); + + // It will take some time for a texture to be drawn for the first + // time. When scrolling, we need to draw several tiles on the screen + // at the same time. It may cause a UI jank even these textures has + // been uploaded. + if (!hasBeenLoad) next.draw(canvas, 0, 0); + return mUploadIndex == mTiles.length; + } + + public TiledTexture(Bitmap bitmap) { + mWidth = bitmap.getWidth(); + mHeight = bitmap.getHeight(); + ArrayList<Tile> list = new ArrayList<Tile>(); + + for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) { + for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) { + Tile tile = obtainTile(); + tile.offsetX = x; + tile.offsetY = y; + tile.bitmap = bitmap; + tile.setSize( + Math.min(CONTENT_SIZE, mWidth - x), + Math.min(CONTENT_SIZE, mHeight - y)); + list.add(tile); + } + } + mTiles = list.toArray(new Tile[list.size()]); + } + + public boolean isReady() { + return mUploadIndex == mTiles.length; + } + + public void recycle() { + for (int i = 0, n = mTiles.length; i < n; ++i) { + freeTile(mTiles[i]); + } + } + + public static void freeResources() { + sUploadBitmap = null; + sCanvas = null; + sPaint = null; + } + + public static void prepareResources() { + sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888); + sCanvas = new Canvas(sUploadBitmap); + sPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + sPaint.setColor(Color.TRANSPARENT); + sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); + } + + // We want to draw the "source" on the "target". + // This method is to find the "output" rectangle which is + // the corresponding area of the "src". + // (x,y) target + // (x0,y0) source +---------------+ + // +----------+ | | + // | src | | output | + // | +--+ | linear map | +----+ | + // | +--+ | ----------> | | | | + // | | by (scaleX, scaleY) | +----+ | + // +----------+ | | + // Texture +---------------+ + // Canvas + private static void mapRect(RectF output, + RectF src, float x0, float y0, float x, float y, float scaleX, + float scaleY) { + output.set(x + (src.left - x0) * scaleX, + y + (src.top - y0) * scaleY, + x + (src.right - x0) * scaleX, + y + (src.bottom - y0) * scaleY); + } + + // Draws a mixed color of this texture and a specified color onto the + // a rectangle. The used color is: from * (1 - ratio) + to * ratio. + public void drawMixed(GLCanvas canvas, int color, float ratio, + int x, int y, int width, int height) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float scaleX = (float) width / mWidth ; + float scaleY = (float) height / mHeight; + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect); + } + } + + // Draws the texture on to the specified rectangle. + public void draw(GLCanvas canvas, int x, int y, int width, int height) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float scaleX = (float) width / mWidth ; + float scaleY = (float) height / mHeight; + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawTexture(t, mSrcRect, mDestRect); + } + } + + // Draws a sub region of this texture on to the specified rectangle. + public void draw(GLCanvas canvas, RectF source, RectF target) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float x0 = source.left; + float y0 = source.top; + float x = target.left; + float y = target.top; + float scaleX = target.width() / source.width(); + float scaleY = target.height() / source.height(); + + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + if (!src.intersect(source)) continue; + mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawTexture(t, src, dest); + } + } +} diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java index 195552116..62f22355e 100644 --- a/src/com/android/gallery3d/util/GalleryUtils.java +++ b/src/com/android/gallery3d/util/GalleryUtils.java @@ -42,7 +42,7 @@ import com.android.gallery3d.app.PackagesMonitor; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; -import com.android.gallery3d.ui.BitmapScreenNail; +import com.android.gallery3d.ui.TiledScreenNail; import com.android.gallery3d.util.ThreadPool.CancelListener; import com.android.gallery3d.util.ThreadPool.JobContext; @@ -81,7 +81,7 @@ public class GalleryUtils { wm.getDefaultDisplay().getMetrics(metrics); sPixelDensity = metrics.density; Resources r = context.getResources(); - BitmapScreenNail.setPlaceholderColor(r.getColor( + TiledScreenNail.setPlaceholderColor(r.getColor( R.color.bitmap_screennail_placeholder)); initializeThumbnailSizes(metrics, r); } @@ -91,7 +91,7 @@ public class GalleryUtils { // Never need to completely fill the screen maxDimensionPixels = maxDimensionPixels / 2; MediaItem.setThumbnailSizes(maxDimensionPixels, 200); - BitmapScreenNail.setMaxSide(maxDimensionPixels); + TiledScreenNail.setMaxSide(maxDimensionPixels); } public static boolean isHighResolution(Context context) { diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java index 2f2d753e0..5a08b8599 100644 --- a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java +++ b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java @@ -83,4 +83,6 @@ public class GLCanvasStub implements GLCanvas { public void dumpStatisticsAndClear() {} public void beginRenderTarget(RawTexture texture) {} public void endRenderTarget() {} + public void drawMixed(BasicTexture from, int toColor, + float ratio, RectF src, RectF target) {} } diff --git a/tests/src/com/android/gallery3d/ui/GLRootMock.java b/tests/src/com/android/gallery3d/ui/GLRootMock.java index 467edfc7f..b1c4355d0 100644 --- a/tests/src/com/android/gallery3d/ui/GLRootMock.java +++ b/tests/src/com/android/gallery3d/ui/GLRootMock.java @@ -16,6 +16,7 @@ package com.android.gallery3d.ui; +import android.content.Context; import android.graphics.Matrix; import com.android.gallery3d.anim.CanvasAnimation; @@ -42,4 +43,5 @@ public class GLRootMock implements GLRoot { public void freeze() {} public void unfreeze() {} public void setLightsOutMode(boolean enabled) {} + public Context getContext() { return null; } } diff --git a/tests/src/com/android/gallery3d/ui/GLRootStub.java b/tests/src/com/android/gallery3d/ui/GLRootStub.java index 0f3a00164..7f134dbf1 100644 --- a/tests/src/com/android/gallery3d/ui/GLRootStub.java +++ b/tests/src/com/android/gallery3d/ui/GLRootStub.java @@ -16,6 +16,7 @@ package com.android.gallery3d.ui; +import android.content.Context; import android.graphics.Matrix; import com.android.gallery3d.anim.CanvasAnimation; @@ -35,4 +36,5 @@ public class GLRootStub implements GLRoot { public void freeze() {} public void unfreeze() {} public void setLightsOutMode(boolean enabled) {} + public Context getContext() { return null; } } |