diff options
author | Doris Liu <tianliu@google.com> | 2014-02-04 16:57:55 -0800 |
---|---|---|
committer | Doris Liu <tianliu@google.com> | 2014-02-07 18:29:20 -0800 |
commit | 213a4a086b54904cee543adf60b16fc1a61efe38 (patch) | |
tree | 93b76ec9db4942e6b60e4c385a2728d3d6dabf6c | |
parent | e69b88893922350461c3a30586aa9194783ff612 (diff) | |
download | android_packages_apps_Camera2-213a4a086b54904cee543adf60b16fc1a61efe38.tar.gz android_packages_apps_Camera2-213a4a086b54904cee543adf60b16fc1a61efe38.tar.bz2 android_packages_apps_Camera2-213a4a086b54904cee543adf60b16fc1a61efe38.zip |
New mode drawer.
-Changed the entire look of the mode drawer by using a live preview
as background, and use circle icons rather than block shaped icons
-Fade in the blurred preview frame during mode switch
-Add highlighted and selected states to mode icons
-Center the mode drawer in preview rather than whole screen
-Swapped mode order in the drawer
TODO:
-Add settings access point
-Refine swipe in/out behavior of the drawer
Change-Id: Ibab0fe960bcfbb9635ca7f45d50178cb1ef2941f
-rw-r--r-- | res/drawable/mode_icon_background.xml | 27 | ||||
-rw-r--r-- | res/drawable/mode_icon_highlight.xml | 30 | ||||
-rw-r--r-- | res/layout/mode_list_layout.xml | 3 | ||||
-rw-r--r-- | res/layout/mode_selector.xml | 16 | ||||
-rw-r--r-- | res/values/arrays.xml | 24 | ||||
-rw-r--r-- | res/values/colors.xml | 3 | ||||
-rw-r--r-- | res/values/dimens.xml | 3 | ||||
-rw-r--r-- | src/com/android/camera/CameraActivity.java | 6 | ||||
-rw-r--r-- | src/com/android/camera/TextureViewHelper.java | 8 | ||||
-rw-r--r-- | src/com/android/camera/app/CameraAppUI.java | 60 | ||||
-rw-r--r-- | src/com/android/camera/ui/ModeIconView.java | 103 | ||||
-rw-r--r-- | src/com/android/camera/ui/ModeListView.java | 310 | ||||
-rw-r--r-- | src/com/android/camera/ui/ModeSelectorItem.java | 130 | ||||
-rw-r--r-- | src/com/android/camera/ui/ModeTransitionView.java | 2 | ||||
-rw-r--r-- | src/com/android/camera/util/CameraUtil.java | 117 |
15 files changed, 689 insertions, 153 deletions
diff --git a/res/drawable/mode_icon_background.xml b/res/drawable/mode_icon_background.xml new file mode 100644 index 000000000..305216c27 --- /dev/null +++ b/res/drawable/mode_icon_background.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> + +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + + <solid + android:color="@color/mode_selector_icon_background"/> + + <size + android:width="@dimen/mode_selector_icon_block_width" + android:height="@dimen/mode_selector_icon_block_width"/> +</shape>
\ No newline at end of file diff --git a/res/drawable/mode_icon_highlight.xml b/res/drawable/mode_icon_highlight.xml new file mode 100644 index 000000000..f9e3d72e9 --- /dev/null +++ b/res/drawable/mode_icon_highlight.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> + +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="ring" + android:thickness="3dp" + android:useLevel="false" + android:innerRadius="21dp" > + + <solid + android:color="@color/mode_selector_icon_background"/> + + <size + android:width="@dimen/mode_selector_icon_block_width" + android:height="@dimen/mode_selector_icon_block_width" /> +</shape>
\ No newline at end of file diff --git a/res/layout/mode_list_layout.xml b/res/layout/mode_list_layout.xml index 24ddf95fe..6925accc3 100644 --- a/res/layout/mode_list_layout.xml +++ b/res/layout/mode_list_layout.xml @@ -26,6 +26,7 @@ <LinearLayout android:id="@+id/mode_list" android:orientation="vertical" - android:layout_width="match_parent" + android:layout_gravity="center_vertical" + android:layout_width="wrap_content" android:layout_height="wrap_content" /> </com.android.camera.ui.ModeListView>
\ No newline at end of file diff --git a/res/layout/mode_selector.xml b/res/layout/mode_selector.xml index c9d937535..939ed07dd 100644 --- a/res/layout/mode_selector.xml +++ b/res/layout/mode_selector.xml @@ -17,21 +17,23 @@ <com.android.camera.ui.ModeSelectorItem xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="@dimen/mode_selector_item_height" + android:paddingLeft="16dp" + android:paddingTop="6dp" + android:paddingBottom="6dp"> <TextView android:id="@+id/selector_text" - android:layout_gravity="left" - android:paddingLeft="24dp" - android:paddingRight="24dp" + android:layout_gravity="left|center_vertical" + android:paddingLeft="16dp" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" - android:textSize="21sp" + android:textSize="14sp" android:textColor="@color/mode_selector_text_color" android:layout_marginLeft="@dimen/mode_selector_icon_block_width" /> - <ImageView + <com.android.camera.ui.ModeIconView android:id="@+id/selector_icon" android:scaleType="centerInside" android:layout_width="@dimen/mode_selector_icon_block_width" - android:layout_height="match_parent" /> + android:layout_height="@dimen/mode_selector_icon_block_width" /> </com.android.camera.ui.ModeSelectorItem>
\ No newline at end of file diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 16b831b3d..3f484508a 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -583,25 +583,32 @@ <integer name="camera_mode_photosphere">3</integer> <integer name="camera_mode_panorama">4</integer> <integer name="camera_mode_timelapse">5</integer> - <integer name="camera_mode_setting">6</integer> - <integer name="camera_mode_gcam">7</integer> + <integer name="camera_mode_gcam">6</integer> <!-- An array of camera mode indices in the sequence of their appearance in the mode drawer. --> <integer-array name="camera_modes_in_nav_drawer_if_supported"> + <item>@integer/camera_mode_panorama</item> + <item>@integer/camera_mode_photosphere</item> + <item>@integer/camera_mode_refocus</item> + <item>@integer/camera_mode_photo</item> + <item>@integer/camera_mode_video</item> + </integer-array> + + <!-- Camera modes that each supported mode is nested in in nav drawer. --> + <integer-array name="camera_mode_nested_in_nav_drawer"> <item>@integer/camera_mode_photo</item> <item>@integer/camera_mode_video</item> <item>@integer/camera_mode_refocus</item> - <item>@integer/camera_mode_panorama</item> <item>@integer/camera_mode_photosphere</item> - <item>@integer/camera_mode_timelapse</item> - <item>@integer/camera_mode_setting</item> + <item>@integer/camera_mode_panorama</item> + <item>@integer/camera_mode_photo</item> + <item>@integer/camera_mode_photo</item> </integer-array> <!-- An array of camera mode indices that should always be visible in mode drawer. --> <integer-array name="camera_modes_always_visible"> <item>@integer/camera_mode_photo</item> - <item>@integer/camera_mode_setting</item> </integer-array> <array name="camera_mode_theme_color"> @@ -611,7 +618,6 @@ <item>@color/photosphere_mode_color</item> <item>@color/panorama_mode_color</item> <item>@color/timelapse_mode_color</item> - <item>@color/settings_mode_color</item> <item>@color/camera_mode_color</item> </array> @@ -622,7 +628,6 @@ <item>@string/mode_photosphere</item> <item>@string/mode_panorama</item> <item>@string/mode_timelapse</item> - <item>@string/mode_settings</item> <item>""</item> </string-array> @@ -633,7 +638,6 @@ <item>@string/mode_photosphere_desc</item> <item>@string/mode_panorama_desc</item> <item>@string/mode_timelapse_desc</item> - <item>@string/mode_settings_desc</item> <item>""</item> </string-array> @@ -644,7 +648,6 @@ <item>@drawable/ic_photo_sphere_normal</item> <item>@drawable/ic_panorama_normal</item> <item>@drawable/ic_timelapse_normal</item> - <item>@drawable/ic_settings_normal</item> <item>@drawable/ic_camera_normal</item> </array> @@ -655,7 +658,6 @@ <item>@null</item> <item>@drawable/ic_panorama_normal</item> <item>@null</item> - <item>@null</item> <item>@drawable/ic_camera_normal</item> </array> diff --git a/res/values/colors.xml b/res/values/colors.xml index 08ebedb75..c8b2abee4 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -71,10 +71,11 @@ <color name="gray">#FFAAAAAA</color> <!-- Camera mode switcher --> - <color name="mode_selector_text_color">#6d6d6d</color> + <color name="mode_selector_text_color">#fff</color> <color name="mode_selector_background_light">#f5f5f5</color> <color name="mode_selector_background_dark">#e7e7e7</color> <color name="mode_selector_text_highlight_color">#ffffffff</color> + <color name="mode_selector_icon_background">#4c000000</color> <color name="mode_list_background">#00000000</color> <color name="camera_mode_color">#76a7fa</color> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index b3a2becde..f3e5b011e 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -154,8 +154,9 @@ <dimen name="photoeditor_original_text_margin">4dp</dimen> <!-- Mode selector icon width --> - <dimen name="mode_selector_icon_block_width">84dp</dimen> + <dimen name="mode_selector_icon_block_width">48dp</dimen> <dimen name="mode_selector_icon_drawable_size">32dp</dimen> + <dimen name="mode_selector_item_height">60dp</dimen> <!-- Filmstrip bottom controls --> <dimen name="filmstrip_bottom_control_size">48dp</dimen> diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index bf5172df7..b900aa764 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -1466,12 +1466,6 @@ public class CameraActivity extends Activity return; } - int settingsIndex = getResources().getInteger(R.integer.camera_mode_setting); - if (modeIndex == settingsIndex) { - onSettingsSelected(); - return; - } - CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START); // Record last used camera mode for quick switching if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo) diff --git a/src/com/android/camera/TextureViewHelper.java b/src/com/android/camera/TextureViewHelper.java index 2e1f5b3e5..80acbadd9 100644 --- a/src/com/android/camera/TextureViewHelper.java +++ b/src/com/android/camera/TextureViewHelper.java @@ -197,6 +197,14 @@ public class TextureViewHelper implements TextureView.SurfaceTextureListener, } /** + * Returns a new copy of the preview area, to avoid internal data being modified + * from outside of the class. + */ + public RectF getPreviewArea() { + return new RectF(mPreviewArea); + } + + /** * Adds a listener that will get notified when the preview size changed. This * can be useful for UI elements or focus overlay to adjust themselves according * to the preview size change. diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java index 9848ae9a9..1438da334 100644 --- a/src/com/android/camera/app/CameraAppUI.java +++ b/src/com/android/camera/app/CameraAppUI.java @@ -17,7 +17,10 @@ package com.android.camera.app; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.display.DisplayManager; import android.util.Log; @@ -427,6 +430,56 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener, } }; + /** + * Provides current preview frame and the controls/overlay from the module that + * are shown on top of the preview. + */ + public interface CameraModuleScreenShotProvider { + /** + * Returns the current preview frame down-sampled using the given down-sample + * factor. + * + * @param downSampleFactor the down sample factor for down sampling the + * preview frame. (e.g. a down sample factor of + * 2 means to scale down the preview frame to 1/2 + * the width and height.) + * @return down-sampled preview frame + */ + public Bitmap getPreviewFrame(int downSampleFactor); + + /** + * @return the controls and overlays that are currently showing on top of + * the preview drawn into a bitmap with no scaling applied. + */ + public Bitmap getPreviewOverlayAndControls(); + } + + private final CameraModuleScreenShotProvider mCameraModuleScreenShotProvider = + new CameraModuleScreenShotProvider() { + @Override + public Bitmap getPreviewFrame(int downSampleFactor) { + if (mCameraRootView == null || mTextureView == null) { + return null; + } + + RectF previewArea = mTextureViewHelper.getPreviewArea(); + // Gets the bitmap from the preview TextureView. + Bitmap preview = mTextureView.getBitmap( + (int) previewArea.width() / downSampleFactor, + (int) previewArea.height() / downSampleFactor); + return preview; + } + + @Override + public Bitmap getPreviewOverlayAndControls() { + Bitmap overlays = Bitmap.createBitmap(mCameraRootView.getWidth(), + mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(overlays); + mCameraRootView.draw(canvas); + return overlays; + } + }; + private long mCoverHiddenTime = -1; // System time when preview cover was hidden. public long getCoverHiddenTime() { @@ -544,6 +597,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener, if (mModeListView != null) { mModeListView.setModeSwitchListener(this); mModeListView.setModeListOpenListener(this); + mModeListView.setCameraModuleScreenShotProvider(mCameraModuleScreenShotProvider); } else { Log.e(TAG, "Cannot find mode list in the view hierarchy"); } @@ -865,6 +919,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener, mCameraRootView.findViewById(R.id.capture_overlay); mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay); mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay); + mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView); if (mIndicatorIconController == null) { mIndicatorIconController = @@ -961,6 +1016,11 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener, } } + @Override + public int getCurrentModeIndex() { + return mController.getCurrentModuleIndex(); + } + /********************** Capture animation **********************/ /* TODO: This session is subject to UX changes. In addition to the generic flash animation and post capture animation, consider designating a parameter diff --git a/src/com/android/camera/ui/ModeIconView.java b/src/com/android/camera/ui/ModeIconView.java new file mode 100644 index 000000000..d70e095f9 --- /dev/null +++ b/src/com/android/camera/ui/ModeIconView.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014 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.camera.ui; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.GradientDrawable; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.android.camera2.R; + +/** + * This class encapsulates the logic of drawing different states of the icon in + * mode drawer for when it is highlighted (to indicate the current module), or when + * it is selected by the user. It handles the internal state change like a state + * list drawable. The advantage over a state list drawable is that in the class + * multiple states can be rendered using the same drawable with some color modification, + * whereas a state list drawable would require a different drawable for each state. + */ +public class ModeIconView extends ImageView { + + private boolean mHighlightIsOn = false; + private final GradientDrawable mBackground; + private final GradientDrawable mHighlightDrawable; + private final int mIconBackgroundSize; + private int mHighlightColor; + private final int mBackgroundDefaultColor; + + public ModeIconView(Context context, AttributeSet attrs) { + super(context, attrs); + mBackgroundDefaultColor = getResources().getColor(R.color.mode_selector_icon_background); + mIconBackgroundSize = getResources().getDimensionPixelSize( + R.dimen.mode_selector_icon_block_width); + mBackground = (GradientDrawable) getResources() + .getDrawable(R.drawable.mode_icon_background).mutate(); + mBackground.setBounds(0, 0, mIconBackgroundSize, mIconBackgroundSize); + mHighlightDrawable = (GradientDrawable) getResources() + .getDrawable(R.drawable.mode_icon_highlight).mutate(); + mHighlightDrawable.setBounds(0, 0, mIconBackgroundSize, mIconBackgroundSize); + } + + @Override + public void draw(Canvas canvas) { + mBackground.draw(canvas); + if (mHighlightIsOn) { + mHighlightDrawable.draw(canvas); + } + super.draw(canvas); + } + + /** + * This gets called when the selected state is changed. When selected, the background + * drawable will use a solid pre-defined color to indicate selection. + * + * @param selected true when selected, false otherwise. + */ + public void setSelected(boolean selected) { + if (selected) { + mBackground.setColor(mHighlightColor); + mHighlightIsOn = false; + } else { + mBackground.setColor(mBackgroundDefaultColor); + } + invalidate(); + } + + /** + * This gets called when the highlighted state is changed. When highlighted, + * a ring shaped drawable of a solid pre-defined color will be drawn on top + * of the background drawable to indicate highlight state. + * + * @param highlighted true when highlighted, false otherwise. + */ + public void setHighlighted(boolean highlighted) { + mHighlightIsOn = highlighted; + invalidate(); + } + + /** + * Sets the color that will be used in the drawable for highlight state. + * + * @param highlightColor color for the highlight state + */ + public void setHighlightColor(int highlightColor) { + mHighlightColor = highlightColor; + mHighlightDrawable.setColor(highlightColor); + } +} diff --git a/src/com/android/camera/ui/ModeListView.java b/src/com/android/camera/ui/ModeListView.java index 939c64303..0fce66df8 100644 --- a/src/com/android/camera/ui/ModeListView.java +++ b/src/com/android/camera/ui/ModeListView.java @@ -23,10 +23,13 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; +import android.os.AsyncTask; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; @@ -34,9 +37,11 @@ import android.util.SparseArray; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.ScrollView; +import com.android.camera.app.CameraAppUI; import com.android.camera.util.CameraUtil; import com.android.camera.util.Gusterpolator; import com.android.camera.widget.AnimationEffects; @@ -52,7 +57,8 @@ import java.util.List; * any of the items in the list will take the user to that corresponding mode * with an animation. To dismiss this list, simply swipe left or select a mode. */ -public class ModeListView extends ScrollView { +public class ModeListView extends FrameLayout + implements PreviewStatusListener.PreviewAreaSizeChangedListener { private static final String TAG = "ModeListView"; @@ -76,13 +82,25 @@ public class ModeListView extends ScrollView { private static final int MODE_SELECTED = 4; // Scrolling delay between non-focused item and focused item - private static final int DELAY_MS = 25; + private static final int DELAY_MS = 30; // If the fling velocity exceeds this threshold, snap to full screen at a constant // speed. Unit: pixel/ms. private static final float VELOCITY_THRESHOLD = 2f; + /** + * A factor to change the UI responsiveness on a scroll. + * e.g. A scroll factor of 0.5 means UI will move half as fast as the finger. + */ + private static final float SCROLL_FACTOR = 0.5f; + // 30% transparent black background. + private static final int BACKGROUND_TRANSPARENTCY = (int) (0.3f * 255); + private static final int PREVIEW_DOWN_SAMPLE_FACTOR = 4; + // Threshold, below which snap back will happen. + private static final float SNAP_BACK_THRESHOLD_RATIO = 0.33f; + private final GestureDetector mGestureDetector; private final int mIconBlockWidth; + private final RectF mPreviewArea = new RectF(); private int mListBackgroundColor; private LinearLayout mListView; @@ -93,6 +111,9 @@ public class ModeListView extends ScrollView { private int mFocusItem = NO_ITEM_SELECTED; private AnimationEffects mCurrentEffect; private ModeListOpenListener mModeListOpenListener; + private CameraAppUI.CameraModuleScreenShotProvider mScreenShotProvider = null; + private int[] mInputPixels; + private int[] mOutputPixels; // Width and height of this view. They get updated in onLayout() // Unit for width and height are pixels. @@ -134,8 +155,14 @@ public class ModeListView extends ScrollView { } }; + @Override + public void onPreviewAreaSizeChanged(RectF previewArea) { + mPreviewArea.set(previewArea); + } + public interface ModeSwitchListener { public void onModeSelected(int modeIndex); + public int getCurrentModeIndex(); } public interface ModeListOpenListener { @@ -231,7 +258,7 @@ public class ModeListView extends ScrollView { mState = SCROLLING; // Scroll based on the scrolling distance on the currently focused // item. - scroll(mFocusItem, distanceX, distanceY); + scroll(mFocusItem, distanceX * SCROLL_FACTOR, distanceY * SCROLL_FACTOR); return true; } @@ -241,11 +268,24 @@ public class ModeListView extends ScrollView { // Only allows tap to choose mode when the list is fully shown return false; } + + // Ignore the tap if it happens outside of the mode list linear layout. + int x = (int) ev.getX() - mListView.getLeft(); + int y = (int) ev.getY() - mListView.getTop(); + if (x < 0 || x > mListView.getWidth() || y < 0 || y > mListView.getHeight()) { + return false; + } + int index = getFocusItem(ev.getX(), ev.getY()); // Validate the selection if (index != NO_ITEM_SELECTED) { final int modeId = getModeIndex(index); - mModeSelectorItems[index].highlight(); + // Select the focused item. + mModeSelectorItems[index].setSelected(true); + // Un-highlight all the modes. + for (int i = 0; i < mModeSelectorItems.length; i++) { + mModeSelectorItems[i].setHighlighted(false); + } mState = MODE_SELECTED; PeepholeAnimationEffect effect = new PeepholeAnimationEffect(); effect.setSize(mWidth, mHeight); @@ -257,8 +297,28 @@ public class ModeListView extends ScrollView { snapBack(false); } }); - effect.setAnimationStartingPosition((int) ev.getX(), (int) ev.getY()); + + // Calculate the position of the icon in the selected item, and + // start animation from that position. + int[] location = new int[2]; + // Gets icon's center position in relative to the window. + mModeSelectorItems[index].getIconCenterLocationInWindow(location); + int iconX = location[0]; + int iconY = location[1]; + // Gets current view's top left position relative to the window. + getLocationInWindow(location); + // Calculate icon location relative to this view + iconX -= location[0]; + iconY -= location[1]; + + effect.setAnimationStartingPosition(iconX, iconY); + if (mScreenShotProvider != null) { + effect.setBackground(mScreenShotProvider + .getPreviewFrame(PREVIEW_DOWN_SAMPLE_FACTOR), mPreviewArea); + effect.setBackgroundOverlay(mScreenShotProvider.getPreviewOverlayAndControls()); + } mCurrentEffect = effect; + invalidate(); // Post mode selection runnable to the end of the message queue // so that current UI changes can finish before mode initialization @@ -276,7 +336,7 @@ public class ModeListView extends ScrollView { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // Cache velocity in the unit pixel/ms. - mVelocityX = velocityX / 1000f; + mVelocityX = velocityX / 1000f * SCROLL_FACTOR; return true; } }; @@ -302,7 +362,7 @@ public class ModeListView extends ScrollView { mListBackgroundColor = mListBackgroundColor & 0xFFFFFF; mListBackgroundColor = mListBackgroundColor | (alpha << 24); // Set new color to list background. - mListView.setBackgroundColor(mListBackgroundColor); + setBackgroundColor(mListBackgroundColor); } /** @@ -341,6 +401,15 @@ public class ModeListView extends ScrollView { initializeModeSelectorItems(); } + /** + * Sets the screen shot provider for getting a preview frame and a bitmap + * of the controls and overlay. + */ + public void setCameraModuleScreenShotProvider( + CameraAppUI.CameraModuleScreenShotProvider provider) { + mScreenShotProvider = provider; + } + private void initializeModeSelectorItems() { mModeSelectorItems = new ModeSelectorItem[mTotalModes]; // Inflate the mode selector items and add them to a linear layout @@ -351,16 +420,9 @@ public class ModeListView extends ScrollView { ModeSelectorItem selectorItem = (ModeSelectorItem) inflater.inflate(R.layout.mode_selector, null); mListView.addView(selectorItem); - // Set alternating background color for each mode selector in the list - if (i % 2 == 0) { - selectorItem.setDefaultBackgroundColor(getResources() - .getColor(R.color.mode_selector_background_light)); - } else { - selectorItem.setDefaultBackgroundColor(getResources() - .getColor(R.color.mode_selector_background_dark)); - } + int modeId = getModeIndex(i); - selectorItem.setIconBackgroundColor(getResources() + selectorItem.setHighlightColor(getResources() .getColor(CameraUtil.getCameraThemeColorId(modeId, getContext()))); // Set image @@ -491,26 +553,8 @@ public class ModeListView extends ScrollView { */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - float height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - - getPaddingBottom(); - - Configuration config = getResources().getConfiguration(); - if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { - height = height / ROWS_TO_SHOW_IN_LANDSCAPE; - setVerticalScrollBarEnabled(true); - } else { - height = height / mTotalModes; - setVerticalScrollBarEnabled(false); - } - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mWidth, 0); - lp.width = LayoutParams.MATCH_PARENT; - for (int i = 0; i < mTotalModes; i++) { - // This is to avoid rounding that would cause the total height of the - // list a few pixels off the height of the screen. - int itemHeight = (int) (height * (i + 1)) - (int) (height * i); - lp.height = itemHeight; - mModeSelectorItems[i].setLayoutParams(lp); - } + centerModeDrawerInPreview(MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec)); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @@ -562,7 +606,6 @@ public class ModeListView extends ScrollView { private void resetModeSelectors() { for (int i = 0; i < mModeSelectorItems.length; i++) { mModeSelectorItems[i].setVisibleWidth(0); - mModeSelectorItems[i].unHighlight(); } // Visible width has been changed to 0 onVisibleWidthChanged(0); @@ -574,22 +617,68 @@ public class ModeListView extends ScrollView { /** * Calculate the mode selector item in the list that is at position (x, y). + * If the position is above the top item or below the bottom item, return + * the top item or bottom item respectively. * * @param x horizontal position * @param y vertical position * @return index of the item that is at position (x, y) */ private int getFocusItem(float x, float y) { - // Take into account the scrolling offset - x += getScrollX(); - y += getScrollY(); + // Convert coordinates into child view's coordinates. + x -= mListView.getLeft(); + y -= mListView.getTop(); for (int i = 0; i < mModeSelectorItems.length; i++) { - if (mModeSelectorItems[i].getTop() <= y && mModeSelectorItems[i].getBottom() >= y) { + if (y <= mModeSelectorItems[i].getBottom()) { return i; } } - return NO_ITEM_SELECTED; + return mModeSelectorItems.length - 1; + } + + @Override + public void onVisibilityChanged(View v, int visibility) { + super.onVisibilityChanged(v, visibility); + if (visibility == VISIBLE) { + centerModeDrawerInPreview(getMeasuredWidth(), getMeasuredHeight()); + // Highlight current module + if (mModeSwitchListener != null) { + int modeId = mModeSwitchListener.getCurrentModeIndex(); + int parentMode = CameraUtil.getCameraModeParentModeId(modeId, getContext()); + // Find parent mode in the nav drawer. + for (int i = 0; i < mSupportedModes.size(); i++) { + if (mSupportedModes.get(i) == parentMode) { + mModeSelectorItems[i].setHighlighted(true); + } + } + } + } else if (mModeSelectorItems != null) { + // When becoming invisible/gone after initializing mode selector items. + for (int i = 0; i < mModeSelectorItems.length; i++) { + mModeSelectorItems[i].setHighlighted(false); + mModeSelectorItems[i].setSelected(false); + } + } + } + + /** + * Center mode drawer in camera preview. + */ + private void centerModeDrawerInPreview(int measuredWidth, int measuredHeight) { + + // Assuming the preview is centered in the space aside from bottom bar. + float previewAreaWidth = mPreviewArea.right + mPreviewArea.left; + float previewAreaHeight = mPreviewArea.top + mPreviewArea.bottom; + if (measuredWidth > measuredHeight) { + // Landscape. + int previewWidth = (int) Math.max(previewAreaHeight, previewAreaWidth); + setPadding(0, 0, measuredWidth - previewWidth, 0); + } else { + // Portrait. + int previewHeight = (int) Math.max(previewAreaHeight, previewAreaWidth); + setPadding(0, 0, 0, measuredHeight - previewHeight); + } } private void scroll(int itemId, float deltaX, float deltaY) { @@ -712,17 +801,11 @@ public class ModeListView extends ScrollView { * to darken/lighten correspondingly. */ private void onVisibleWidthChanged(int focusItemWidth) { - // Background alpha should be 0 before the icon block is entirely visible, - // and when the longest mode item is entirely shown (across the screen), the + // When the longest mode item is entirely shown (across the screen), the // background should be 50% transparent. - if (focusItemWidth <= mIconBlockWidth) { - setBackgroundAlpha(0); - } else { - // Alpha should increase linearly when mode item goes beyond the icon block - // till it reaches its max width - int alpha = 127 * (focusItemWidth - mIconBlockWidth) / (mWidth - mIconBlockWidth); - setBackgroundAlpha(alpha); - } + int maxVisibleWidth = mModeSelectorItems[0].getMaxVisibleWidth(); + focusItemWidth = Math.min(maxVisibleWidth, focusItemWidth); + setBackgroundAlpha(BACKGROUND_TRANSPARENTCY * focusItemWidth / maxVisibleWidth); } @Override @@ -744,7 +827,8 @@ public class ModeListView extends ScrollView { private void snap() { if (mState == SCROLLING) { int itemId = Math.max(0, mFocusItem); - if (mModeSelectorItems[itemId].getVisibleWidth() < mIconBlockWidth) { + if (mModeSelectorItems[itemId].getVisibleWidth() + < mModeSelectorItems[itemId].getMaxVisibleWidth() * SNAP_BACK_THRESHOLD_RATIO) { snapBack(); } else if (Math.abs(mScrollTrendX) > Math.abs(mScrollTrendY) && mScrollTrendX > 0) { snapBack(); @@ -761,7 +845,11 @@ public class ModeListView extends ScrollView { */ public void snapBack(boolean withAnimation) { if (withAnimation) { - animateListToWidth(0); + if (mVelocityX > -VELOCITY_THRESHOLD * SCROLL_FACTOR) { + animateListToWidth(0); + } else { + animateListToWidthAtVelocity(mVelocityX, 0); + } mState = IDLE; } else { setVisibility(INVISIBLE); @@ -778,12 +866,14 @@ public class ModeListView extends ScrollView { } private void snapToFullScreen() { - if (mVelocityX <= VELOCITY_THRESHOLD) { - animateListToWidth(mWidth); + int focusItem = mFocusItem == NO_ITEM_SELECTED ? 0 : mFocusItem; + int fullWidth = mModeSelectorItems[focusItem].getMaxVisibleWidth(); + if (mVelocityX <= VELOCITY_THRESHOLD * SCROLL_FACTOR) { + animateListToWidth(fullWidth); } else { // If the fling velocity exceeds this threshold, snap to full screen // at a constant speed. - animateListToWidthAtVelocity(mVelocityX, mWidth); + animateListToWidthAtVelocity(mVelocityX, fullWidth); } mState = FULLY_SHOWN; if (mModeListOpenListener != null) { @@ -905,17 +995,22 @@ public class ModeListView extends ScrollView { private class PeepholeAnimationEffect extends AnimationEffects { private final static int UNSET = -1; - private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 650; + private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 300; + + private final Paint mMaskPaint = new Paint(); + private final Paint mBackgroundPaint = new Paint(); + private final RectF mBackgroundDrawArea = new RectF(); private int mWidth; private int mHeight; - private int mPeepHoleCenterX = UNSET; private int mPeepHoleCenterY = UNSET; private float mRadius = 0f; private ValueAnimator mPeepHoleAnimator; private Runnable mEndAction; - private final Paint mMaskPaint = new Paint(); + private Bitmap mBackground; + private Bitmap mBlurredBackground; + private Bitmap mBackgroundOverlay; public PeepholeAnimationEffect() { mMaskPaint.setAlpha(0); @@ -942,6 +1037,62 @@ public class ModeListView extends ScrollView { mPeepHoleCenterY = y; } + /** + * Sets the bitmap to be drawn in the background and the drawArea to draw + * the bitmap. In the meantime, start processing the image in a background + * thread to get a blurred background image. + * + * @param background image to be drawn in the background + * @param drawArea area to draw the background image + */ + public void setBackground(Bitmap background, RectF drawArea) { + mBackground = background; + mBackgroundDrawArea.set(drawArea); + new BlurTask().execute(Bitmap.createScaledBitmap(background, background.getWidth(), + background.getHeight(), true)); + } + + /** + * Sets the overlay image to be drawn on top of the background. + */ + public void setBackgroundOverlay(Bitmap overlay) { + mBackgroundOverlay = overlay; + } + + /** + * This gets called when a blurred image of the background is generated. + * Start an animation to fade in the blur. + * + * @param blur blurred image of the background. + */ + public void setBlurredBackground(Bitmap blur) { + mBlurredBackground = blur; + // Start fade in. + ObjectAnimator alpha = ObjectAnimator.ofInt(mBackgroundPaint, "alpha", 80, 255); + alpha.setDuration(250); + alpha.setInterpolator(Gusterpolator.INSTANCE); + alpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + invalidate(); + } + }); + alpha.start(); + invalidate(); + } + + @Override + public void drawBackground(Canvas canvas) { + if (mBackground != null && mBackgroundOverlay != null) { + canvas.drawARGB(255, 0, 0, 0); + canvas.drawBitmap(mBackground, null, mBackgroundDrawArea, null); + if (mBlurredBackground != null) { + canvas.drawBitmap(mBlurredBackground, null, mBackgroundDrawArea, mBackgroundPaint); + } + canvas.drawBitmap(mBackgroundOverlay, 0, 0, null); + } + } + @Override public void startAnimation() { if (mPeepHoleAnimator != null && mPeepHoleAnimator.isRunning()) { @@ -956,6 +1107,8 @@ public class ModeListView extends ScrollView { int verticalDistanceToFarEdge = Math.max(mPeepHoleCenterY, mHeight - mPeepHoleCenterY); int endRadius = (int) (Math.sqrt(horizontalDistanceToFarEdge * horizontalDistanceToFarEdge + verticalDistanceToFarEdge * verticalDistanceToFarEdge)); + int startRadius = getResources().getDimensionPixelSize( + R.dimen.mode_selector_icon_block_width) / 2; mPeepHoleAnimator = ValueAnimator.ofFloat(0, endRadius); mPeepHoleAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS); @@ -1013,5 +1166,40 @@ public class ModeListView extends ScrollView { public void setAnimationEndAction(Runnable runnable) { mEndAction = runnable; } + + private class BlurTask extends AsyncTask<Bitmap, Integer, Bitmap> { + + // Gaussian blur mask size. + private static final int MASK_SIZE = 7; + @Override + protected Bitmap doInBackground(Bitmap... params) { + + Bitmap intermediateBitmap = params[0]; + int factor = 4; + Bitmap lowResPreview = Bitmap.createScaledBitmap(intermediateBitmap, + intermediateBitmap.getWidth() / factor, + intermediateBitmap.getHeight() / factor, true); + + int width = lowResPreview.getWidth(); + int height = lowResPreview.getHeight(); + + if (mInputPixels == null || mInputPixels.length < width * height) { + mInputPixels = new int[width * height]; + mOutputPixels = new int[width * height]; + } + lowResPreview.getPixels(mInputPixels, 0, width, 0, 0, width, height); + CameraUtil.blur(mInputPixels, mOutputPixels, width, height, MASK_SIZE); + lowResPreview.setPixels(mOutputPixels, 0, width, 0, 0, width, height); + + intermediateBitmap.recycle(); + return Bitmap.createScaledBitmap(lowResPreview, width * factor, + height * factor, true); + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + setBlurredBackground(bitmap); + } + }; } } diff --git a/src/com/android/camera/ui/ModeSelectorItem.java b/src/com/android/camera/ui/ModeSelectorItem.java index 44c567cbd..836c3d941 100644 --- a/src/com/android/camera/ui/ModeSelectorItem.java +++ b/src/com/android/camera/ui/ModeSelectorItem.java @@ -17,14 +17,11 @@ package com.android.camera.ui; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Typeface; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; import android.util.AttributeSet; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.TextView; import com.android.camera.util.ApiHelper; @@ -39,41 +36,39 @@ import com.android.camera2.R; * and a GradientDrawable at the end of the TextView. * * The purpose of this class is to encapsulate different drawing logic into - * its own class. There are two drawing mode, <code>TRUNCATE_TEXT_END</code> - * and <code>TRUNCATE_TEXT_FRONT</code>. They define how we draw the view when + * its own class. There are two drawing mode, <code>FLY_IN</code> + * and <code>FLY_OUT</code>. They define how we draw the view when * we display the view partially. */ class ModeSelectorItem extends FrameLayout { // Drawing modes that defines how the TextView should be drawn when there // is not enough space to draw the whole TextView. - public static final int TRUNCATE_TEXT_END = 1; - public static final int TRUNCATE_TEXT_FRONT = 2; + public static final int FLY_IN = 1; + public static final int FLY_OUT = 2; private static final int SHADE_WIDTH_PIX = 100; private TextView mText; - private ImageView mIcon; + private ModeIconView mIcon; private int mVisibleWidth; - private int mMinVisibleWidth; - private GradientDrawable mGradientShade; + private final int mMinVisibleWidth; - private int mDrawingMode = TRUNCATE_TEXT_END; + private int mDrawingMode = FLY_IN; private int mHeight; private int mWidth; private int mDefaultBackgroundColor; private int mDefaultTextColor; - private int mIconBlockColor; - private final int mHighlightTextColor; public ModeSelectorItem(Context context, AttributeSet attrs) { super(context, attrs); - mHighlightTextColor = context.getResources() - .getColor(R.color.mode_selector_text_highlight_color); + setWillNotDraw(false); + mMinVisibleWidth = getResources() + .getDimensionPixelSize(R.dimen.mode_selector_icon_block_width); } @Override public void onFinishInflate() { - mIcon = (ImageView) findViewById(R.id.selector_icon); + mIcon = (ModeIconView) findViewById(R.id.selector_icon); mText = (TextView) findViewById(R.id.selector_text); Typeface typeface; if (ApiHelper.HAS_ROBOTO_LIGHT_FONT) { @@ -84,8 +79,6 @@ class ModeSelectorItem extends FrameLayout { "Roboto-Light.ttf"); } mText.setTypeface(typeface); - mMinVisibleWidth = getResources() - .getDimensionPixelSize(R.dimen.mode_selector_icon_block_width); mDefaultTextColor = mText.getCurrentTextColor(); } @@ -94,23 +87,12 @@ class ModeSelectorItem extends FrameLayout { setBackgroundColor(color); } - @Override - public void setBackgroundColor(int color) { - super.setBackgroundColor(color); - int startColor = 0x00FFFFFF & color; - // Gradient shade will draw at the end of the item - mGradientShade = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, - new int[] {startColor, color}); - } - - public void highlight() { - mText.setTextColor(mHighlightTextColor); - setBackgroundColor(mIconBlockColor); + public void setHighlighted(boolean highlighted) { + mIcon.setHighlighted(highlighted); } - public void unHighlight() { - setBackgroundColor(mDefaultBackgroundColor); - mText.setTextColor(mDefaultTextColor); + public void setSelected(boolean selected) { + mIcon.setSelected(selected); } /** @@ -122,15 +104,10 @@ class ModeSelectorItem extends FrameLayout { * to right) */ public void onSwipeModeChanged(boolean swipeIn) { - mDrawingMode = swipeIn ? TRUNCATE_TEXT_END : TRUNCATE_TEXT_FRONT; + mDrawingMode = swipeIn ? FLY_IN : FLY_OUT; mText.setTranslationX(0); } - public void setIconBackgroundColor(int color) { - mIconBlockColor = color; - mIcon.setBackgroundColor(color); - } - public void setText(CharSequence text) { mText.setText(text); } @@ -143,7 +120,7 @@ class ModeSelectorItem extends FrameLayout { if (changed && mVisibleWidth > 0) { // Reset mode list to full screen setVisibleWidth(mWidth); - mDrawingMode = TRUNCATE_TEXT_FRONT; + mDrawingMode = FLY_OUT; } } @@ -174,16 +151,34 @@ class ModeSelectorItem extends FrameLayout { * @param newWidth new visible width */ public void setVisibleWidth(int newWidth) { + int fullyShownIconWidth = getMaxVisibleWidth(); newWidth = Math.max(newWidth, 0); // Visible width should not be greater than view width - newWidth = Math.min(newWidth, mWidth); + newWidth = Math.min(newWidth, fullyShownIconWidth); mVisibleWidth = newWidth; float transX = 0f; // If the given width is less than the icon width, we need to translate icon - if (mVisibleWidth < mMinVisibleWidth) { - transX = mMinVisibleWidth - mVisibleWidth; + if (mVisibleWidth < mMinVisibleWidth + mIcon.getLeft()) { + transX = mMinVisibleWidth + mIcon.getLeft() - mVisibleWidth; } setTranslationX(-transX); + + if (mDrawingMode == FLY_IN) { + // Swipe open. + int width = Math.min(mVisibleWidth, fullyShownIconWidth); + // Linear interpolate text opacity. + float alpha = (float) width / (float) fullyShownIconWidth; + mText.setAlpha(alpha); + } else { + // Swipe back. + int width = Math.max(mVisibleWidth, mMinVisibleWidth / 2); + width = Math.min(width, fullyShownIconWidth); + // Linear interpolate text opacity. + float alpha = (float) (width - mMinVisibleWidth / 2) + / (float) (fullyShownIconWidth - mMinVisibleWidth); + mText.setAlpha(alpha); + } + invalidate(); } @@ -204,27 +199,34 @@ class ModeSelectorItem extends FrameLayout { */ @Override public void draw(Canvas canvas) { - int width = Math.max(mVisibleWidth, mMinVisibleWidth); - int height = canvas.getHeight(); - int shadeStart = -1; - - if (mDrawingMode == TRUNCATE_TEXT_END) { - if (mVisibleWidth > mMinVisibleWidth) { - shadeStart = Math.max(mMinVisibleWidth, mVisibleWidth - SHADE_WIDTH_PIX); - } - } else { - if (mVisibleWidth <= mWidth) { - mText.setTranslationX(mVisibleWidth - mWidth); - } - } - - if (width < mWidth) { - canvas.clipRect(0, 0, width, height); - } super.draw(canvas); - if (shadeStart > 0 && width < mWidth) { - mGradientShade.setBounds(shadeStart, 0, width, height); - mGradientShade.draw(canvas); - } + } + + /** + * Sets the color that will be used in the drawable for highlight state. + * + * @param highlightColor color for the highlight state + */ + public void setHighlightColor(int highlightColor) { + mIcon.setHighlightColor(highlightColor); + } + + /** + * Gets the maximum visible width of the mode icon. The mode item will be + * full shown when the mode icon has max visible width. + */ + public int getMaxVisibleWidth() { + return mIcon.getLeft() + mMinVisibleWidth; + } + + /** + * Gets the position of the icon center relative to the window. + * + * @param loc integer array of size 2, to hold the position x and y + */ + public void getIconCenterLocationInWindow(int[] loc) { + mIcon.getLocationInWindow(loc); + loc[0] += mMinVisibleWidth / 2; + loc[1] += mMinVisibleWidth / 2; } } diff --git a/src/com/android/camera/ui/ModeTransitionView.java b/src/com/android/camera/ui/ModeTransitionView.java index b5a2c4223..0d0f46520 100644 --- a/src/com/android/camera/ui/ModeTransitionView.java +++ b/src/com/android/camera/ui/ModeTransitionView.java @@ -46,7 +46,7 @@ import com.android.camera2.R; public class ModeTransitionView extends View { private static final String TAG = "ModeTransitionView"; - private static final int PEEP_HOLE_ANIMATION_DURATION_MS = 550; + private static final int PEEP_HOLE_ANIMATION_DURATION_MS = 300; private static final int ICON_FADE_OUT_DURATION_MS = 850; private static final int FADE_OUT_DURATION_MS = 100; diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java index fc324af1b..707a3e268 100644 --- a/src/com/android/camera/util/CameraUtil.java +++ b/src/com/android/camera/util/CameraUtil.java @@ -41,6 +41,7 @@ import android.net.Uri; import android.os.ParcelFileDescriptor; import android.telephony.TelephonyManager; import android.util.DisplayMetrics; +import android.util.FloatMath; import android.util.Log; import android.util.TypedValue; import android.view.Display; @@ -903,6 +904,104 @@ public class CameraUtil { } } + /** + * Generates a 1d Gaussian mask of the input array size, and store the mask + * in the input array. + * + * @param mask empty array of size n, where n will be used as the size of the + * Gaussian mask, and the array will be populated with the values + * of the mask. + */ + private static void getGaussianMask(float[] mask) { + int len = mask.length; + int mid = len / 2; + float sigma = len; + float sum = 0; + for (int i = 0; i <= mid; i++) { + float ex = FloatMath.exp(-(i - mid) * (i - mid) / (mid * mid)) + / (2 * sigma * sigma); + int symmetricIndex = len - 1 - i; + mask[i] = ex; + mask[symmetricIndex] = ex; + sum += mask[i]; + if (i != symmetricIndex) { + sum += mask[symmetricIndex]; + } + } + + for (int i = 0; i < mask.length; i++) { + mask[i] /= sum; + } + + } + + /** + * Add two pixels together where the second pixel will be applied with a weight. + * + * @param pixel pixel color value of weight 1 + * @param newPixel second pixel color value where the weight will be applied + * @param weight a float weight that will be applied to the second pixel color + * @return the weighted addition of the two pixels + */ + public static int addPixel(int pixel, int newPixel, float weight) { + // TODO: scale weight to [0, 1024] to avoid casting to float and back to int. + int r = ((pixel & 0x00ff0000) + (int) (((float) (newPixel & 0x00ff0000)) * weight)) & 0x00ff0000; + int g = ((pixel & 0x0000ff00) + (int) (((float) (newPixel & 0x0000ff00)) * weight)) & 0x0000ff00; + int b = ((pixel & 0x000000ff) + (int) (((float) (newPixel & 0x000000ff)) * weight)) & 0x000000ff; + return 0xff000000 | r | g | b; + } + + /** + * Apply blur to the input image represented in an array of colors and put the + * output image, in the form of an array of colors, into the output array. + * + * @param src source array of colors + * @param out output array of colors after the blur + * @param w width of the image + * @param h height of the image + * @param size size of the Gaussian blur mask + */ + public static void blur(int[] src, int[] out, int w, int h, int size) { + float[] k = new float[size]; + int off = size / 2; + + getGaussianMask(k); + + int[] tmp = new int[src.length]; + + // Apply the 1d Gaussian mask horizontally to the image and put the intermediate + // results in a temporary array. + int rowPointer = 0; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int sum = 0; + for (int i = 0; i < k.length; i++) { + int dx = x + i - off; + dx = clamp(dx, 0, w - 1); + sum = addPixel(sum, src[rowPointer + dx], k[i]); + } + tmp[x + rowPointer] = sum; + } + rowPointer += w; + } + + // Apply the 1d Gaussian mask vertically to the intermediate array, and + // the final results will be stored in the output array. + for (int x = 0; x < w; x++) { + rowPointer = 0; + for (int y = 0; y < h; y++) { + int sum = 0; + for (int i = 0; i < k.length; i++) { + int dy = y + i - off; + dy = clamp(dy, 0, h - 1); + sum = addPixel(sum, tmp[dy * w + x], k[i]); + } + out[x + rowPointer] = sum; + rowPointer += w; + } + } + } + private static class ImageFileNamer { private final SimpleDateFormat mFormat; @@ -1094,4 +1193,22 @@ public class CameraUtil { } return shutterIcons.getResourceId(modeIndex, 0); } + + /** + * Gets the parent mode that hosts a specific mode in nav drawer. + * + * @param modeIndex index of the mode + * @param context current context + * @return mode id if the index is valid, otherwise 0 + */ + public static int getCameraModeParentModeId(int modeIndex, Context context) { + // Find the camera mode icon using id + int[] cameraModeParent = context.getResources() + .getIntArray(R.array.camera_mode_nested_in_nav_drawer); + if (modeIndex < 0 || modeIndex >= cameraModeParent.length) { + Log.e(TAG, "Invalid mode index: " + modeIndex); + return 0; + } + return cameraModeParent[modeIndex]; + } } |