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
diff --git a/res/drawable/mode_icon_background.xml b/res/drawable/mode_icon_background.xml
new file mode 100644
index 0000000..305216c
--- /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 0000000..f9e3d72
--- /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 24ddf95..6925acc 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 c9d9375..939ed07 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 16b831b..3f48450 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 08ebedb..c8b2abe 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 b3a2bec..f3e5b01 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 bf5172d..b900aa7 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -1466,12 +1466,6 @@
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 2e1f5b3..80acbad 100644
--- a/src/com/android/camera/TextureViewHelper.java
+++ b/src/com/android/camera/TextureViewHelper.java
@@ -197,6 +197,14 @@
}
/**
+ * 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 9848ae9..1438da3 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 @@
}
};
+ /**
+ * 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 @@
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 @@
mCameraRootView.findViewById(R.id.capture_overlay);
mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
+ mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView);
if (mIndicatorIconController == null) {
mIndicatorIconController =
@@ -961,6 +1016,11 @@
}
}
+ @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 0000000..d70e095
--- /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 939c643..0fce66d 100644
--- a/src/com/android/camera/ui/ModeListView.java
+++ b/src/com/android/camera/ui/ModeListView.java
@@ -23,10 +23,13 @@
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.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 @@
* 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 @@
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 @@
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 @@
}
};
+ @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 @@
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 @@
// 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 @@
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 @@
@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 @@
mListBackgroundColor = mListBackgroundColor & 0xFFFFFF;
mListBackgroundColor = mListBackgroundColor | (alpha << 24);
// Set new color to list background.
- mListView.setBackgroundColor(mListBackgroundColor);
+ setBackgroundColor(mListBackgroundColor);
}
/**
@@ -341,6 +401,15 @@
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 @@
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 @@
*/
@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 @@
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 @@
/**
* 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 @@
* 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 @@
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 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 @@
}
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 @@
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 @@
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 @@
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 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 44c567c..836c3d9 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 @@
* 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 @@
"Roboto-Light.ttf");
}
mText.setTypeface(typeface);
- mMinVisibleWidth = getResources()
- .getDimensionPixelSize(R.dimen.mode_selector_icon_block_width);
mDefaultTextColor = mText.getCurrentTextColor();
}
@@ -94,23 +87,12 @@
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 setHighlighted(boolean highlighted) {
+ mIcon.setHighlighted(highlighted);
}
- public void highlight() {
- mText.setTextColor(mHighlightTextColor);
- setBackgroundColor(mIconBlockColor);
- }
-
- public void unHighlight() {
- setBackgroundColor(mDefaultBackgroundColor);
- mText.setTextColor(mDefaultTextColor);
+ public void setSelected(boolean selected) {
+ mIcon.setSelected(selected);
}
/**
@@ -122,15 +104,10 @@
* 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 @@
if (changed && mVisibleWidth > 0) {
// Reset mode list to full screen
setVisibleWidth(mWidth);
- mDrawingMode = TRUNCATE_TEXT_FRONT;
+ mDrawingMode = FLY_OUT;
}
}
@@ -174,16 +151,34 @@
* @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 @@
*/
@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 b5a2c42..0d0f465 100644
--- a/src/com/android/camera/ui/ModeTransitionView.java
+++ b/src/com/android/camera/ui/ModeTransitionView.java
@@ -46,7 +46,7 @@
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 fc324af..707a3e2 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -41,6 +41,7 @@
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 @@
}
}
+ /**
+ * 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 @@
}
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];
+ }
}