OSDN Git Service

Fix b/4643148: Make PhotoEditor integrated into Gallery.
authorYuli Huang <yuli@google.com>
Mon, 12 Sep 2011 14:25:30 +0000 (22:25 +0800)
committerYuli Huang <yuli@google.com>
Mon, 12 Sep 2011 16:36:00 +0000 (00:36 +0800)
1. Move PhotoEditor code/resources into Gallery for single apk.
2. Change PhotoEditor package to com.android.gallery3d.photoeditor.
3. Rename PhotoEditor resources to avoid mess up Gallery resources.
4. Move Doodle effect from fix-effects to color-effects.
5. Update PhotoEditor bottom action-bar background.

Change-Id: I1a2f7d27d89a14fe6a0435575b993ed8b75e6bf4

141 files changed:
AndroidManifest.xml
res/anim/photoeditor_fade_in.xml [new file with mode: 0644]
res/anim/photoeditor_fade_out.xml [new file with mode: 0644]
res/drawable/photoeditor_actionbar_translucent.9.png [new file with mode: 0644]
res/drawable/photoeditor_actionbar_translucent_bottom.9.png [new file with mode: 0644]
res/drawable/photoeditor_arrow_back.png [new file with mode: 0644]
res/drawable/photoeditor_artistic.png [new file with mode: 0644]
res/drawable/photoeditor_color.png [new file with mode: 0644]
res/drawable/photoeditor_effect_autofix.png [new file with mode: 0644]
res/drawable/photoeditor_effect_crop.png [new file with mode: 0644]
res/drawable/photoeditor_effect_crossprocess.png [new file with mode: 0644]
res/drawable/photoeditor_effect_documentary.png [new file with mode: 0644]
res/drawable/photoeditor_effect_doodle.png [new file with mode: 0644]
res/drawable/photoeditor_effect_duotone.png [new file with mode: 0644]
res/drawable/photoeditor_effect_filllight.png [new file with mode: 0644]
res/drawable/photoeditor_effect_fisheye.png [new file with mode: 0644]
res/drawable/photoeditor_effect_flip.png [new file with mode: 0644]
res/drawable/photoeditor_effect_grain.png [new file with mode: 0644]
res/drawable/photoeditor_effect_grayscale.png [new file with mode: 0644]
res/drawable/photoeditor_effect_highlight.png [new file with mode: 0644]
res/drawable/photoeditor_effect_lomoish.png [new file with mode: 0644]
res/drawable/photoeditor_effect_negative.png [new file with mode: 0644]
res/drawable/photoeditor_effect_posterize.png [new file with mode: 0644]
res/drawable/photoeditor_effect_redeye.png [new file with mode: 0644]
res/drawable/photoeditor_effect_rotate.png [new file with mode: 0644]
res/drawable/photoeditor_effect_saturation.png [new file with mode: 0644]
res/drawable/photoeditor_effect_sepia.png [new file with mode: 0644]
res/drawable/photoeditor_effect_shadow.png [new file with mode: 0644]
res/drawable/photoeditor_effect_sharpen.png [new file with mode: 0644]
res/drawable/photoeditor_effect_straighten.png [new file with mode: 0644]
res/drawable/photoeditor_effect_temperature.png [new file with mode: 0644]
res/drawable/photoeditor_effect_tint.png [new file with mode: 0644]
res/drawable/photoeditor_effect_vignette.png [new file with mode: 0644]
res/drawable/photoeditor_exposure.png [new file with mode: 0644]
res/drawable/photoeditor_fix.png [new file with mode: 0644]
res/drawable/photoeditor_redo.png [new file with mode: 0644]
res/drawable/photoeditor_scale_seekbar_color.9.png [new file with mode: 0644]
res/drawable/photoeditor_scale_seekbar_filllight.9.png [new file with mode: 0644]
res/drawable/photoeditor_scale_seekbar_generic.9.png [new file with mode: 0644]
res/drawable/photoeditor_scale_seekbar_highlight.9.png [new file with mode: 0644]
res/drawable/photoeditor_scale_seekbar_shadow.9.png [new file with mode: 0644]
res/drawable/photoeditor_seekbar_thumb.png [new file with mode: 0644]
res/drawable/photoeditor_toggle_button_background.xml [new file with mode: 0644]
res/drawable/photoeditor_toggle_button_highlight.9.png [new file with mode: 0644]
res/drawable/photoeditor_undo.png [new file with mode: 0644]
res/layout/photoeditor_color_seekbar.xml [new file with mode: 0644]
res/layout/photoeditor_crop_view.xml [new file with mode: 0644]
res/layout/photoeditor_doodle_view.xml [new file with mode: 0644]
res/layout/photoeditor_effect_tool_panel.xml [new file with mode: 0644]
res/layout/photoeditor_effects_artistic.xml [new file with mode: 0644]
res/layout/photoeditor_effects_color.xml [new file with mode: 0644]
res/layout/photoeditor_effects_exposure.xml [new file with mode: 0644]
res/layout/photoeditor_effects_fix.xml [new file with mode: 0644]
res/layout/photoeditor_effects_gallery.xml [new file with mode: 0644]
res/layout/photoeditor_main.xml [new file with mode: 0644]
res/layout/photoeditor_rotate_view.xml [new file with mode: 0644]
res/layout/photoeditor_scale_seekbar.xml [new file with mode: 0644]
res/layout/photoeditor_touch_view.xml [new file with mode: 0644]
res/values-sw320dp/photoeditor_dimens.xml [new file with mode: 0755]
res/values-sw600dp/photoeditor_dimens.xml [new file with mode: 0755]
res/values-sw800dp/photoeditor_dimens.xml [new file with mode: 0755]
res/values/photoeditor_arrays.xml [new file with mode: 0644]
res/values/photoeditor_colors.xml [new file with mode: 0644]
res/values/photoeditor_styles.xml [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/ActionBar.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/BitmapUtils.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/EffectsBar.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/FilterStack.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/LoadScreennailTask.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/OnDoneBitmapCallback.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/OnDoneCallback.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/Photo.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/PhotoEditor.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/PhotoView.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/RendererUtils.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/SaveCopyTask.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/SpinnerProgressDialog.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/Toolbar.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/ToolbarIdleHandler.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/AbstractSeekBar.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/ColorSeekBar.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/CropAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/CropView.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/DoodleAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/DoodlePaint.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/DoodleView.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/EffectAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/FillLightAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/FlipAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/FullscreenToolView.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/GrainAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/HighlightAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/LomoishAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/NegativeAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/RotateAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/RotateView.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/SaturationAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/ScaleSeekBar.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/SepiaAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/ShadowAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/SharpenAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/StraightenAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/TintAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/TouchView.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/actions/VignetteAction.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/AutoFixFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/ColorTemperatureFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/CropFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/FillLightFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/Filter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/FisheyeFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/FlipFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/GrainFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/HighlightFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/RotateFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/SaturationFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/ShadowFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/SharpenFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/TintFilter.java [new file with mode: 0644]
src/com/android/gallery3d/photoeditor/filters/VignetteFilter.java [new file with mode: 0644]

index a5a6421..5beb3bf 100644 (file)
                 <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
             </intent-filter>
         </activity>
+        <activity android:name="com.android.gallery3d.photoeditor.PhotoEditor"
+                android:label="@string/photoeditor_name"
+                android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen"
+                android:configChanges="keyboardHidden|orientation|screenSize"
+                android:hardwareAccelerated="true">
+            <intent-filter>
+                <action android:name="android.intent.action.EDIT" />
+                <data android:mimeType="image/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
 
         <activity android:name="com.android.gallery3d.app.SlideshowDream"
             android:label="@string/slideshow_dream_name"
diff --git a/res/anim/photoeditor_fade_in.xml b/res/anim/photoeditor_fade_in.xml
new file mode 100644 (file)
index 0000000..7334041
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="0.25"
+    android:toAlpha="1.0"
+    android:duration="500"
+    android:fillAfter="true"/>
diff --git a/res/anim/photoeditor_fade_out.xml b/res/anim/photoeditor_fade_out.xml
new file mode 100644 (file)
index 0000000..ad97c49
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="1.0"
+    android:toAlpha="0.25"
+    android:duration="500"
+    android:fillAfter="true"/>
diff --git a/res/drawable/photoeditor_actionbar_translucent.9.png b/res/drawable/photoeditor_actionbar_translucent.9.png
new file mode 100644 (file)
index 0000000..76775a3
Binary files /dev/null and b/res/drawable/photoeditor_actionbar_translucent.9.png differ
diff --git a/res/drawable/photoeditor_actionbar_translucent_bottom.9.png b/res/drawable/photoeditor_actionbar_translucent_bottom.9.png
new file mode 100644 (file)
index 0000000..97018f8
Binary files /dev/null and b/res/drawable/photoeditor_actionbar_translucent_bottom.9.png differ
diff --git a/res/drawable/photoeditor_arrow_back.png b/res/drawable/photoeditor_arrow_back.png
new file mode 100644 (file)
index 0000000..897a1c1
Binary files /dev/null and b/res/drawable/photoeditor_arrow_back.png differ
diff --git a/res/drawable/photoeditor_artistic.png b/res/drawable/photoeditor_artistic.png
new file mode 100644 (file)
index 0000000..c887fe4
Binary files /dev/null and b/res/drawable/photoeditor_artistic.png differ
diff --git a/res/drawable/photoeditor_color.png b/res/drawable/photoeditor_color.png
new file mode 100644 (file)
index 0000000..566773d
Binary files /dev/null and b/res/drawable/photoeditor_color.png differ
diff --git a/res/drawable/photoeditor_effect_autofix.png b/res/drawable/photoeditor_effect_autofix.png
new file mode 100644 (file)
index 0000000..f0e2d46
Binary files /dev/null and b/res/drawable/photoeditor_effect_autofix.png differ
diff --git a/res/drawable/photoeditor_effect_crop.png b/res/drawable/photoeditor_effect_crop.png
new file mode 100644 (file)
index 0000000..eb7da1b
Binary files /dev/null and b/res/drawable/photoeditor_effect_crop.png differ
diff --git a/res/drawable/photoeditor_effect_crossprocess.png b/res/drawable/photoeditor_effect_crossprocess.png
new file mode 100644 (file)
index 0000000..f9e596a
Binary files /dev/null and b/res/drawable/photoeditor_effect_crossprocess.png differ
diff --git a/res/drawable/photoeditor_effect_documentary.png b/res/drawable/photoeditor_effect_documentary.png
new file mode 100644 (file)
index 0000000..92c40f9
Binary files /dev/null and b/res/drawable/photoeditor_effect_documentary.png differ
diff --git a/res/drawable/photoeditor_effect_doodle.png b/res/drawable/photoeditor_effect_doodle.png
new file mode 100644 (file)
index 0000000..5b63178
Binary files /dev/null and b/res/drawable/photoeditor_effect_doodle.png differ
diff --git a/res/drawable/photoeditor_effect_duotone.png b/res/drawable/photoeditor_effect_duotone.png
new file mode 100644 (file)
index 0000000..994e104
Binary files /dev/null and b/res/drawable/photoeditor_effect_duotone.png differ
diff --git a/res/drawable/photoeditor_effect_filllight.png b/res/drawable/photoeditor_effect_filllight.png
new file mode 100644 (file)
index 0000000..999b7f7
Binary files /dev/null and b/res/drawable/photoeditor_effect_filllight.png differ
diff --git a/res/drawable/photoeditor_effect_fisheye.png b/res/drawable/photoeditor_effect_fisheye.png
new file mode 100644 (file)
index 0000000..5fd1b93
Binary files /dev/null and b/res/drawable/photoeditor_effect_fisheye.png differ
diff --git a/res/drawable/photoeditor_effect_flip.png b/res/drawable/photoeditor_effect_flip.png
new file mode 100644 (file)
index 0000000..dd74813
Binary files /dev/null and b/res/drawable/photoeditor_effect_flip.png differ
diff --git a/res/drawable/photoeditor_effect_grain.png b/res/drawable/photoeditor_effect_grain.png
new file mode 100644 (file)
index 0000000..f6ff7fa
Binary files /dev/null and b/res/drawable/photoeditor_effect_grain.png differ
diff --git a/res/drawable/photoeditor_effect_grayscale.png b/res/drawable/photoeditor_effect_grayscale.png
new file mode 100644 (file)
index 0000000..3c09cd3
Binary files /dev/null and b/res/drawable/photoeditor_effect_grayscale.png differ
diff --git a/res/drawable/photoeditor_effect_highlight.png b/res/drawable/photoeditor_effect_highlight.png
new file mode 100644 (file)
index 0000000..1d59fb4
Binary files /dev/null and b/res/drawable/photoeditor_effect_highlight.png differ
diff --git a/res/drawable/photoeditor_effect_lomoish.png b/res/drawable/photoeditor_effect_lomoish.png
new file mode 100644 (file)
index 0000000..ee347e6
Binary files /dev/null and b/res/drawable/photoeditor_effect_lomoish.png differ
diff --git a/res/drawable/photoeditor_effect_negative.png b/res/drawable/photoeditor_effect_negative.png
new file mode 100644 (file)
index 0000000..96da62c
Binary files /dev/null and b/res/drawable/photoeditor_effect_negative.png differ
diff --git a/res/drawable/photoeditor_effect_posterize.png b/res/drawable/photoeditor_effect_posterize.png
new file mode 100644 (file)
index 0000000..ac5384b
Binary files /dev/null and b/res/drawable/photoeditor_effect_posterize.png differ
diff --git a/res/drawable/photoeditor_effect_redeye.png b/res/drawable/photoeditor_effect_redeye.png
new file mode 100644 (file)
index 0000000..ba845b5
Binary files /dev/null and b/res/drawable/photoeditor_effect_redeye.png differ
diff --git a/res/drawable/photoeditor_effect_rotate.png b/res/drawable/photoeditor_effect_rotate.png
new file mode 100644 (file)
index 0000000..fa50ce2
Binary files /dev/null and b/res/drawable/photoeditor_effect_rotate.png differ
diff --git a/res/drawable/photoeditor_effect_saturation.png b/res/drawable/photoeditor_effect_saturation.png
new file mode 100644 (file)
index 0000000..d151d5c
Binary files /dev/null and b/res/drawable/photoeditor_effect_saturation.png differ
diff --git a/res/drawable/photoeditor_effect_sepia.png b/res/drawable/photoeditor_effect_sepia.png
new file mode 100644 (file)
index 0000000..236b990
Binary files /dev/null and b/res/drawable/photoeditor_effect_sepia.png differ
diff --git a/res/drawable/photoeditor_effect_shadow.png b/res/drawable/photoeditor_effect_shadow.png
new file mode 100644 (file)
index 0000000..e3caa44
Binary files /dev/null and b/res/drawable/photoeditor_effect_shadow.png differ
diff --git a/res/drawable/photoeditor_effect_sharpen.png b/res/drawable/photoeditor_effect_sharpen.png
new file mode 100644 (file)
index 0000000..2bd0fff
Binary files /dev/null and b/res/drawable/photoeditor_effect_sharpen.png differ
diff --git a/res/drawable/photoeditor_effect_straighten.png b/res/drawable/photoeditor_effect_straighten.png
new file mode 100644 (file)
index 0000000..309eb5a
Binary files /dev/null and b/res/drawable/photoeditor_effect_straighten.png differ
diff --git a/res/drawable/photoeditor_effect_temperature.png b/res/drawable/photoeditor_effect_temperature.png
new file mode 100644 (file)
index 0000000..d4f1355
Binary files /dev/null and b/res/drawable/photoeditor_effect_temperature.png differ
diff --git a/res/drawable/photoeditor_effect_tint.png b/res/drawable/photoeditor_effect_tint.png
new file mode 100644 (file)
index 0000000..6a6bb33
Binary files /dev/null and b/res/drawable/photoeditor_effect_tint.png differ
diff --git a/res/drawable/photoeditor_effect_vignette.png b/res/drawable/photoeditor_effect_vignette.png
new file mode 100644 (file)
index 0000000..e529563
Binary files /dev/null and b/res/drawable/photoeditor_effect_vignette.png differ
diff --git a/res/drawable/photoeditor_exposure.png b/res/drawable/photoeditor_exposure.png
new file mode 100644 (file)
index 0000000..b6e3566
Binary files /dev/null and b/res/drawable/photoeditor_exposure.png differ
diff --git a/res/drawable/photoeditor_fix.png b/res/drawable/photoeditor_fix.png
new file mode 100644 (file)
index 0000000..4b8f3b8
Binary files /dev/null and b/res/drawable/photoeditor_fix.png differ
diff --git a/res/drawable/photoeditor_redo.png b/res/drawable/photoeditor_redo.png
new file mode 100644 (file)
index 0000000..9daa01c
Binary files /dev/null and b/res/drawable/photoeditor_redo.png differ
diff --git a/res/drawable/photoeditor_scale_seekbar_color.9.png b/res/drawable/photoeditor_scale_seekbar_color.9.png
new file mode 100644 (file)
index 0000000..d78ff9b
Binary files /dev/null and b/res/drawable/photoeditor_scale_seekbar_color.9.png differ
diff --git a/res/drawable/photoeditor_scale_seekbar_filllight.9.png b/res/drawable/photoeditor_scale_seekbar_filllight.9.png
new file mode 100644 (file)
index 0000000..43acaf3
Binary files /dev/null and b/res/drawable/photoeditor_scale_seekbar_filllight.9.png differ
diff --git a/res/drawable/photoeditor_scale_seekbar_generic.9.png b/res/drawable/photoeditor_scale_seekbar_generic.9.png
new file mode 100644 (file)
index 0000000..3ee3cc7
Binary files /dev/null and b/res/drawable/photoeditor_scale_seekbar_generic.9.png differ
diff --git a/res/drawable/photoeditor_scale_seekbar_highlight.9.png b/res/drawable/photoeditor_scale_seekbar_highlight.9.png
new file mode 100644 (file)
index 0000000..70f54b8
Binary files /dev/null and b/res/drawable/photoeditor_scale_seekbar_highlight.9.png differ
diff --git a/res/drawable/photoeditor_scale_seekbar_shadow.9.png b/res/drawable/photoeditor_scale_seekbar_shadow.9.png
new file mode 100644 (file)
index 0000000..b17a785
Binary files /dev/null and b/res/drawable/photoeditor_scale_seekbar_shadow.9.png differ
diff --git a/res/drawable/photoeditor_seekbar_thumb.png b/res/drawable/photoeditor_seekbar_thumb.png
new file mode 100644 (file)
index 0000000..0d452c1
Binary files /dev/null and b/res/drawable/photoeditor_seekbar_thumb.png differ
diff --git a/res/drawable/photoeditor_toggle_button_background.xml b/res/drawable/photoeditor_toggle_button_background.xml
new file mode 100644 (file)
index 0000000..81c39ca
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+
+    <item android:state_selected="true" android:drawable="@drawable/photoeditor_toggle_button_highlight" />
+    <item android:state_pressed="true" android:drawable="@drawable/photoeditor_toggle_button_highlight" />
+    <item android:drawable="@android:color/transparent" />
+
+</selector>
diff --git a/res/drawable/photoeditor_toggle_button_highlight.9.png b/res/drawable/photoeditor_toggle_button_highlight.9.png
new file mode 100644 (file)
index 0000000..add582a
Binary files /dev/null and b/res/drawable/photoeditor_toggle_button_highlight.9.png differ
diff --git a/res/drawable/photoeditor_undo.png b/res/drawable/photoeditor_undo.png
new file mode 100644 (file)
index 0000000..0a7e0d1
Binary files /dev/null and b/res/drawable/photoeditor_undo.png differ
diff --git a/res/layout/photoeditor_color_seekbar.xml b/res/layout/photoeditor_color_seekbar.xml
new file mode 100644 (file)
index 0000000..fb93869
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.gallery3d.photoeditor.actions.ColorSeekBar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/seekbar_width"
+    android:layout_height="@dimen/seekbar_height"
+    android:layout_marginBottom="@dimen/seekbar_margin_bottom"
+    android:minHeight="@dimen/seekbar_height"
+    android:maxHeight="@dimen/seekbar_height"
+    android:progressDrawable="@android:color/transparent"/>
diff --git a/res/layout/photoeditor_crop_view.xml b/res/layout/photoeditor_crop_view.xml
new file mode 100644 (file)
index 0000000..151e6a6
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.gallery3d.photoeditor.actions.CropView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/fullscreen_effect_tool"
+    style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_doodle_view.xml b/res/layout/photoeditor_doodle_view.xml
new file mode 100644 (file)
index 0000000..d8e0868
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.gallery3d.photoeditor.actions.DoodleView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/fullscreen_effect_tool"
+    style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_effect_tool_panel.xml b/res/layout/photoeditor_effect_tool_panel.xml
new file mode 100644 (file)
index 0000000..7fd9257
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/effect_tool_panel_padding"
+    android:background="@color/translucent_black"
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/effect_label"
+        style="@style/EffectLabel"/>
+</LinearLayout>
diff --git a/res/layout/photoeditor_effects_artistic.xml b/res/layout/photoeditor_effects_artistic.xml
new file mode 100644 (file)
index 0000000..f5eb63a
--- /dev/null
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/EffectsContainer">
+
+    <com.android.gallery3d.photoeditor.actions.CrossProcessAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_crossprocess"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/crossprocess"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.CrossProcessAction>
+    <com.android.gallery3d.photoeditor.actions.PosterizeAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_posterize"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/posterize"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.PosterizeAction>
+    <com.android.gallery3d.photoeditor.actions.LomoishAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_lomoish"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/lomoish"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.LomoishAction>
+    <com.android.gallery3d.photoeditor.actions.DocumentaryAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_documentary"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/documentary"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.DocumentaryAction>
+    <com.android.gallery3d.photoeditor.actions.VignetteAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_vignette"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/vignette"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.VignetteAction>
+    <com.android.gallery3d.photoeditor.actions.GrainAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_grain"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/grain"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.GrainAction>
+    <com.android.gallery3d.photoeditor.actions.FisheyeAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_fisheye"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/fisheye"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.FisheyeAction>
+
+</LinearLayout>
diff --git a/res/layout/photoeditor_effects_color.xml b/res/layout/photoeditor_effects_color.xml
new file mode 100644 (file)
index 0000000..c078dfa
--- /dev/null
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/EffectsContainer">
+
+    <com.android.gallery3d.photoeditor.actions.ColorTemperatureAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_temperature"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/temperature"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.ColorTemperatureAction>
+    <com.android.gallery3d.photoeditor.actions.SaturationAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_saturation"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/saturation"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.SaturationAction>
+    <com.android.gallery3d.photoeditor.actions.GrayscaleAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_grayscale"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/grayscale"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.GrayscaleAction>
+    <com.android.gallery3d.photoeditor.actions.SepiaAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_sepia"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/sepia"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.SepiaAction>
+    <com.android.gallery3d.photoeditor.actions.NegativeAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_negative"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/negative"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.NegativeAction>
+    <com.android.gallery3d.photoeditor.actions.TintAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_tint"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/tint"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.TintAction>
+    <com.android.gallery3d.photoeditor.actions.DuotoneAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_duotone"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/duotone"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.DuotoneAction>
+    <com.android.gallery3d.photoeditor.actions.DoodleAction style="@style/Effect"
+        android:tag="@string/doodle_tooltip">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_doodle"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/doodle"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.DoodleAction>
+
+</LinearLayout>
diff --git a/res/layout/photoeditor_effects_exposure.xml b/res/layout/photoeditor_effects_exposure.xml
new file mode 100644 (file)
index 0000000..5a4fb29
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/EffectsContainer">
+
+    <com.android.gallery3d.photoeditor.actions.FillLightAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_filllight"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/filllight"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.FillLightAction>
+    <com.android.gallery3d.photoeditor.actions.HighlightAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_highlight"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/highlight"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.HighlightAction>
+    <com.android.gallery3d.photoeditor.actions.ShadowAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_shadow"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/shadow"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.ShadowAction>
+    <com.android.gallery3d.photoeditor.actions.AutoFixAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_autofix"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/autofix"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.AutoFixAction>
+
+</LinearLayout>
diff --git a/res/layout/photoeditor_effects_fix.xml b/res/layout/photoeditor_effects_fix.xml
new file mode 100644 (file)
index 0000000..b77ffd1
--- /dev/null
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/EffectsContainer">
+
+    <com.android.gallery3d.photoeditor.actions.CropAction style="@style/Effect"
+        android:tag="@string/crop_tooltip">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_crop"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/crop"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.CropAction>
+    <com.android.gallery3d.photoeditor.actions.RedEyeAction style="@style/Effect"
+        android:tag="@string/redeye_tooltip">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_redeye"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/redeye"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.RedEyeAction>
+    <com.android.gallery3d.photoeditor.actions.StraightenAction style="@style/Effect"
+        android:tag="@string/straighten_tooltip">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_straighten"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/straighten"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.StraightenAction>
+    <com.android.gallery3d.photoeditor.actions.RotateAction style="@style/Effect"
+        android:tag="@string/rotate_tooltip">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_rotate"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/rotate"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.RotateAction>
+    <com.android.gallery3d.photoeditor.actions.FlipAction style="@style/Effect"
+        android:tag="@string/flip_tooltip">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_flip"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/flip"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.FlipAction>
+    <com.android.gallery3d.photoeditor.actions.SharpenAction style="@style/Effect">
+        <ImageButton
+            android:id="@+id/effect_button"
+            style="@style/EffectIcon"
+            android:src="@drawable/photoeditor_effect_sharpen"/>
+        <TextView
+            android:id="@+id/effect_label"
+            android:text="@string/sharpen"
+            style="@style/EffectLabel"/>
+    </com.android.gallery3d.photoeditor.actions.SharpenAction>
+
+</LinearLayout>
diff --git a/res/layout/photoeditor_effects_gallery.xml b/res/layout/photoeditor_effects_gallery.xml
new file mode 100644 (file)
index 0000000..16a2366
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- The linear-layout is used to center content that cannot fill scroll-view -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/translucent_black"
+    android:gravity="center_horizontal">
+
+    <HorizontalScrollView
+        android:id="@+id/scroll_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:scrollbars="none"/>
+</LinearLayout>
diff --git a/res/layout/photoeditor_main.xml b/res/layout/photoeditor_main.xml
new file mode 100644 (file)
index 0000000..dd7551f
--- /dev/null
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.gallery3d.photoeditor.Toolbar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/toolbar"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <com.android.gallery3d.photoeditor.PhotoView
+        android:id="@+id/photo_view"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"/>
+
+    <com.android.gallery3d.photoeditor.EffectsBar android:id="@+id/effects_bar" style="@style/EffectsBar">
+
+        <LinearLayout style="@style/BottomActionBar" android:gravity="center_horizontal">
+
+            <LinearLayout android:id="@+id/effects_menu" style="@style/ActionBarLinearLayout">
+
+                <ImageButton
+                    android:id="@+id/exposure_button"
+                    style="@style/EffectsMenuActionButton"
+                    android:src="@drawable/photoeditor_exposure"/>
+                <ImageButton
+                    android:id="@+id/artistic_button"
+                    style="@style/EffectsMenuActionButton"
+                    android:src="@drawable/photoeditor_artistic"/>
+                <ImageButton
+                    android:id="@+id/color_button"
+                    style="@style/EffectsMenuActionButton"
+                    android:src="@drawable/photoeditor_color"/>
+                <ImageButton
+                    android:id="@+id/fix_button"
+                    style="@style/EffectsMenuActionButton"
+                    android:src="@drawable/photoeditor_fix"/>
+            </LinearLayout>
+
+        </LinearLayout>
+    </com.android.gallery3d.photoeditor.EffectsBar>
+
+    <com.android.gallery3d.photoeditor.ActionBar android:id="@+id/action_bar" style="@style/TopActionBar">
+
+        <LinearLayout style="@style/ActionBarLinearLayout">
+
+            <LinearLayout
+                android:id="@+id/action_bar_back"
+                style="@style/ActionBarBackLinearLayout">
+                <ImageView style="@style/ActionBarArrow"/>
+                <ImageView style="@style/ActionBarIcon"/>
+            </LinearLayout>
+
+            <TextView android:id="@+id/action_bar_title" style="@style/ActionBarText"/>
+        </LinearLayout>
+
+        <LinearLayout style="@style/ActionBarLinearLayout" android:layout_alignParentRight="true">
+
+            <ImageButton
+                android:id="@+id/undo_button"
+                style="@style/ImageActionButton"
+                android:src="@drawable/photoeditor_undo"/>
+            <ImageButton
+                android:id="@+id/redo_button"
+                style="@style/ImageActionButton"
+                android:src="@drawable/photoeditor_redo"/>
+            <Button
+                android:id="@+id/save_button"
+                style="@style/TextActionButton"
+                android:text="@string/save"/>
+        </LinearLayout>
+    </com.android.gallery3d.photoeditor.ActionBar>
+</com.android.gallery3d.photoeditor.Toolbar>
diff --git a/res/layout/photoeditor_rotate_view.xml b/res/layout/photoeditor_rotate_view.xml
new file mode 100644 (file)
index 0000000..9dbe9d0
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.gallery3d.photoeditor.actions.RotateView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/fullscreen_effect_tool"
+    style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_scale_seekbar.xml b/res/layout/photoeditor_scale_seekbar.xml
new file mode 100644 (file)
index 0000000..d8df19f
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.gallery3d.photoeditor.actions.ScaleSeekBar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/seekbar_width"
+    android:layout_height="@dimen/seekbar_height"
+    android:layout_marginBottom="@dimen/seekbar_margin_bottom"
+    android:minHeight="@dimen/seekbar_height"
+    android:maxHeight="@dimen/seekbar_height"
+    android:progressDrawable="@android:color/transparent"/>
diff --git a/res/layout/photoeditor_touch_view.xml b/res/layout/photoeditor_touch_view.xml
new file mode 100644 (file)
index 0000000..39a0871
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.gallery3d.photoeditor.actions.TouchView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/fullscreen_effect_tool"
+    style="@style/FullscreenToolView"/>
diff --git a/res/values-sw320dp/photoeditor_dimens.xml b/res/values-sw320dp/photoeditor_dimens.xml
new file mode 100755 (executable)
index 0000000..cde3fe9
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="effect_label_text_size">11sp</dimen>
+    <dimen name="effect_label_width">98sp</dimen>
+    <dimen name="effect_icon_size">72dp</dimen>
+    <dimen name="effect_padding_left_right">2dp</dimen>
+    <dimen name="effects_container_padding">12dp</dimen>
+    <dimen name="action_bar_height">40dp</dimen>
+    <dimen name="action_bar_text_size">17sp</dimen>
+    <dimen name="action_bar_arrow_padding_top">12dp</dimen>
+    <dimen name="action_bar_arrow_padding_bottom">12dp</dimen>
+    <dimen name="action_bar_arrow_padding_left">3dp</dimen>
+    <dimen name="action_bar_arrow_padding_right">0dp</dimen>
+    <dimen name="action_bar_icon_padding_top">4dp</dimen>
+    <dimen name="action_bar_icon_padding_bottom">4dp</dimen>
+    <dimen name="action_bar_icon_padding_left">0dp</dimen>
+    <dimen name="action_bar_icon_padding_right">6dp</dimen>
+    <dimen name="action_button_padding_top">5dp</dimen>
+    <dimen name="action_button_padding_bottom">5dp</dimen>
+    <dimen name="action_button_padding_left">15dp</dimen>
+    <dimen name="action_button_padding_right">15dp</dimen>
+    <dimen name="effect_tool_panel_padding">12dp</dimen>
+    <dimen name="seekbar_width">290dp</dimen>
+    <dimen name="seekbar_height">29dp</dimen>
+    <dimen name="seekbar_margin_bottom">6dp</dimen>
+    <dimen name="crop_indicator_size">35dp</dimen>
+</resources>
diff --git a/res/values-sw600dp/photoeditor_dimens.xml b/res/values-sw600dp/photoeditor_dimens.xml
new file mode 100755 (executable)
index 0000000..285420a
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="effect_label_text_size">14sp</dimen>
+    <dimen name="effect_label_width">120sp</dimen>
+    <dimen name="effect_icon_size">90dp</dimen>
+    <dimen name="effect_padding_left_right">2dp</dimen>
+    <dimen name="effects_container_padding">12dp</dimen>
+    <dimen name="action_bar_height">56dp</dimen>
+    <dimen name="action_bar_text_size">18sp</dimen>
+    <dimen name="action_bar_arrow_padding_top">20dp</dimen>
+    <dimen name="action_bar_arrow_padding_bottom">20dp</dimen>
+    <dimen name="action_bar_arrow_padding_left">4dp</dimen>
+    <dimen name="action_bar_arrow_padding_right">0dp</dimen>
+    <dimen name="action_bar_icon_padding_top">7dp</dimen>
+    <dimen name="action_bar_icon_padding_bottom">7dp</dimen>
+    <dimen name="action_bar_icon_padding_left">0dp</dimen>
+    <dimen name="action_bar_icon_padding_right">8dp</dimen>
+    <dimen name="action_button_padding_top">9dp</dimen>
+    <dimen name="action_button_padding_bottom">9dp</dimen>
+    <dimen name="action_button_padding_left">22dp</dimen>
+    <dimen name="action_button_padding_right">22dp</dimen>
+    <dimen name="effect_tool_panel_padding">16dp</dimen>
+    <dimen name="seekbar_width">560dp</dimen>
+    <dimen name="seekbar_height">33dp</dimen>
+    <dimen name="seekbar_margin_bottom">8dp</dimen>
+    <dimen name="crop_indicator_size">43dp</dimen>
+</resources>
diff --git a/res/values-sw800dp/photoeditor_dimens.xml b/res/values-sw800dp/photoeditor_dimens.xml
new file mode 100755 (executable)
index 0000000..8cce1ba
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="effect_label_text_size">15sp</dimen>
+    <dimen name="effect_label_width">138sp</dimen>
+    <dimen name="effect_icon_size">100dp</dimen>
+    <dimen name="effect_padding_left_right">3dp</dimen>
+    <dimen name="effects_container_padding">18dp</dimen>
+    <dimen name="action_bar_height">56dp</dimen>
+    <dimen name="action_bar_text_size">19sp</dimen>
+    <dimen name="action_bar_arrow_padding_top">20dp</dimen>
+    <dimen name="action_bar_arrow_padding_bottom">20dp</dimen>
+    <dimen name="action_bar_arrow_padding_left">4dp</dimen>
+    <dimen name="action_bar_arrow_padding_right">0dp</dimen>
+    <dimen name="action_bar_icon_padding_top">7dp</dimen>
+    <dimen name="action_bar_icon_padding_bottom">7dp</dimen>
+    <dimen name="action_bar_icon_padding_left">0dp</dimen>
+    <dimen name="action_bar_icon_padding_right">11dp</dimen>
+    <dimen name="action_button_padding_top">9dp</dimen>
+    <dimen name="action_button_padding_bottom">9dp</dimen>
+    <dimen name="action_button_padding_left">28dp</dimen>
+    <dimen name="action_button_padding_right">28dp</dimen>
+    <dimen name="effect_tool_panel_padding">18dp</dimen>
+    <dimen name="seekbar_width">560dp</dimen>
+    <dimen name="seekbar_height">35dp</dimen>
+    <dimen name="seekbar_margin_bottom">9dp</dimen>
+    <dimen name="crop_indicator_size">48dp</dimen>
+</resources>
diff --git a/res/values/photoeditor_arrays.xml b/res/values/photoeditor_arrays.xml
new file mode 100644 (file)
index 0000000..4c27215
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <array name="color_picker_colors">
+        <item>@color/color_picker_preset_color1</item>
+        <item>@color/color_picker_preset_color2</item>
+        <item>@color/color_picker_preset_color3</item>
+        <item>@color/color_picker_preset_color4</item>
+        <item>@color/color_picker_preset_color5</item>
+        <item>@color/color_picker_preset_color6</item>
+        <item>@color/color_picker_preset_color7</item>
+        <item>@color/color_picker_preset_color8</item>
+        <item>@color/color_picker_preset_color9</item>
+        <item>@color/color_picker_preset_color10</item>
+        <item>@color/color_picker_preset_color11</item>
+        <item>@color/color_picker_preset_color12</item>
+        <item>@color/color_picker_preset_color13</item>
+        <item>@color/color_picker_preset_color14</item>
+        <item>@color/color_picker_preset_color15</item>
+        <item>@color/color_picker_preset_color16</item>
+        <item>@color/color_picker_preset_color17</item>
+        <item>@color/color_picker_preset_color18</item>
+        <item>@color/color_picker_preset_color19</item>
+        <item>@color/color_picker_preset_color20</item>
+        <item>@color/color_picker_preset_color21</item>
+        <item>@color/color_picker_preset_color22</item>
+        <item>@color/color_picker_preset_color23</item>
+        <item>@color/color_picker_preset_color24</item>
+    </array>
+</resources>
diff --git a/res/values/photoeditor_colors.xml b/res/values/photoeditor_colors.xml
new file mode 100644 (file)
index 0000000..9899924
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <color name="translucent_black">#67000000</color>
+    <color name="color_picker_preset_color1">#ddff1e00</color>
+    <color name="color_picker_preset_color2">#ddff5b00</color>
+    <color name="color_picker_preset_color3">#ddff9900</color>
+    <color name="color_picker_preset_color4">#ddffd600</color>
+    <color name="color_picker_preset_color5">#ddeaff00</color>
+    <color name="color_picker_preset_color6">#ddadff00</color>
+    <color name="color_picker_preset_color7">#dd70ff00</color>
+    <color name="color_picker_preset_color8">#dd32ff00</color>
+    <color name="color_picker_preset_color9">#dd00ff0a</color>
+    <color name="color_picker_preset_color10">#dd00ff47</color>
+    <color name="color_picker_preset_color11">#dd00ff84</color>
+    <color name="color_picker_preset_color12">#dd00ffc1</color>
+    <color name="color_picker_preset_color13">#dd00ffff</color>
+    <color name="color_picker_preset_color14">#dd00c1ff</color>
+    <color name="color_picker_preset_color15">#dd0084ff</color>
+    <color name="color_picker_preset_color16">#dd0047ff</color>
+    <color name="color_picker_preset_color17">#dd000aff</color>
+    <color name="color_picker_preset_color18">#dd3300ff</color>
+    <color name="color_picker_preset_color19">#dd7000ff</color>
+    <color name="color_picker_preset_color20">#ddad00ff</color>
+    <color name="color_picker_preset_color21">#ddea00ff</color>
+    <color name="color_picker_preset_color22">#ddff00d6</color>
+    <color name="color_picker_preset_color23">#ddff0098</color>
+    <color name="color_picker_preset_color24">#ddff005b</color>
+</resources>
diff --git a/res/values/photoeditor_styles.xml b/res/values/photoeditor_styles.xml
new file mode 100644 (file)
index 0000000..42b91d1
--- /dev/null
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="EffectIcon">
+        <item name="android:layout_width">@dimen/effect_icon_size</item>
+        <item name="android:layout_height">@dimen/effect_icon_size</item>
+        <item name="android:layout_gravity">center_horizontal</item>
+        <item name="android:scaleType">fitCenter</item>
+        <item name="android:background">@null</item>
+    </style>
+    <style name="EffectLabel">
+        <item name="android:layout_width">@dimen/effect_label_width</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_horizontal</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textSize">@dimen/effect_label_text_size</item>
+        <item name="android:textColor">#FFFFFF</item>
+        <item name="android:maxLines">1</item>
+        <item name="android:shadowColor">#000000</item>
+        <item name="android:shadowRadius">2.0</item>
+    </style>
+    <style name="Effect">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_vertical</item>
+        <item name="android:paddingLeft">@dimen/effect_padding_left_right</item>
+        <item name="android:paddingRight">@dimen/effect_padding_left_right</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+    <style name="EffectsContainer">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:padding">@dimen/effects_container_padding</item>
+        <item name="android:orientation">horizontal</item>
+    </style>
+    <style name="EffectsBar">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_alignParentBottom">true</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+    <style name="TopActionBar" parent="@style/ActionBar">
+        <item name="android:layout_alignParentTop">true</item>
+        <item name="android:background">@drawable/photoeditor_actionbar_translucent</item>
+    </style>
+    <style name="BottomActionBar" parent="@style/ActionBar">
+        <item name="android:background">@drawable/photoeditor_actionbar_translucent_bottom</item>
+    </style>
+    <style name="ActionBar">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">@dimen/action_bar_height</item>
+    </style>
+    <style name="ActionBarLinearLayout">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:layout_centerVertical">true</item>
+        <item name="android:orientation">horizontal</item>
+    </style>
+    <style name="ActionBarBackLinearLayout" parent="@style/ActionBarLinearLayout">
+        <item name="android:clickable">true</item>
+        <item name="android:focusable">true</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+    <style name="ActionBarImageView">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:scaleType">centerInside</item>
+        <item name="android:adjustViewBounds">true</item>
+    </style>
+    <style name="ActionBarArrow" parent="@style/ActionBarImageView">
+        <item name="android:paddingTop">@dimen/action_bar_arrow_padding_top</item>
+        <item name="android:paddingBottom">@dimen/action_bar_arrow_padding_bottom</item>
+        <item name="android:paddingLeft">@dimen/action_bar_arrow_padding_left</item>
+        <item name="android:paddingRight">@dimen/action_bar_arrow_padding_right</item>
+        <item name="android:src">@drawable/photoeditor_arrow_back</item>
+    </style>
+    <style name="ActionBarIcon" parent="@style/ActionBarImageView">
+        <item name="android:paddingTop">@dimen/action_bar_icon_padding_top</item>
+        <item name="android:paddingBottom">@dimen/action_bar_icon_padding_bottom</item>
+        <item name="android:paddingLeft">@dimen/action_bar_icon_padding_left</item>
+        <item name="android:paddingRight">@dimen/action_bar_icon_padding_right</item>
+        <item name="android:src">@mipmap/ic_launcher_gallery</item>
+    </style>
+    <style name="ActionBarText">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:textSize">@dimen/action_bar_text_size</item>
+        <item name="android:textColor">#FFFFFF</item>
+    </style>
+    <style name="ImageActionButton" parent="@style/ActionBarImageView">
+        <item name="android:paddingTop">@dimen/action_button_padding_top</item>
+        <item name="android:paddingBottom">@dimen/action_button_padding_bottom</item>
+        <item name="android:paddingLeft">@dimen/action_button_padding_left</item>
+        <item name="android:paddingRight">@dimen/action_button_padding_right</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+    <style name="TextActionButton" parent="@style/ActionBarText">
+        <item name="android:paddingLeft">@dimen/action_button_padding_left</item>
+        <item name="android:paddingRight">@dimen/action_button_padding_right</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+    <style name="EffectsMenuActionButton" parent="@style/ImageActionButton">
+        <item name="android:background">@drawable/photoeditor_toggle_button_background</item>
+    </style>
+    <style name="FullscreenToolView">
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">fill_parent</item>
+    </style>
+    <style name="SpinnerProgressDialog">
+        <item name="android:background">@android:color/transparent</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowTitleStyle">@null</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:backgroundDimEnabled">false</item>
+    </style>
+</resources>
diff --git a/src/com/android/gallery3d/photoeditor/ActionBar.java b/src/com/android/gallery3d/photoeditor/ActionBar.java
new file mode 100644 (file)
index 0000000..52dfb0b
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import com.android.gallery3d.R;
+
+/**
+ * Action bar that contains buttons such as undo, redo, save, etc. and listens to stack changes for
+ * enabling/disabling buttons.
+ */
+public class ActionBar extends RelativeLayout implements FilterStack.StackListener {
+
+    /**
+     * Listener of action button clicked.
+     */
+    public interface ActionBarListener {
+
+        void onUndo();
+
+        void onRedo();
+
+        void onSave();
+    }
+
+    private static final int ENABLE_BUTTON = 1;
+    private static final float ENABLED_ALPHA = 1;
+    private static final float DISABLED_ALPHA = 0.47f;
+
+    private final Handler handler;
+    private View undo;
+    private View redo;
+    private View save;
+
+    public ActionBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        handler = new Handler() {
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case ENABLE_BUTTON:
+                        boolean canUndo = (msg.arg1 > 0);
+                        boolean canRedo = (msg.arg2 > 0);
+                        enableButton(undo, canUndo);
+                        enableButton(redo, canRedo);
+                        enableButton(save, canUndo);
+                        break;
+                }
+            }
+        };
+    }
+
+    /**
+     * Initializes with a non-null ActionBarListener.
+     */
+    public void initialize(final ActionBarListener listener) {
+        undo = findViewById(R.id.undo_button);
+        undo.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (isEnabled()) {
+                    listener.onUndo();
+                }
+            }
+        });
+
+        redo = findViewById(R.id.redo_button);
+        redo.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (isEnabled()) {
+                    listener.onRedo();
+                }
+            }
+        });
+
+        save = findViewById(R.id.save_button);
+        save.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (isEnabled()) {
+                    listener.onSave();
+                }
+            }
+        });
+
+        resetButtons();
+    }
+
+    public void resetButtons() {
+        // Disable buttons immediately instead of waiting for ENABLE_BUTTON messages which may
+        // happen some time later after stack changes.
+        enableButton(undo, false);
+        enableButton(redo, false);
+        enableButton(save, false);
+    }
+
+    public void disableSave() {
+        enableButton(save, false);
+    }
+
+    private void enableButton(View button, boolean enabled) {
+        button.setEnabled(enabled);
+        button.setAlpha(enabled ? ENABLED_ALPHA : DISABLED_ALPHA);
+    }
+
+    @Override
+    public void onStackChanged(boolean canUndo, boolean canRedo) {
+        // Listens to stack changes that may come from the worker thread; send messages to enable
+        // buttons only in the UI thread.
+        handler.sendMessage(handler.obtainMessage(ENABLE_BUTTON, canUndo ? 1 : 0, canRedo ? 1 : 0));
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/BitmapUtils.java b/src/com/android/gallery3d/photoeditor/BitmapUtils.java
new file mode 100644 (file)
index 0000000..5211a83
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Utils for bitmap operations.
+ */
+public class BitmapUtils {
+
+    private static final String TAG = "BitmapUtils";
+    private static final int DEFAULT_COMPRESS_QUALITY = 90;
+    private static final int INDEX_ORIENTATION = 0;
+
+    private static final String[] IMAGE_PROJECTION = new String[] {
+        ImageColumns.ORIENTATION
+    };
+
+    private final Context context;
+
+    public BitmapUtils(Context context) {
+        this.context = context;
+    }
+
+    /**
+     * Creates a mutable bitmap from subset of source bitmap, transformed by the optional matrix.
+     */
+    public static Bitmap createBitmap(
+            Bitmap source, int x, int y, int width, int height, Matrix m) {
+        // Re-implement Bitmap createBitmap() to always return a mutable bitmap.
+        Canvas canvas = new Canvas();
+
+        Bitmap bitmap;
+        Paint paint;
+        if ((m == null) || m.isIdentity()) {
+            bitmap = Bitmap.createBitmap(width, height, source.getConfig());
+            paint = null;
+        } else {
+            RectF rect = new RectF(0, 0, width, height);
+            m.mapRect(rect);
+            bitmap = Bitmap.createBitmap(
+                    Math.round(rect.width()), Math.round(rect.height()), source.getConfig());
+
+            canvas.translate(-rect.left, -rect.top);
+            canvas.concat(m);
+
+            paint = new Paint(Paint.FILTER_BITMAP_FLAG);
+            if (!m.rectStaysRect()) {
+                paint.setAntiAlias(true);
+            }
+        }
+        bitmap.setDensity(source.getDensity());
+        canvas.setBitmap(bitmap);
+
+        Rect srcBounds = new Rect(x, y, x + width, y + height);
+        RectF dstBounds = new RectF(0, 0, width, height);
+        canvas.drawBitmap(source, srcBounds, dstBounds, paint);
+        return bitmap;
+    }
+
+    private void closeStream(Closeable stream) {
+        if (stream != null) {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private Rect getBitmapBounds(Uri uri) {
+        Rect bounds = new Rect();
+        InputStream is = null;
+
+        try {
+            is = context.getContentResolver().openInputStream(uri);
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inJustDecodeBounds = true;
+            BitmapFactory.decodeStream(is, null, options);
+
+            bounds.right = options.outWidth;
+            bounds.bottom = options.outHeight;
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } finally {
+            closeStream(is);
+        }
+
+        return bounds;
+    }
+
+    private int getOrientation(Uri uri) {
+        int orientation = 0;
+        Cursor cursor = context.getContentResolver().query(uri, IMAGE_PROJECTION, null, null, null);
+        try {
+            if ((cursor != null) && cursor.moveToNext()) {
+                orientation = cursor.getInt(INDEX_ORIENTATION);
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return orientation;
+    }
+
+    /**
+     * Decodes bitmap (maybe immutable) that keeps aspect-ratio and spans most within the bounds.
+     */
+    private Bitmap decodeBitmap(Uri uri, int width, int height) {
+        InputStream is = null;
+        Bitmap bitmap = null;
+
+        try {
+            // TODO: Take max pixels allowed into account for calculation to avoid possible OOM.
+            Rect bounds = getBitmapBounds(uri);
+            int sampleSize = Math.max(bounds.width() / width, bounds.height() / height);
+            sampleSize = Math.min(sampleSize,
+                    Math.max(bounds.width() / height, bounds.height() / width));
+
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inSampleSize = Math.max(sampleSize, 1);
+            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+            is = context.getContentResolver().openInputStream(uri);
+            bitmap = BitmapFactory.decodeStream(is, null, options);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "FileNotFoundException: " + uri);
+        } finally {
+            closeStream(is);
+        }
+
+        // Scale down the sampled bitmap if it's still larger than the desired dimension.
+        if (bitmap != null) {
+            float scale = Math.min((float) width / bitmap.getWidth(),
+                    (float) height / bitmap.getHeight());
+            scale = Math.max(scale, Math.min((float) height / bitmap.getWidth(),
+                    (float) width / bitmap.getHeight()));
+            if (scale < 1) {
+                Matrix m = new Matrix();
+                m.setScale(scale, scale);
+                Bitmap transformed = createBitmap(
+                        bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m);
+                bitmap.recycle();
+                return transformed;
+            }
+        }
+        return bitmap;
+    }
+
+    /**
+     * Gets decoded bitmap that keeps orientation as well.
+     */
+    public Bitmap getBitmap(Uri uri, int width, int height) {
+        Bitmap bitmap = decodeBitmap(uri, width, height);
+
+        // Rotate the decoded bitmap according to its orientation if it's necessary.
+        if (bitmap != null) {
+            int orientation = getOrientation(uri);
+            if (orientation != 0) {
+                Matrix m = new Matrix();
+                m.setRotate(orientation);
+                Bitmap transformed = createBitmap(
+                        bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m);
+                bitmap.recycle();
+                return transformed;
+            }
+        }
+        return bitmap;
+    }
+
+    /**
+     * Saves the bitmap by given directory, filename, and format; if the directory is given null,
+     * then saves it under the cache directory.
+     */
+    public File saveBitmap(
+            Bitmap bitmap, String directory, String filename, CompressFormat format) {
+
+        if (directory == null) {
+            directory = context.getCacheDir().getAbsolutePath();
+        } else {
+            // Check if the given directory exists or try to create it.
+            File file = new File(directory);
+            if (!file.isDirectory() && !file.mkdirs()) {
+                return null;
+            }
+        }
+
+        File file = null;
+        OutputStream os = null;
+
+        try {
+            filename = (format == CompressFormat.PNG) ? filename + ".png" : filename + ".jpg";
+            file = new File(directory, filename);
+            os = new FileOutputStream(file);
+            bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } finally {
+            closeStream(os);
+        }
+        return file;
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/EffectsBar.java b/src/com/android/gallery3d/photoeditor/EffectsBar.java
new file mode 100644 (file)
index 0000000..6e3178e
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.photoeditor.actions.EffectAction;
+import com.android.gallery3d.photoeditor.actions.EffectToolFactory;
+
+/**
+ * Effects bar that contains all effects and shows them in categorized views.
+ */
+public class EffectsBar extends LinearLayout {
+
+    private FilterStack filterStack;
+    private LayoutInflater inflater;
+    private View effectsGallery;
+    private ViewGroup effectToolPanel;
+    private EffectAction activeEffect;
+
+    public EffectsBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void initialize(FilterStack filterStack) {
+        this.filterStack = filterStack;
+        inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        setupMenuToggle(R.id.exposure_button, R.layout.photoeditor_effects_exposure);
+        setupMenuToggle(R.id.artistic_button, R.layout.photoeditor_effects_artistic);
+        setupMenuToggle(R.id.color_button, R.layout.photoeditor_effects_color);
+        setupMenuToggle(R.id.fix_button, R.layout.photoeditor_effects_fix);
+
+        setEnabled(false);
+    }
+
+    private void setupMenuToggle(int toggleId, final int effectsId) {
+        final View toggle = findViewById(toggleId);
+        toggle.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                // Toggle off to exit effects gallery that is showing. Or toggle on to show effects
+                // gallery after exiting an active effect if applicable.
+                exit((toggle.isSelected() && (effectsGallery != null)) ? null : new Runnable() {
+
+                    @Override
+                    public void run() {
+                        toggle.setSelected(true);
+                        showEffectsGallery(effectsId);
+                    }
+                });
+            }
+        });
+    }
+
+    private void setupEffectListener(final EffectAction effect) {
+        effect.setListener(new EffectAction.Listener() {
+
+            @Override
+            public void onClick() {
+                if (isEnabled()) {
+                    // Set the clicked effect active before exiting effects-gallery.
+                    activeEffect = effect;
+                    exitEffectsGallery();
+                    // Create effect tool panel first before the factory could create tools within.
+                    createEffectToolPanel();
+                    activeEffect.begin(
+                            filterStack, new EffectToolFactory(effectToolPanel, inflater));
+                }
+            }
+
+            @Override
+            public void onDone() {
+                exit(null);
+            }
+        });
+    }
+
+    private void createEffectToolPanel() {
+        effectToolPanel = (ViewGroup) inflater.inflate(
+                R.layout.photoeditor_effect_tool_panel, this, false);
+        ((TextView) effectToolPanel.findViewById(R.id.effect_label)).setText(activeEffect.name());
+        addView(effectToolPanel, 0);
+    }
+
+    private void showEffectsGallery(int effectsId) {
+        // Inflate scrollable effects-gallery and desired effects into effects-bar.
+        effectsGallery = inflater.inflate(R.layout.photoeditor_effects_gallery, this, false);
+        ViewGroup scrollView = (ViewGroup) effectsGallery.findViewById(R.id.scroll_view);
+        ViewGroup effects = (ViewGroup) inflater.inflate(effectsId, scrollView, false);
+        for (int i = 0; i < effects.getChildCount(); i++) {
+            setupEffectListener((EffectAction) effects.getChildAt(i));
+        }
+        scrollView.addView(effects);
+        scrollView.scrollTo(0, 0);
+        addView(effectsGallery, 0);
+    }
+
+    private boolean exitEffectsGallery() {
+        if (effectsGallery != null) {
+            if (activeEffect != null) {
+                // Detach the active effect from effects-gallery that could be recycled by gc.
+                ViewGroup scrollView = (ViewGroup) effectsGallery.findViewById(R.id.scroll_view);
+                ((ViewGroup) scrollView.getChildAt(0)).removeView(activeEffect);
+            }
+            removeView(effectsGallery);
+            effectsGallery = null;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean exitActiveEffect(final Runnable runnableOnDone) {
+        if (activeEffect != null) {
+            final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(
+                    (ViewGroup) getRootView().findViewById(R.id.toolbar));
+            activeEffect.end(new Runnable() {
+
+                @Override
+                public void run() {
+                    progressDialog.dismiss();
+                    View fullscreenTool = getRootView().findViewById(R.id.fullscreen_effect_tool);
+                    if (fullscreenTool != null) {
+                        ((ViewGroup) fullscreenTool.getParent()).removeView(fullscreenTool);
+                    }
+                    removeView(effectToolPanel);
+                    effectToolPanel = null;
+                    activeEffect = null;
+                    if (runnableOnDone != null) {
+                        runnableOnDone.run();
+                    }
+                }
+            });
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Exits from effects gallery or the active effect; then executes the runnable if applicable.
+     *
+     * @return true if exiting from effects gallery or the active effect; otherwise, false.
+     */
+    public boolean exit(final Runnable runnableOnDone) {
+        // Exit effects-menu selected states.
+        ViewGroup menu = (ViewGroup) findViewById(R.id.effects_menu);
+        for (int i = 0; i < menu.getChildCount(); i++) {
+            View toggle = menu.getChildAt(i);
+            if (toggle.isSelected()) {
+                toggle.setSelected(false);
+            }
+        }
+
+        if (exitActiveEffect(runnableOnDone)) {
+            return true;
+        }
+
+        boolean exited = exitEffectsGallery();
+        if (runnableOnDone != null) {
+            runnableOnDone.run();
+        }
+        return exited;
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/FilterStack.java b/src/com/android/gallery3d/photoeditor/FilterStack.java
new file mode 100644 (file)
index 0000000..3b35aa3
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.graphics.Bitmap;
+import android.media.effect.EffectContext;
+
+import com.android.gallery3d.photoeditor.filters.Filter;
+
+import java.util.Stack;
+
+/**
+ * A stack of filters to be applied onto a photo.
+ */
+public class FilterStack {
+
+    /**
+     * Listener of stack changes.
+     */
+    public interface StackListener {
+
+        void onStackChanged(boolean canUndo, boolean canRedo);
+    }
+
+    private final Stack<Filter> appliedStack = new Stack<Filter>();
+    private final Stack<Filter> redoStack = new Stack<Filter>();
+
+    // Use two photo buffers as in and out in turns to apply filters in the stack.
+    private final Photo[] buffers = new Photo[2];
+    private final PhotoView photoView;
+    private final StackListener stackListener;
+
+    private EffectContext effectContext;
+    private Photo source;
+    private Runnable queuedTopFilterChange;
+    private volatile boolean paused;
+
+    public FilterStack(PhotoView photoView, StackListener stackListener) {
+        this.photoView = photoView;
+        this.stackListener = stackListener;
+    }
+
+    private void clearBuffers() {
+        for (int i = 0; i < buffers.length; i++) {
+            if (buffers[i] != null) {
+                buffers[i].clear();
+                buffers[i] = null;
+            }
+        }
+    }
+
+    private void reallocateBuffer(int target) {
+        int other = target ^ 1;
+        buffers[target] = Photo.create(buffers[other].width(), buffers[other].height());
+    }
+
+    private void invalidate() {
+        // In/out buffers need redrawn by re-applying filters on source photo.
+        clearBuffers();
+        if (source != null) {
+            buffers[0] = Photo.create(source.width(), source.height());
+            reallocateBuffer(1);
+
+            Photo photo = source;
+            for (int i = 0; i < appliedStack.size() && !paused; i++) {
+                photo = runFilter(i);
+            }
+            // Source photo will be displayed if there is no filter stacked.
+            photoView.setPhoto(photo);
+        }
+    }
+
+    private void invalidateTopFilter() {
+        if (!appliedStack.empty()) {
+            photoView.setPhoto(runFilter(appliedStack.size() - 1));
+        }
+    }
+
+    private Photo runFilter(int filterIndex) {
+        int out = getOutBufferIndex(filterIndex);
+        Photo input = (filterIndex > 0) ? buffers[out ^ 1] : source;
+        if ((input != null) && (buffers[out] != null)) {
+            if (!buffers[out].matchDimension(input)) {
+                buffers[out].clear();
+                reallocateBuffer(out);
+            }
+            appliedStack.get(filterIndex).process(effectContext, input, buffers[out]);
+            return buffers[out];
+        }
+        return null;
+    }
+
+    private int getOutBufferIndex(int filterIndex) {
+        // buffers[0] and buffers[1] are swapped in turns as the in/out buffers for
+        // processing stacked filters. For example, the first filter reads buffer[0] and
+        // writes buffer[1]; the second filter then reads buffer[1] and writes buffer[0].
+        // The returned index should only be used when the applied filter stack isn't empty.
+        return (filterIndex + 1) % 2;
+    }
+
+    private void callbackDone(final OnDoneCallback callback) {
+        // Call back in UI thread to report done.
+        photoView.post(new Runnable() {
+
+            @Override
+            public void run() {
+                callback.onDone();
+            }
+        });
+    }
+
+    private void stackChanged() {
+        stackListener.onStackChanged(!appliedStack.empty(), !redoStack.empty());
+    }
+
+    public void saveBitmap(final OnDoneBitmapCallback callback) {
+        photoView.queue(new Runnable() {
+
+            @Override
+            public void run() {
+                Photo photo = appliedStack.empty() ?
+                        null : buffers[getOutBufferIndex(appliedStack.size() - 1)];
+                final Bitmap bitmap = (photo != null) ? photo.save() : null;
+                photoView.post(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        callback.onDone(bitmap);
+                    }
+                });
+            }
+        });
+    }
+
+    public void setPhotoSource(final Bitmap bitmap, final OnDoneCallback callback) {
+        photoView.queue(new Runnable() {
+
+            @Override
+            public void run() {
+                source = Photo.create(bitmap);
+                invalidate();
+                callbackDone(callback);
+            }
+        });
+    }
+
+    public void pushFilter(final Filter filter) {
+        photoView.queue(new Runnable() {
+
+            @Override
+            public void run() {
+                while (!redoStack.empty()) {
+                    redoStack.pop().release();
+                }
+                appliedStack.push(filter);
+                stackChanged();
+            }
+        });
+    }
+
+    public void undo(final OnDoneCallback callback) {
+        photoView.queue(new Runnable() {
+
+            @Override
+            public void run() {
+                if (!appliedStack.empty()) {
+                    redoStack.push(appliedStack.pop());
+                    stackChanged();
+                    invalidate();
+                }
+                callbackDone(callback);
+            }
+        });
+    }
+
+    public void redo(final OnDoneCallback callback) {
+        photoView.queue(new Runnable() {
+
+            @Override
+            public void run() {
+                if (!redoStack.empty()) {
+                    appliedStack.push(redoStack.pop());
+                    stackChanged();
+                    invalidateTopFilter();
+                }
+                callbackDone(callback);
+            }
+        });
+    }
+
+    public void topFilterChanged(final OnDoneCallback callback) {
+        // Remove the outdated top-filter change before queuing a new one.
+        if (queuedTopFilterChange != null) {
+            photoView.remove(queuedTopFilterChange);
+        }
+        queuedTopFilterChange = new Runnable() {
+
+            @Override
+            public void run() {
+                invalidateTopFilter();
+                callbackDone(callback);
+            }
+        };
+        photoView.queue(queuedTopFilterChange);
+    }
+
+    public void onPause() {
+        // Flush pending queued operations and release effect-context before GL context is lost.
+        // Use pause-flag to avoid lengthy runnable in GL thread blocking onPause().
+        paused = true;
+        photoView.flush();
+        photoView.queueEvent(new Runnable() {
+
+            @Override
+            public void run() {
+                if (effectContext != null) {
+                    effectContext.release();
+                    effectContext = null;
+                }
+                photoView.setPhoto(null);
+                clearBuffers();
+            }
+        });
+        photoView.onPause();
+    }
+
+    public void onResume() {
+        photoView.onResume();
+        photoView.queue(new Runnable() {
+
+            @Override
+            public void run() {
+                // Create effect context after GL context is created or recreated.
+                effectContext = EffectContext.createWithCurrentGlContext();
+            }
+        });
+        paused = false;
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/LoadScreennailTask.java b/src/com/android/gallery3d/photoeditor/LoadScreennailTask.java
new file mode 100644 (file)
index 0000000..914d50b
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.view.Gravity;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+
+/**
+ * Asynchronous task for loading source photo screennail.
+ */
+public class LoadScreennailTask extends AsyncTask<Uri, Void, Bitmap> {
+
+    /**
+     * Callback for the completed asynchronous task.
+     */
+    public interface Callback {
+
+        void onComplete(Bitmap bitmap);
+    }
+
+    private static final int SCREENNAIL_WIDTH = 1280;
+    private static final int SCREENNAIL_HEIGHT = 960;
+
+    private final Context context;
+    private final Callback callback;
+
+    public LoadScreennailTask(Context context, Callback callback) {
+        this.context = context;
+        this.callback = callback;
+    }
+
+    /**
+     * The task should be executed with one given source photo uri.
+     */
+    @Override
+    protected Bitmap doInBackground(Uri... params) {
+        if (params[0] == null) {
+            return null;
+        }
+        return new BitmapUtils(context).getBitmap(params[0], SCREENNAIL_WIDTH, SCREENNAIL_HEIGHT);
+    }
+
+    @Override
+    protected void onPostExecute(Bitmap bitmap) {
+        if (bitmap == null) {
+            Toast toast = Toast.makeText(context, R.string.loading_failure, Toast.LENGTH_SHORT);
+            toast.setGravity(Gravity.CENTER, 0, 0);
+            toast.show();
+        }
+        callback.onComplete(bitmap);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/OnDoneBitmapCallback.java b/src/com/android/gallery3d/photoeditor/OnDoneBitmapCallback.java
new file mode 100644 (file)
index 0000000..a0ef490
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.graphics.Bitmap;
+
+/**
+ * Callback that will only be called back in UI thread to notify that a bitmap is done.
+ */
+public interface OnDoneBitmapCallback {
+
+    void onDone(Bitmap bitmap);
+}
diff --git a/src/com/android/gallery3d/photoeditor/OnDoneCallback.java b/src/com/android/gallery3d/photoeditor/OnDoneCallback.java
new file mode 100644 (file)
index 0000000..80fbf3c
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+/**
+ * Callback that will only be called back in UI thread to notify that an operation has been done.
+ */
+public interface OnDoneCallback {
+
+    void onDone();
+}
diff --git a/src/com/android/gallery3d/photoeditor/Photo.java b/src/com/android/gallery3d/photoeditor/Photo.java
new file mode 100644 (file)
index 0000000..4aaa906
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.graphics.Bitmap;
+
+/**
+ * Photo that holds a GL texture and all its methods must be only accessed from the GL thread.
+ */
+public class Photo {
+
+    private int texture;
+    private int width;
+    private int height;
+
+    /**
+     * Factory method to ensure every Photo instance holds a valid texture.
+     */
+    public static Photo create(Bitmap bitmap) {
+        return (bitmap != null) ? new Photo(
+                RendererUtils.createTexture(bitmap), bitmap.getWidth(), bitmap.getHeight()) : null;
+    }
+
+    public static Photo create(int width, int height) {
+        return new Photo(RendererUtils.createTexture(), width, height);
+    }
+
+    private Photo(int texture, int width, int height) {
+        this.texture = texture;
+        this.width = width;
+        this.height = height;
+    }
+
+    public int texture() {
+        return texture;
+    }
+
+    public boolean matchDimension(Photo photo) {
+        return ((photo.width() == width()) && (photo.height() == height()));
+    }
+
+    public void changeDimension(int width, int height) {
+        this.width = width;
+        this.height = height;
+        RendererUtils.clearTexture(texture);
+        texture = RendererUtils.createTexture();
+    }
+
+    public int width() {
+        return width;
+    }
+
+    public int height() {
+        return height;
+    }
+
+    public Bitmap save() {
+        return RendererUtils.saveTexture(texture, width, height);
+    }
+
+    /**
+     * Clears the texture; this instance should not be used after its clear() is called.
+     */
+    public void clear() {
+        RendererUtils.clearTexture(texture);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/PhotoEditor.java b/src/com/android/gallery3d/photoeditor/PhotoEditor.java
new file mode 100644 (file)
index 0000000..c7ec48f
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+/**
+ * Main activity of the photo editor.
+ */
+public class PhotoEditor extends Activity {
+
+    private Uri uri;
+    private FilterStack filterStack;
+    private Toolbar toolbar;
+    private View backButton;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.photoeditor_main);
+
+        Intent intent = getIntent();
+        uri = Intent.ACTION_EDIT.equalsIgnoreCase(intent.getAction()) ? intent.getData() : null;
+
+        final ActionBar actionBar = (ActionBar) findViewById(R.id.action_bar);
+        filterStack = new FilterStack((PhotoView) findViewById(R.id.photo_view), actionBar);
+        toolbar = (Toolbar) findViewById(R.id.toolbar);
+        toolbar.initialize(filterStack);
+
+        final EffectsBar effectsBar = (EffectsBar) findViewById(R.id.effects_bar);
+        backButton = findViewById(R.id.action_bar_back);
+        backButton.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (actionBar.isEnabled()) {
+                    // Exit effects or go back to the previous activity on pressing back button.
+                    if (!effectsBar.exit(null)) {
+                        tryRun(new Runnable() {
+
+                            @Override
+                            public void run() {
+                                finish();
+                            }
+                        });
+                    }
+                }
+            }
+        });
+    }
+
+    private void tryRun(final Runnable runnable) {
+        if (findViewById(R.id.save_button).isEnabled()) {
+            // Pop-up a dialog before executing the runnable to save unsaved photo.
+            AlertDialog.Builder builder = new AlertDialog.Builder(this)
+                    .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            toolbar.savePhoto(new Runnable() {
+
+                                @Override
+                                public void run() {
+                                    runnable.run();
+                                }
+                            });
+                        }
+                    })
+                    .setNeutralButton(R.string.no, new DialogInterface.OnClickListener() {
+
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            runnable.run();
+                        }
+                    })
+                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            // no-op
+                        }
+                    });
+            builder.setMessage(R.string.save_photo).show();
+            return;
+        }
+
+        runnable.run();
+    }
+
+    @Override
+    public void onBackPressed() {
+        backButton.performClick();
+    }
+
+    @Override
+    protected void onPause() {
+        // TODO: Close running spinner progress dialogs as all pending operations will be paused.
+        super.onPause();
+        filterStack.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        filterStack.onResume();
+        toolbar.openPhoto(uri);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/PhotoView.java b/src/com/android/gallery3d/photoeditor/PhotoView.java
new file mode 100644 (file)
index 0000000..8c758dd
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.content.Context;
+import android.graphics.RectF;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+
+import java.util.Vector;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Renders and displays photo in the surface view.
+ */
+public class PhotoView extends GLSurfaceView {
+
+    private final PhotoRenderer renderer;
+
+    public PhotoView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        renderer = new PhotoRenderer();
+        setEGLContextClientVersion(2);
+        setRenderer(renderer);
+        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+    }
+
+    public RectF getPhotoBounds() {
+        RectF photoBounds;
+        synchronized (renderer.photoBounds) {
+            photoBounds = new RectF(renderer.photoBounds);
+        }
+        return photoBounds;
+    }
+
+    /**
+     * Queues a runnable and renders a frame after execution. Queued runnables could be later
+     * removed by remove() or flush().
+     */
+    public void queue(Runnable r) {
+        renderer.queue.add(r);
+        requestRender();
+    }
+
+    /**
+     * Removes the specified queued runnable.
+     */
+    public void remove(Runnable runnable) {
+        renderer.queue.remove(runnable);
+    }
+
+    /**
+     * Flushes all queued runnables to cancel their execution.
+     */
+    public void flush() {
+        renderer.queue.clear();
+    }
+
+    /**
+     * Sets photo for display; this method must be queued for GL thread.
+     */
+    public void setPhoto(Photo photo) {
+        renderer.setPhoto(photo);
+    }
+
+    /**
+     * Rotates displayed photo; this method must be queued for GL thread.
+     */
+    public void rotatePhoto(float degrees) {
+        renderer.rotatePhoto(degrees);
+    }
+
+    /**
+     * Renderer that renders the GL surface-view and only be called from the GL thread.
+     */
+    private class PhotoRenderer implements GLSurfaceView.Renderer {
+
+        final Vector<Runnable> queue = new Vector<Runnable>();
+        final RectF photoBounds = new RectF();
+        RendererUtils.RenderContext renderContext;
+        Photo photo;
+        int viewWidth;
+        int viewHeight;
+
+        void setPhoto(Photo photo) {
+            int width = (photo != null) ? photo.width() : 0;
+            int height = (photo != null) ? photo.height() : 0;
+            synchronized (photoBounds) {
+                photoBounds.set(0, 0, width, height);
+            }
+            this.photo = photo;
+            fitPhotoToSurface();
+        }
+
+        void fitPhotoToSurface() {
+            if (photo != null) {
+                RendererUtils.setRenderToFit(renderContext, photo.width(), photo.height(),
+                        viewWidth, viewHeight);
+            }
+        }
+
+        void rotatePhoto(float degrees) {
+            if (photo != null) {
+                RendererUtils.setRenderToRotate(renderContext, photo.width(), photo.height(),
+                        viewWidth, viewHeight, degrees);
+            }
+        }
+
+        void renderPhoto() {
+            if (photo != null) {
+                RendererUtils.renderTexture(renderContext, photo.texture(), viewWidth, viewHeight);
+            }
+        }
+
+        @Override
+        public void onDrawFrame(GL10 gl) {
+            Runnable r = null;
+            synchronized (queue) {
+                if (!queue.isEmpty()) {
+                    r = queue.remove(0);
+                }
+            }
+            if (r != null) {
+                r.run();
+            }
+            if (!queue.isEmpty()) {
+                requestRender();
+            }
+            renderPhoto();
+        }
+
+        @Override
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            viewWidth = width;
+            viewHeight = height;
+            fitPhotoToSurface();
+        }
+
+        @Override
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            renderContext = RendererUtils.createProgram();
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/RendererUtils.java b/src/com/android/gallery3d/photoeditor/RendererUtils.java
new file mode 100644 (file)
index 0000000..a0cd598
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.graphics.Bitmap;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+/**
+ * Utils for GL renderer.
+ */
+public class RendererUtils {
+
+    public static class RenderContext {
+        private int shaderProgram;
+        private int texSamplerHandle;
+        private int texCoordHandle;
+        private int posCoordHandle;
+        private FloatBuffer texVertices;
+        private FloatBuffer posVertices;
+    }
+
+    private static final float[] TEX_VERTICES = {
+        0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f
+    };
+
+    private static final float[] POS_VERTICES = {
+        -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f
+    };
+
+    private static final String VERTEX_SHADER =
+            "attribute vec4 a_position;\n" +
+            "attribute vec2 a_texcoord;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  gl_Position = a_position;\n" +
+            "  v_texcoord = a_texcoord;\n" +
+            "}\n";
+
+    private static final String FRAGMENT_SHADER =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(tex_sampler, v_texcoord);\n" +
+            "}\n";
+
+    private static final int FLOAT_SIZE_BYTES = 4;
+    private static final float DEGREE_TO_RADIAN = (float) Math.PI / 180.0f;
+
+    public static int createTexture() {
+        int[] textures = new int[1];
+        GLES20.glGenTextures(textures.length, textures, 0);
+        checkGlError("glGenTextures");
+        return textures[0];
+    }
+
+    public static int createTexture(Bitmap bitmap) {
+        int texture = createTexture();
+
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
+        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
+        GLES20.glTexParameteri(
+                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(
+                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(
+                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(
+                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+        checkGlError("texImage2D");
+
+        return texture;
+    }
+
+    public static Bitmap saveTexture(int texture, int width, int height) {
+        int[] frame = new int[1];
+        GLES20.glGenFramebuffers(1, frame, 0);
+        checkGlError("glGenFramebuffers");
+        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frame[0]);
+        checkGlError("glBindFramebuffer");
+        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
+                GLES20.GL_TEXTURE_2D, texture, 0);
+        checkGlError("glFramebufferTexture2D");
+
+        ByteBuffer buffer = ByteBuffer.allocate(width * height * 4);
+        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer);
+        checkGlError("glReadPixels");
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        bitmap.copyPixelsFromBuffer(buffer);
+
+        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+        checkGlError("glBindFramebuffer");
+        GLES20.glDeleteFramebuffers(1, frame, 0);
+        checkGlError("glDeleteFramebuffer");
+        return bitmap;
+    }
+
+    public static void clearTexture(int texture) {
+        int[] textures = new int[1];
+        textures[0] = texture;
+        GLES20.glDeleteTextures(textures.length, textures, 0);
+        checkGlError("glDeleteTextures");
+    }
+
+    public static void setRenderToFit(RenderContext context, int srcWidth, int srcHeight,
+            int dstWidth, int dstHeight) {
+        float srcAspectRatio = ((float) srcWidth) / srcHeight;
+        float dstAspectRatio = ((float) dstWidth) / dstHeight;
+        float relativeAspectRatio = dstAspectRatio / srcAspectRatio;
+
+        float vertices[] = new float[8];
+        System.arraycopy(POS_VERTICES, 0, vertices, 0, vertices.length);
+        if (relativeAspectRatio > 1.0f) {
+            // Screen is wider than the camera, scale down X
+            vertices[0] /= relativeAspectRatio;
+            vertices[2] /= relativeAspectRatio;
+            vertices[4] /= relativeAspectRatio;
+            vertices[6] /= relativeAspectRatio;
+        } else {
+            vertices[1] *= relativeAspectRatio;
+            vertices[3] *= relativeAspectRatio;
+            vertices[5] *= relativeAspectRatio;
+            vertices[7] *= relativeAspectRatio;
+        }
+        context.posVertices = createVerticesBuffer(vertices);
+    }
+
+    public static void setRenderToRotate(RenderContext context, int srcWidth, int srcHeight,
+            int dstWidth, int dstHeight, float degrees) {
+        float cosTheta = (float) Math.cos(-degrees * DEGREE_TO_RADIAN);
+        float sinTheta = (float) Math.sin(-degrees * DEGREE_TO_RADIAN);
+        float cosWidth = cosTheta * srcWidth;
+        float sinWidth = sinTheta * srcWidth;
+        float cosHeight = cosTheta * srcHeight;
+        float sinHeight = sinTheta * srcHeight;
+
+        float[] vertices = new float[8];
+        vertices[0] = -cosWidth + sinHeight;
+        vertices[1] = -sinWidth - cosHeight;
+        vertices[2] = cosWidth + sinHeight;
+        vertices[3] = sinWidth - cosHeight;
+        vertices[4] = -vertices[2];
+        vertices[5] = -vertices[3];
+        vertices[6] = -vertices[0];
+        vertices[7] = -vertices[1];
+
+        float maxWidth = Math.max(Math.abs(vertices[0]), Math.abs(vertices[2]));
+        float maxHeight = Math.max(Math.abs(vertices[1]), Math.abs(vertices[3]));
+        float scale = Math.min(dstWidth / maxWidth, dstHeight / maxHeight);
+
+        for (int i = 0; i < 8; i += 2) {
+            vertices[i] *= scale / dstWidth;
+            vertices[i + 1] *= scale / dstHeight;
+        }
+        context.posVertices = createVerticesBuffer(vertices);
+    }
+
+    public static void renderTexture(
+            RenderContext context, int texture, int viewWidth, int viewHeight) {
+        // Use our shader program
+        GLES20.glUseProgram(context.shaderProgram);
+        checkGlError("glUseProgram");
+
+        // Set viewport
+        GLES20.glViewport(0, 0, viewWidth, viewHeight);
+        checkGlError("glViewport");
+
+        // Disable blending
+        GLES20.glDisable(GLES20.GL_BLEND);
+
+        // Set the vertex attributes
+        GLES20.glVertexAttribPointer(
+                context.texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, context.texVertices);
+        GLES20.glEnableVertexAttribArray(context.texCoordHandle);
+        GLES20.glVertexAttribPointer(
+                context.posCoordHandle, 2, GLES20.GL_FLOAT, false, 0, context.posVertices);
+        GLES20.glEnableVertexAttribArray(context.posCoordHandle);
+        checkGlError("vertex attribute setup");
+
+        // Set the input texture
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        checkGlError("glActiveTexture");
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
+        checkGlError("glBindTexture");
+        GLES20.glUniform1i(context.texSamplerHandle, 0);
+
+        // Draw!
+        GLES20.glClearColor(0, 0, 0, 1);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+    }
+
+    public static RenderContext createProgram() {
+        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
+        if (vertexShader == 0) {
+            return null;
+        }
+        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
+        if (pixelShader == 0) {
+            return null;
+        }
+
+        int program = GLES20.glCreateProgram();
+        if (program != 0) {
+            GLES20.glAttachShader(program, vertexShader);
+            checkGlError("glAttachShader");
+            GLES20.glAttachShader(program, pixelShader);
+            checkGlError("glAttachShader");
+            GLES20.glLinkProgram(program);
+            int[] linkStatus = new int[1];
+            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+            if (linkStatus[0] != GLES20.GL_TRUE) {
+                String info = GLES20.glGetProgramInfoLog(program);
+                GLES20.glDeleteProgram(program);
+                program = 0;
+                throw new RuntimeException("Could not link program: " + info);
+            }
+        }
+
+        // Bind attributes and uniforms
+        RenderContext context = new RenderContext();
+        context.texSamplerHandle = GLES20.glGetUniformLocation(program, "tex_sampler");
+        context.texCoordHandle = GLES20.glGetAttribLocation(program, "a_texcoord");
+        context.posCoordHandle = GLES20.glGetAttribLocation(program, "a_position");
+        context.texVertices = createVerticesBuffer(TEX_VERTICES);
+        context.posVertices = createVerticesBuffer(POS_VERTICES);
+
+        context.shaderProgram = program;
+        return context;
+    }
+
+    private static int loadShader(int shaderType, String source) {
+        int shader = GLES20.glCreateShader(shaderType);
+        if (shader != 0) {
+            GLES20.glShaderSource(shader, source);
+            GLES20.glCompileShader(shader);
+            int[] compiled = new int[1];
+            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+            if (compiled[0] == 0) {
+                String info = GLES20.glGetShaderInfoLog(shader);
+                GLES20.glDeleteShader(shader);
+                shader = 0;
+                throw new RuntimeException("Could not compile shader " + shaderType + ":" + info);
+            }
+        }
+        return shader;
+    }
+
+    private static FloatBuffer createVerticesBuffer(float[] vertices) {
+        if (vertices.length != 8) {
+            throw new RuntimeException("Number of vertices should be four.");
+        }
+
+        FloatBuffer buffer = ByteBuffer.allocateDirect(
+                vertices.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+        buffer.put(vertices).position(0);
+        return buffer;
+    }
+
+    private static void checkGlError(String op) {
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            throw new RuntimeException(op + ": glError " + error);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/SaveCopyTask.java b/src/com/android/gallery3d/photoeditor/SaveCopyTask.java
new file mode 100644 (file)
index 0000000..e43959e
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.view.Gravity;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+
+import java.io.File;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+
+/**
+ * Asynchronous task for saving edited photo as a new copy.
+ */
+public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
+
+    /**
+     * Callback for the completed asynchronous task.
+     */
+    public interface Callback {
+
+        void onComplete(Uri uri);
+    }
+
+    private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
+    private static final int INDEX_DATE_TAKEN = 0;
+    private static final int INDEX_LATITUDE = 1;
+    private static final int INDEX_LONGITUDE = 2;
+
+    private static final String[] IMAGE_PROJECTION = new String[] {
+        ImageColumns.DATE_TAKEN,
+        ImageColumns.LATITUDE,
+        ImageColumns.LONGITUDE,
+    };
+
+    private final Context context;
+    private final Uri sourceUri;
+    private final Callback callback;
+    private final String albumName;
+    private final String saveFileName;
+
+    public SaveCopyTask(Context context, Uri sourceUri, Callback callback) {
+        this.context = context;
+        this.sourceUri = sourceUri;
+        this.callback = callback;
+
+        albumName = context.getString(R.string.edited_photo_bucket_name);
+        saveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(
+                new Date(System.currentTimeMillis()));
+    }
+
+    /**
+     * The task should be executed with one given bitmap to be saved.
+     */
+    @Override
+    protected Uri doInBackground(Bitmap... params) {
+        // TODO: Support larger dimensions for photo saving.
+        if (params[0] == null) {
+            return null;
+        }
+        Bitmap bitmap = params[0];
+        File file = save(bitmap);
+        Uri uri = (file != null) ? insertContent(file) : null;
+        bitmap.recycle();
+        return uri;
+    }
+
+    @Override
+    protected void onPostExecute(Uri result) {
+        String message = (result == null) ? context.getString(R.string.saving_failure)
+                : context.getString(R.string.photo_saved, albumName);
+        Toast toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
+        toast.setGravity(Gravity.CENTER, 0, 0);
+        toast.show();
+
+        callback.onComplete(result);
+    }
+
+    private File save(Bitmap bitmap) {
+        String directory = Environment.getExternalStorageDirectory().toString() + "/" + albumName;
+        return new BitmapUtils(context).saveBitmap(
+                bitmap, directory, saveFileName, Bitmap.CompressFormat.JPEG);
+    }
+
+    /**
+     * Insert the content (saved file) with proper source photo properties.
+     */
+    private Uri insertContent(File file) {
+        long now = System.currentTimeMillis() / 1000;
+        long dateTaken = now;
+        double latitude = 0f;
+        double longitude = 0f;
+
+        ContentResolver contentResolver = context.getContentResolver();
+        Cursor cursor = contentResolver.query(
+                sourceUri, IMAGE_PROJECTION, null, null, null);
+        try {
+            if ((cursor != null) && cursor.moveToNext()) {
+                dateTaken = cursor.getLong(INDEX_DATE_TAKEN);
+                latitude = cursor.getDouble(INDEX_LATITUDE);
+                longitude = cursor.getDouble(INDEX_LONGITUDE);
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        ContentValues values = new ContentValues();
+        values.put(Images.Media.TITLE, saveFileName);
+        values.put(Images.Media.DISPLAY_NAME, saveFileName);
+        values.put(Images.Media.MIME_TYPE, "image/jpeg");
+        values.put(Images.Media.DATE_TAKEN, dateTaken);
+        values.put(Images.Media.DATE_MODIFIED, now);
+        values.put(Images.Media.DATE_ADDED, now);
+        values.put(Images.Media.ORIENTATION, 0);
+        values.put(Images.Media.DATA, file.getAbsolutePath());
+        values.put(Images.Media.SIZE, file.length());
+
+        // TODO: Change || to && after the default location issue is fixed.
+        if ((latitude != 0f) || (longitude != 0f)) {
+            values.put(Images.Media.LATITUDE, latitude);
+            values.put(Images.Media.LONGITUDE, longitude);
+        }
+        return contentResolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/SpinnerProgressDialog.java b/src/com/android/gallery3d/photoeditor/SpinnerProgressDialog.java
new file mode 100644 (file)
index 0000000..9a3d849
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.app.Dialog;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ProgressBar;
+
+import com.android.gallery3d.R;
+
+/**
+ * Spinner model progress dialog that disables all tools for user interaction after it shows up and
+ * and re-enables them after it dismisses.
+ */
+public class SpinnerProgressDialog extends Dialog {
+
+    private final ViewGroup tools;
+
+    public static SpinnerProgressDialog show(ViewGroup tools) {
+        SpinnerProgressDialog dialog = new SpinnerProgressDialog(tools);
+        dialog.setCancelable(false);
+        dialog.show();
+        return dialog;
+    }
+
+    private SpinnerProgressDialog(ViewGroup tools) {
+        super(tools.getContext(), R.style.SpinnerProgressDialog);
+
+        addContentView(new ProgressBar(tools.getContext()), new LayoutParams(
+                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+        this.tools = tools;
+        enableTools(false);
+    }
+
+    @Override
+    public void dismiss() {
+        super.dismiss();
+
+        enableTools(true);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        // Pass touch events to tools for killing idle even when the progress dialog is shown.
+        return tools.dispatchTouchEvent(event);
+    }
+
+    private void enableTools(boolean enabled) {
+        for (int i = 0; i < tools.getChildCount(); i++) {
+            tools.getChildAt(i).setEnabled(enabled);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/Toolbar.java b/src/com/android/gallery3d/photoeditor/Toolbar.java
new file mode 100644 (file)
index 0000000..72f243b
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.RelativeLayout;
+
+import com.android.gallery3d.R;
+
+/**
+ * Toolbar that contains all tools and handles all operations for editing photo.
+ */
+public class Toolbar extends RelativeLayout {
+
+    private final ToolbarIdleHandler idleHandler;
+    private FilterStack filterStack;
+    private EffectsBar effectsBar;
+    private ActionBar actionBar;
+    private Uri sourceUri;
+
+    public Toolbar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        idleHandler = new ToolbarIdleHandler(context);
+        setOnHierarchyChangeListener(idleHandler);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        idleHandler.killIdle();
+        return super.dispatchTouchEvent(ev);
+    }
+
+    public void initialize(FilterStack filterStack) {
+        this.filterStack = filterStack;
+        effectsBar = (EffectsBar) findViewById(R.id.effects_bar);
+        effectsBar.initialize(filterStack);
+        actionBar = (ActionBar) findViewById(R.id.action_bar);
+        actionBar.initialize(createActionBarListener());
+        idleHandler.killIdle();
+    }
+
+    private ActionBar.ActionBarListener createActionBarListener() {
+        actionBar = (ActionBar) findViewById(R.id.action_bar);
+        return new ActionBar.ActionBarListener() {
+
+            @Override
+            public void onUndo() {
+                effectsBar.exit(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(
+                                Toolbar.this);
+                        filterStack.undo(new OnDoneCallback() {
+
+                            @Override
+                            public void onDone() {
+                                progressDialog.dismiss();
+                            }
+                        });
+                    }
+                });
+            }
+
+            @Override
+            public void onRedo() {
+                effectsBar.exit(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(
+                                Toolbar.this);
+                        filterStack.redo(new OnDoneCallback() {
+
+                            @Override
+                            public void onDone() {
+                                progressDialog.dismiss();
+                            }
+                        });
+                    }
+                });
+            }
+
+            @Override
+            public void onSave() {
+                effectsBar.exit(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        savePhoto(null);
+                    }
+                });
+            }
+        };
+    }
+
+    public void openPhoto(Uri uri) {
+        sourceUri = uri;
+
+        final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(this);
+        new LoadScreennailTask(getContext(), new LoadScreennailTask.Callback() {
+
+            @Override
+            public void onComplete(final Bitmap bitmap) {
+                filterStack.setPhotoSource(bitmap, new OnDoneCallback() {
+
+                    @Override
+                    public void onDone() {
+                        progressDialog.dismiss();
+                    }
+                });
+            }
+        }).execute(sourceUri);
+    }
+
+    /**
+     * Saves photo and executes runnable (if provided) after saving done.
+     */
+    public void savePhoto(final Runnable runnable) {
+        final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(this);
+        filterStack.saveBitmap(new OnDoneBitmapCallback() {
+
+            @Override
+            public void onDone(Bitmap bitmap) {
+                new SaveCopyTask(getContext(), sourceUri, new SaveCopyTask.Callback() {
+
+                    @Override
+                    public void onComplete(Uri uri) {
+                        // TODO: Handle saving failure.
+                        progressDialog.dismiss();
+                        actionBar.disableSave();
+                        if (runnable != null) {
+                            runnable.run();
+                        }
+                    }
+                }).execute(bitmap);
+            }
+        });
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/ToolbarIdleHandler.java b/src/com/android/gallery3d/photoeditor/ToolbarIdleHandler.java
new file mode 100644 (file)
index 0000000..5cef566
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.view.ViewGroup.OnHierarchyChangeListener;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handler that controls idle/awake behaviors of toolbar's child views from UI thread.
+ */
+class ToolbarIdleHandler implements OnHierarchyChangeListener {
+
+    private static final int MAKE_IDLE = 1;
+    private static final int TIMEOUT_IDLE = 8000;
+
+    private final List<View> childViews = new ArrayList<View>();
+    private final Handler mainHandler;
+    private final Animation fadeIn;
+    private final Animation fadeOut;
+    private boolean idle;
+
+    public ToolbarIdleHandler(Context context) {
+        mainHandler = new Handler() {
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MAKE_IDLE:
+                        if (!idle) {
+                            idle = true;
+                            for (View view : childViews) {
+                                view.startAnimation(fadeOut);
+                            }
+                        }
+                        break;
+                }
+            }
+        };
+
+        fadeIn = AnimationUtils.loadAnimation(context, R.anim.photoeditor_fade_in);
+        fadeOut = AnimationUtils.loadAnimation(context, R.anim.photoeditor_fade_out);
+    }
+
+    public void killIdle() {
+        mainHandler.removeMessages(MAKE_IDLE);
+        if (idle) {
+            idle = false;
+            for (View view : childViews) {
+                view.startAnimation(fadeIn);
+            }
+        }
+        mainHandler.sendEmptyMessageDelayed(MAKE_IDLE, TIMEOUT_IDLE);
+    }
+
+    @Override
+    public void onChildViewAdded(View parent, View child) {
+        // All child views, except photo-view, will fade out on inactivity timeout.
+        if (child.getId() != R.id.photo_view) {
+            childViews.add(child);
+        }
+    }
+
+    @Override
+    public void onChildViewRemoved(View parent, View child) {
+        childViews.remove(child);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/AbstractSeekBar.java b/src/com/android/gallery3d/photoeditor/actions/AbstractSeekBar.java
new file mode 100644 (file)
index 0000000..27a0bce
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+import com.android.gallery3d.R;
+
+/**
+ * Seek-bar base that implements a draggable thumb that fits seek-bar height.
+ */
+abstract class AbstractSeekBar extends SeekBar {
+
+    public AbstractSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        // Scale the thumb to fit seek-bar height.
+        Resources res = getResources();
+        Drawable thumb = res.getDrawable(R.drawable.photoeditor_seekbar_thumb);
+
+        // Set the left/right padding to half width of the thumb drawn.
+        int scaledWidth = thumb.getIntrinsicWidth() * h / thumb.getIntrinsicHeight();
+        int padding = (scaledWidth + 1) / 2;
+        setPadding(padding, 0, padding, 0);
+
+        Bitmap bitmap = Bitmap.createBitmap(scaledWidth, h, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        thumb.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        thumb.draw(canvas);
+
+        setThumb(new BitmapDrawable(res, bitmap));
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java b/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java
new file mode 100644 (file)
index 0000000..26b5f51
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.AutoFixFilter;
+
+/**
+ * An action handling auto-fix effect.
+ */
+public class AutoFixAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    public AutoFixAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        AutoFixFilter filter = new AutoFixFilter();
+        filter.setScale(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+        notifyDone();
+    }
+
+    @Override
+    public void doEnd() {
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/ColorSeekBar.java b/src/com/android/gallery3d/photoeditor/actions/ColorSeekBar.java
new file mode 100644 (file)
index 0000000..5f9809b
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Region.Op;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+import com.android.gallery3d.R;
+
+/**
+ * Seek-bar that has a draggable thumb to set and get the color from predefined color set.
+ */
+class ColorSeekBar extends AbstractSeekBar {
+
+    /**
+     * Listens to color changes.
+     */
+    public interface OnColorChangeListener {
+
+        void onColorChanged(int color, boolean fromUser);
+    }
+
+    private final int[] colors;
+    private Bitmap background;
+
+    public ColorSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // Set up the predefined colors that could be indexed in the seek-bar.
+        TypedArray a = getResources().obtainTypedArray(R.array.color_picker_colors);
+        colors = new int[a.length()];
+        for (int i = 0; i < a.length(); i++) {
+            colors[i] = a.getColor(i, 0x000000);
+        }
+        a.recycle();
+        setMax(colors.length - 1);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        if (background != null) {
+            background.recycle();
+        }
+        background = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(background);
+
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setStyle(Paint.Style.FILL);
+
+        // Draw two half circles in the first and last colors at seek-bar left/right ends.
+        int radius = getThumbOffset();
+        float left = radius;
+        float right = w - radius;
+        float cy = h / 2;
+
+        canvas.save();
+        canvas.clipRect(left, 0, right, h, Op.DIFFERENCE);
+        paint.setColor(colors[0]);
+        canvas.drawCircle(left, cy, radius, paint);
+        paint.setColor(colors[colors.length - 1]);
+        canvas.drawCircle(right, cy, radius, paint);
+        canvas.restore();
+
+        // Draw color strips that make the thumb stop at every strip's center during seeking.
+        float strip = (right - left) / (colors.length - 1);
+        right = left + strip / 2;
+        paint.setColor(colors[0]);
+        canvas.drawRect(left, 0, right, h, paint);
+        left = right;
+        for (int i = 1; i < colors.length - 1; i++) {
+            right = left + strip;
+            paint.setColor(colors[i]);
+            canvas.drawRect(left, 0, right, h, paint);
+            left = right;
+        }
+        right = left + strip / 2;
+        paint.setColor(colors[colors.length - 1]);
+        canvas.drawRect(left, 0, right, h, paint);
+
+        setBackgroundDrawable(new BitmapDrawable(getResources(), background));
+    }
+
+    public void setOnColorChangeListener(final OnColorChangeListener listener) {
+        setOnSeekBarChangeListener((listener == null) ? null : new OnSeekBarChangeListener() {
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                listener.onColorChanged(colors[progress], fromUser);
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+        });
+    }
+
+    public void setColorIndex(int colorIndex) {
+        setProgress(colorIndex);
+    }
+
+    public int getColor() {
+        return colors[getProgress()];
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java b/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java
new file mode 100644 (file)
index 0000000..41a89db
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.ColorTemperatureFilter;
+
+/**
+ * An action handling color temperature effect.
+ */
+public class ColorTemperatureAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    private ScaleSeekBar scalePicker;
+
+    public ColorTemperatureAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final ColorTemperatureFilter filter = new ColorTemperatureFilter();
+
+        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.COLOR);
+        scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setColorTemperature(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scalePicker.setProgress(DEFAULT_SCALE);
+    }
+
+    @Override
+    public void doEnd() {
+        scalePicker.setOnScaleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/CropAction.java b/src/com/android/gallery3d/photoeditor/actions/CropAction.java
new file mode 100644 (file)
index 0000000..60a0179
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.CropFilter;
+
+/**
+ * An action handling crop effect.
+ */
+public class CropAction extends EffectAction {
+
+    private static final float DEFAULT_CROP = 0.2f;
+
+    private CropFilter filter;
+    private CropView cropView;
+
+    public CropAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        filter = new CropFilter();
+
+        cropView = factory.createCropView();
+        cropView.setOnCropChangeListener(new CropView.OnCropChangeListener() {
+
+            @Override
+            public void onCropChanged(RectF cropBounds, boolean fromUser) {
+                if (fromUser) {
+                    filter.setCropBounds(cropBounds);
+                    notifyFilterChanged(filter, false);
+                }
+            }
+        });
+
+        RectF bounds = new RectF(DEFAULT_CROP, DEFAULT_CROP, 1 - DEFAULT_CROP, 1 - DEFAULT_CROP);
+        cropView.setCropBounds(bounds);
+        filter.setCropBounds(bounds);
+        notifyFilterChanged(filter, false);
+    }
+
+    @Override
+    public void doEnd() {
+        cropView.setOnCropChangeListener(null);
+        notifyFilterChanged(filter, true);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/CropView.java b/src/com/android/gallery3d/photoeditor/actions/CropView.java
new file mode 100644 (file)
index 0000000..75d74fc
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.R;
+
+/**
+ * A view that tracks touch motions and adjusts crop bounds accordingly.
+ */
+class CropView extends FullscreenToolView {
+
+    /**
+     * Listener of crop bounds.
+     */
+    public interface OnCropChangeListener {
+
+        void onCropChanged(RectF cropBounds, boolean fromUser);
+    }
+
+    private static final int MOVE_LEFT = 1;
+    private static final int MOVE_TOP = 2;
+    private static final int MOVE_RIGHT = 4;
+    private static final int MOVE_BOTTOM = 8;
+    private static final int MOVE_BLOCK = 16;
+
+    private static final int MIN_CROP_WIDTH_HEIGHT = 2;
+    private static final int TOUCH_TOLERANCE = 25;
+    private static final int SHADOW_ALPHA = 160;
+    private static final int BORDER_COLOR = 0xFF008AFF;
+    private static final float BORDER_WIDTH = 2.0f;
+
+    private final Paint borderPaint;
+    private final Drawable heightIndicator;
+    private final Drawable widthIndicator;
+    private final int indicatorSize;
+    private final RectF cropBounds = new RectF(0, 0, 1, 1);
+
+    private float lastX;
+    private float lastY;
+    private int movingEdges;
+    private OnCropChangeListener listener;
+
+    public CropView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        Resources resources = context.getResources();
+        heightIndicator = resources.getDrawable(R.drawable.camera_crop_height_holo);
+        widthIndicator = resources.getDrawable(R.drawable.camera_crop_width_holo);
+        indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size);
+
+        borderPaint = new Paint();
+        borderPaint.setStyle(Paint.Style.STROKE);
+        borderPaint.setColor(BORDER_COLOR);
+        borderPaint.setStrokeWidth(BORDER_WIDTH);
+    }
+
+    public void setOnCropChangeListener(OnCropChangeListener listener) {
+        this.listener = listener;
+    }
+
+    private void refreshByCropChange(boolean fromUser) {
+        if (listener != null) {
+            listener.onCropChanged(new RectF(cropBounds), fromUser);
+        }
+        invalidate();
+    }
+
+    /**
+     * Sets cropped bounds; modifies the bounds if it's smaller than the allowed dimensions.
+     */
+    public void setCropBounds(RectF bounds) {
+        // Avoid cropping smaller than minimum width or height.
+        if (bounds.width() * getPhotoWidth() < MIN_CROP_WIDTH_HEIGHT) {
+            bounds.set(0, bounds.top, 1, bounds.bottom);
+        }
+        if (bounds.height() * getPhotoHeight() < MIN_CROP_WIDTH_HEIGHT) {
+            bounds.set(bounds.left, 0, bounds.right, 1);
+        }
+        cropBounds.set(bounds);
+        refreshByCropChange(false);
+    }
+
+    private RectF getCropBoundsDisplayed() {
+        float width = displayBounds.width();
+        float height = displayBounds.height();
+        RectF cropped = new RectF(cropBounds.left * width, cropBounds.top * height,
+                cropBounds.right * width, cropBounds.bottom * height);
+        cropped.offset(displayBounds.left, displayBounds.top);
+        return cropped;
+    }
+
+    private void detectMovingEdges(float x, float y) {
+        RectF cropped = getCropBoundsDisplayed();
+        movingEdges = 0;
+
+        // Check left or right.
+        float left = Math.abs(x - cropped.left);
+        float right = Math.abs(x - cropped.right);
+        if ((left <= TOUCH_TOLERANCE) && (left < right)) {
+            movingEdges |= MOVE_LEFT;
+        }
+        else if (right <= TOUCH_TOLERANCE) {
+            movingEdges |= MOVE_RIGHT;
+        }
+
+        // Check top or bottom.
+        float top = Math.abs(y - cropped.top);
+        float bottom = Math.abs(y - cropped.bottom);
+        if ((top <= TOUCH_TOLERANCE) & (top < bottom)) {
+            movingEdges |= MOVE_TOP;
+        }
+        else if (bottom <= TOUCH_TOLERANCE) {
+            movingEdges |= MOVE_BOTTOM;
+        }
+
+        // Check inside block.
+        if (cropped.contains(x, y) && (movingEdges == 0)) {
+            movingEdges = MOVE_BLOCK;
+        }
+        invalidate();
+    }
+
+    private void moveEdges(float deltaX, float deltaY) {
+        RectF cropped = getCropBoundsDisplayed();
+        if (movingEdges == MOVE_BLOCK) {
+            // Move the whole cropped bounds within the photo display bounds.
+            deltaX = (deltaX > 0) ? Math.min(displayBounds.right - cropped.right, deltaX)
+                    : Math.max(displayBounds.left - cropped.left, deltaX);
+            deltaY = (deltaY > 0) ? Math.min(displayBounds.bottom - cropped.bottom, deltaY)
+                    : Math.max(displayBounds.top - cropped.top, deltaY);
+            cropped.offset(deltaX, deltaY);
+        } else {
+            // Adjust cropped bound dimensions within the photo display bounds.
+            float minWidth = MIN_CROP_WIDTH_HEIGHT * displayBounds.width() / getPhotoWidth();
+            float minHeight = MIN_CROP_WIDTH_HEIGHT * displayBounds.height() / getPhotoHeight();
+            if ((movingEdges & MOVE_LEFT) != 0) {
+                cropped.left = Math.min(cropped.left + deltaX, cropped.right - minWidth);
+            }
+            if ((movingEdges & MOVE_TOP) != 0) {
+                cropped.top = Math.min(cropped.top + deltaY, cropped.bottom - minHeight);
+            }
+            if ((movingEdges & MOVE_RIGHT) != 0) {
+                cropped.right = Math.max(cropped.right + deltaX, cropped.left + minWidth);
+            }
+            if ((movingEdges & MOVE_BOTTOM) != 0) {
+                cropped.bottom = Math.max(cropped.bottom + deltaY, cropped.top + minHeight);
+            }
+            cropped.intersect(displayBounds);
+        }
+        mapPhotoRect(cropped, cropBounds);
+        refreshByCropChange(true);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        if (isEnabled()) {
+            float x = event.getX();
+            float y = event.getY();
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    detectMovingEdges(x, y);
+                    lastX = x;
+                    lastY = y;
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    if (movingEdges != 0) {
+                        moveEdges(x - lastX, y - lastY);
+                    }
+                    lastX = x;
+                    lastY = y;
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    movingEdges = 0;
+                    invalidate();
+                    break;
+            }
+        }
+        return true;
+    }
+
+    private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) {
+        int left = (int) centerX - indicatorSize / 2;
+        int top = (int) centerY - indicatorSize / 2;
+        indicator.setBounds(left, top, left + indicatorSize, top + indicatorSize);
+        indicator.draw(canvas);
+    }
+
+    private void drawShadow(Canvas canvas, float left, float top, float right, float bottom) {
+        canvas.save();
+        canvas.clipRect(left, top, right, bottom);
+        canvas.drawARGB(SHADOW_ALPHA, 0, 0, 0);
+        canvas.restore();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // Draw shadow on non-cropped bounds and the border around cropped bounds.
+        RectF cropped = getCropBoundsDisplayed();
+        drawShadow(canvas, displayBounds.left, displayBounds.top, displayBounds.right, cropped.top);
+        drawShadow(canvas, displayBounds.left, cropped.top, cropped.left, displayBounds.bottom);
+        drawShadow(canvas, cropped.right, cropped.top, displayBounds.right, displayBounds.bottom);
+        drawShadow(canvas, cropped.left, cropped.bottom, cropped.right, displayBounds.bottom);
+        canvas.drawRect(cropped, borderPaint);
+
+        boolean block = movingEdges == MOVE_BLOCK;
+        if (((movingEdges & MOVE_TOP) != 0) || block) {
+            drawIndicator(canvas, heightIndicator, cropped.centerX(), cropped.top);
+        }
+        if (((movingEdges & MOVE_BOTTOM) != 0) || block) {
+            drawIndicator(canvas, heightIndicator, cropped.centerX(), cropped.bottom);
+        }
+        if (((movingEdges & MOVE_LEFT) != 0) || block) {
+            drawIndicator(canvas, widthIndicator, cropped.left, cropped.centerY());
+        }
+        if (((movingEdges & MOVE_RIGHT) != 0) || block) {
+            drawIndicator(canvas, widthIndicator, cropped.right, cropped.centerY());
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java b/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java
new file mode 100644 (file)
index 0000000..8be60d3
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.CrossProcessFilter;
+
+/**
+ * An action handling cross-process effect.
+ */
+public class CrossProcessAction extends EffectAction {
+
+    public CrossProcessAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        notifyFilterChanged(new CrossProcessFilter(), true);
+        notifyDone();
+    }
+
+    @Override
+    public void doEnd() {
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java b/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java
new file mode 100644 (file)
index 0000000..0ec4a02
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.DocumentaryFilter;
+
+/**
+ * An action handling the preset "Documentary" effect.
+ */
+public class DocumentaryAction extends EffectAction {
+
+    public DocumentaryAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        notifyFilterChanged(new DocumentaryFilter(), true);
+        notifyDone();
+    }
+
+    @Override
+    public void doEnd() {
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java b/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java
new file mode 100644 (file)
index 0000000..b82414d
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.graphics.Path;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.DoodleFilter;
+
+/**
+ * An action handling doodle effect.
+ */
+public class DoodleAction extends EffectAction {
+
+    private static final int DEFAULT_COLOR_INDEX = 4;
+
+    private DoodleFilter filter;
+    private ColorSeekBar colorPicker;
+    private DoodleView doodleView;
+
+    public DoodleAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        filter = new DoodleFilter();
+
+        colorPicker = factory.createColorPicker();
+        colorPicker.setOnColorChangeListener(new ColorSeekBar.OnColorChangeListener() {
+
+            @Override
+            public void onColorChanged(int color, boolean fromUser) {
+                if (fromUser) {
+                    doodleView.setColor(color);
+                }
+            }
+        });
+        colorPicker.setColorIndex(DEFAULT_COLOR_INDEX);
+
+        doodleView = factory.createDoodleView();
+        doodleView.setOnDoodleChangeListener(new DoodleView.OnDoodleChangeListener() {
+
+            @Override
+            public void onDoodleInPhotoBounds() {
+                // Notify the user has drawn within photo bounds and made visible changes on photo.
+                filter.setDoodledInPhotoBounds();
+                notifyFilterChanged(filter, false);
+            }
+
+            @Override
+            public void onDoodleFinished(Path path, int color) {
+                filter.addPath(path, color);
+                notifyFilterChanged(filter, false);
+            }
+        });
+        doodleView.setColor(colorPicker.getColor());
+    }
+
+    @Override
+    public void doEnd() {
+        colorPicker.setOnColorChangeListener(null);
+        doodleView.setOnDoodleChangeListener(null);
+        notifyFilterChanged(filter, true);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/DoodlePaint.java b/src/com/android/gallery3d/photoeditor/actions/DoodlePaint.java
new file mode 100644 (file)
index 0000000..bcde9f1
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.graphics.Paint;
+
+/**
+ * A paint class for doodle effect.
+ */
+public class DoodlePaint extends Paint {
+
+    public DoodlePaint() {
+        super(Paint.DITHER_FLAG | Paint.ANTI_ALIAS_FLAG);
+
+        setStyle(Paint.Style.STROKE);
+        setStrokeJoin(Paint.Join.ROUND);
+        setStrokeCap(Paint.Cap.ROUND);
+        setStrokeWidth(15);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/DoodleView.java b/src/com/android/gallery3d/photoeditor/actions/DoodleView.java
new file mode 100644 (file)
index 0000000..cc5af84
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+/**
+ * A view that tracks touch motions as paths and paints them as doodles.
+ */
+class DoodleView extends FullscreenToolView {
+
+    /**
+     * Listener of doodle paths.
+     */
+    public interface OnDoodleChangeListener {
+
+        void onDoodleInPhotoBounds();
+
+        void onDoodleFinished(Path path, int color);
+    }
+
+    private final Path normalizedPath = new Path();
+    private final Path drawingPath = new Path();
+    private final Paint doodlePaint = new DoodlePaint();
+    private final Paint bitmapPaint = new Paint(Paint.DITHER_FLAG);
+    private final PointF lastPoint = new PointF();
+    private final Matrix pathMatrix = new Matrix();
+    private final Matrix displayMatrix = new Matrix();
+
+    private Bitmap bitmap;
+    private Canvas bitmapCanvas;
+    private OnDoodleChangeListener listener;
+
+    public DoodleView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setOnDoodleChangeListener(OnDoodleChangeListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        RectF r = new RectF(0, 0, getPhotoWidth(), getPhotoHeight());
+        if ((bitmap == null) && !r.isEmpty()) {
+            bitmap = Bitmap.createBitmap((int) r.width(), (int) r.height(),
+                    Bitmap.Config.ARGB_8888);
+            bitmap.eraseColor(0x00000000);
+            bitmapCanvas = new Canvas(bitmap);
+
+            // Set up a matrix that maps back normalized paths to be drawn on the bitmap or canvas.
+            pathMatrix.setRectToRect(new RectF(0, 0, 1, 1), r, Matrix.ScaleToFit.FILL);
+        }
+        displayMatrix.setRectToRect(r, displayBounds, Matrix.ScaleToFit.FILL);
+    }
+
+    private void drawDoodle(Canvas canvas) {
+        if ((canvas != null) && !normalizedPath.isEmpty()) {
+            drawingPath.set(normalizedPath);
+            drawingPath.transform(pathMatrix);
+            canvas.drawPath(drawingPath, doodlePaint);
+        }
+    }
+
+    public void setColor(int color) {
+        // Reset path to draw in a new color.
+        finishCurrentPath();
+        normalizedPath.moveTo(lastPoint.x, lastPoint.y);
+        doodlePaint.setColor(Color.argb(192, Color.red(color), Color.green(color),
+                Color.blue(color)));
+    }
+
+    private void finishCurrentPath() {
+        if (!normalizedPath.isEmpty()) {
+            // Update the finished path to the bitmap.
+            drawDoodle(bitmapCanvas);
+            if (listener != null) {
+                listener.onDoodleFinished(new Path(normalizedPath), doodlePaint.getColor());
+            }
+            normalizedPath.rewind();
+            invalidate();
+        }
+    }
+
+    private void checkCurrentPathInBounds() {
+        if ((listener != null) && !normalizedPath.isEmpty()) {
+            RectF r = new RectF();
+            normalizedPath.computeBounds(r, false);
+            if (r.intersects(0, 0, 1, 1)) {
+                listener.onDoodleInPhotoBounds();
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        if (isEnabled()) {
+            float x = event.getX();
+            float y = event.getY();
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    mapPhotoPoint(x, y, lastPoint);
+                    normalizedPath.moveTo(lastPoint.x, lastPoint.y);
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    float lastX = lastPoint.x;
+                    float lastY = lastPoint.y;
+                    mapPhotoPoint(x, y, lastPoint);
+                    normalizedPath.quadTo(lastX, lastY, (lastX + lastPoint.x) / 2,
+                            (lastY + lastPoint.y) / 2);
+                    checkCurrentPathInBounds();
+                    invalidate();
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    // Line to last position with offset to draw at least dots for single clicks.
+                    mapPhotoPoint(x + 1, y + 1, lastPoint);
+                    normalizedPath.lineTo(lastPoint.x, lastPoint.y);
+                    checkCurrentPathInBounds();
+                    finishCurrentPath();
+                    break;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        canvas.save();
+        canvas.clipRect(displayBounds);
+        canvas.concat(displayMatrix);
+        if (bitmap != null) {
+            canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
+        }
+        drawDoodle(canvas);
+        canvas.restore();
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java b/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java
new file mode 100644 (file)
index 0000000..b8da71e
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.DuotoneFilter;
+
+/**
+ * An action handling duo-tone effect.
+ */
+public class DuotoneAction extends EffectAction {
+
+    private static final int DEFAULT_FIRST_COLOR = 0x004488;
+    private static final int DEFAULT_SECOND_COLOR = 0xffff00;
+
+    public DuotoneAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        // TODO: Add several sets of duo-tone colors to select from.
+        DuotoneFilter filter = new DuotoneFilter();
+        filter.setDuotone(DEFAULT_FIRST_COLOR, DEFAULT_SECOND_COLOR);
+        notifyFilterChanged(filter, true);
+        notifyDone();
+    }
+
+    @Override
+    public void doEnd() {
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/EffectAction.java b/src/com/android/gallery3d/photoeditor/actions/EffectAction.java
new file mode 100644 (file)
index 0000000..6c6a893
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.photoeditor.FilterStack;
+import com.android.gallery3d.photoeditor.OnDoneCallback;
+import com.android.gallery3d.photoeditor.filters.Filter;
+
+/**
+ * An action binding UI controls and effect operation for editing photo.
+ */
+public abstract class EffectAction extends LinearLayout {
+
+    /**
+     * Listener of effect action.
+     */
+    public interface Listener {
+
+        void onClick();
+
+        void onDone();
+    }
+
+    protected EffectToolFactory factory;
+
+    private Listener listener;
+    private Toast tooltip;
+    private FilterStack filterStack;
+    private boolean pushedFilter;
+    private FilterChangedCallback lastFilterChangedCallback;
+
+    public EffectAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setListener(Listener l) {
+        listener = l;
+        findViewById(R.id.effect_button).setOnClickListener(
+                (listener == null) ? null : new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                listener.onClick();
+            }
+        });
+    }
+
+    public CharSequence name() {
+        return ((TextView) findViewById(R.id.effect_label)).getText();
+    }
+
+    public void begin(FilterStack filterStack, EffectToolFactory factory) {
+        // This view is already detached from UI view hierarchy by reaching here; findViewById()
+        // could only access its own child views from here.
+        this.filterStack = filterStack;
+        this.factory = factory;
+
+        // Shows the tooltip if it's available.
+        if (getTag() != null) {
+            tooltip = Toast.makeText(getContext(), (String) getTag(), Toast.LENGTH_SHORT);
+            tooltip.setGravity(Gravity.CENTER, 0, 0);
+            tooltip.show();
+        }
+        doBegin();
+    }
+
+    /**
+     * Ends the effect and then executes the runnable after the effect is finished.
+     */
+    public void end(final Runnable runnableOnODone) {
+        doEnd();
+
+        // Wait till last output callback is done before finishing.
+        if ((lastFilterChangedCallback == null) || lastFilterChangedCallback.done) {
+            finish(runnableOnODone);
+        } else {
+            lastFilterChangedCallback.runnableOnReady = new Runnable() {
+
+                @Override
+                public void run() {
+                    finish(runnableOnODone);
+                }
+            };
+        }
+    }
+
+    private void finish(Runnable runnableOnDone) {
+        // Close the tooltip if it's still showing.
+        if ((tooltip != null) && (tooltip.getView().getParent() != null)) {
+            tooltip.cancel();
+            tooltip = null;
+        }
+        pushedFilter = false;
+        lastFilterChangedCallback = null;
+
+        runnableOnDone.run();
+    }
+
+    protected void notifyDone() {
+        if (listener != null) {
+            listener.onDone();
+        }
+    }
+
+    protected void notifyFilterChanged(Filter filter, boolean output) {
+        if (!pushedFilter && filter.isValid()) {
+            filterStack.pushFilter(filter);
+            pushedFilter = true;
+        }
+        if (pushedFilter && output) {
+            // Notify the stack to execute the changed top filter and output the results.
+            lastFilterChangedCallback = new FilterChangedCallback();
+            filterStack.topFilterChanged(lastFilterChangedCallback);
+        }
+    }
+
+    /**
+     * Subclasses should creates a specific filter and binds the filter to necessary UI controls
+     * here when the action is about to begin.
+     */
+    protected abstract void doBegin();
+
+    /**
+     * Subclasses could do specific ending operations here when the action is about to end.
+     */
+    protected abstract void doEnd();
+
+    /**
+     * Done callback for executing top filter changes.
+     */
+    private class FilterChangedCallback implements OnDoneCallback {
+
+        private boolean done;
+        private Runnable runnableOnReady;
+
+        @Override
+        public void onDone() {
+            done = true;
+
+            if (runnableOnReady != null) {
+                runnableOnReady.run();
+            }
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java b/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java
new file mode 100644 (file)
index 0000000..4bc49c5
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.photoeditor.PhotoView;
+
+/**
+ * Factory to create tools that will be used by effect actions.
+ */
+public class EffectToolFactory {
+
+    public enum ScalePickerType {
+        FILLLIGHT, HIGHLIGHT, SHADOW, COLOR, GENERIC
+    }
+
+    private final ViewGroup effectToolPanel;
+    private final LayoutInflater inflater;
+
+    public EffectToolFactory(ViewGroup effectToolPanel, LayoutInflater inflater) {
+        this.effectToolPanel = effectToolPanel;
+        this.inflater = inflater;
+    }
+
+    private View createFullscreenTool(int toolId) {
+        // Create full screen effect tool on top of photo-view and place it within the same
+        // view group that contains photo-view.
+        View photoView = effectToolPanel.getRootView().findViewById(R.id.photo_view);
+        ViewGroup parent = (ViewGroup) photoView.getParent();
+        FullscreenToolView view = (FullscreenToolView) inflater.inflate(toolId, parent, false);
+        view.setPhotoBounds(((PhotoView) photoView).getPhotoBounds());
+        parent.addView(view, parent.indexOfChild(photoView) + 1);
+        return view;
+    }
+
+    private View createPanelTool(int toolId) {
+        View view = inflater.inflate(toolId, effectToolPanel, false);
+        effectToolPanel.addView(view, 0);
+        return view;
+    }
+
+    private int getScalePickerBackground(ScalePickerType type) {
+        switch (type) {
+            case FILLLIGHT:
+                return R.drawable.photoeditor_scale_seekbar_filllight;
+
+            case HIGHLIGHT:
+                return R.drawable.photoeditor_scale_seekbar_highlight;
+
+            case SHADOW:
+                return R.drawable.photoeditor_scale_seekbar_shadow;
+
+            case COLOR:
+                return R.drawable.photoeditor_scale_seekbar_color;
+        }
+        return R.drawable.photoeditor_scale_seekbar_generic;
+    }
+
+    public ScaleSeekBar createScalePicker(ScalePickerType type) {
+        ScaleSeekBar scalePicker = (ScaleSeekBar) createPanelTool(
+                R.layout.photoeditor_scale_seekbar);
+        scalePicker.setBackgroundResource(getScalePickerBackground(type));
+        return scalePicker;
+    }
+
+    public ColorSeekBar createColorPicker() {
+        return (ColorSeekBar) createPanelTool(R.layout.photoeditor_color_seekbar);
+    }
+
+    public DoodleView createDoodleView() {
+        return (DoodleView) createFullscreenTool(R.layout.photoeditor_doodle_view);
+    }
+
+    public TouchView createTouchView() {
+        return (TouchView) createFullscreenTool(R.layout.photoeditor_touch_view);
+    }
+
+    public RotateView createRotateView() {
+        return (RotateView) createFullscreenTool(R.layout.photoeditor_rotate_view);
+    }
+
+    public CropView createCropView() {
+        return (CropView) createFullscreenTool(R.layout.photoeditor_crop_view);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java b/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java
new file mode 100644 (file)
index 0000000..e5a743c
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.FillLightFilter;
+
+/**
+ * An action handling fill-light effect.
+ */
+public class FillLightAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0f;
+
+    private ScaleSeekBar scalePicker;
+
+    public FillLightAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final FillLightFilter filter = new FillLightFilter();
+
+        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.FILLLIGHT);
+        scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setBacklight(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scalePicker.setProgress(DEFAULT_SCALE);
+    }
+
+    @Override
+    public void doEnd() {
+        scalePicker.setOnScaleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java b/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java
new file mode 100644 (file)
index 0000000..348f004
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.FisheyeFilter;
+
+/**
+ * An action handling fisheye effect.
+ */
+public class FisheyeAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    private ScaleSeekBar scalePicker;
+
+    public FisheyeAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final FisheyeFilter filter = new FisheyeFilter();
+
+        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setScale(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scalePicker.setProgress(DEFAULT_SCALE);
+
+        filter.setScale(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void doEnd() {
+        scalePicker.setOnScaleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FlipAction.java b/src/com/android/gallery3d/photoeditor/actions/FlipAction.java
new file mode 100644 (file)
index 0000000..00abc60
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.FlipFilter;
+
+/**
+ * An action handling flip effect.
+ */
+public class FlipAction extends EffectAction {
+
+    private boolean flipHorizontal;
+    private boolean flipVertical;
+    private TouchView touchView;
+
+    public FlipAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final FlipFilter filter = new FlipFilter();
+
+        touchView = factory.createTouchView();
+        touchView.setSwipeListener(new TouchView.SwipeListener() {
+
+            @Override
+            public void onSwipeDown() {
+                flipFilterVertically(filter);
+            }
+
+            @Override
+            public void onSwipeLeft() {
+                flipFilterHorizontally(filter);
+            }
+
+            @Override
+            public void onSwipeRight() {
+                flipFilterHorizontally(filter);
+            }
+
+            @Override
+            public void onSwipeUp() {
+                flipFilterVertically(filter);
+            }
+        });
+
+        flipHorizontal = false;
+        flipVertical = false;
+        flipFilterHorizontally(filter);
+    }
+
+    @Override
+    public void doEnd() {
+        touchView.setSwipeListener(null);
+    }
+
+    private void flipFilterHorizontally(final FlipFilter filter) {
+        flipHorizontal = !flipHorizontal;
+        filter.setFlip(flipHorizontal, flipVertical);
+        notifyFilterChanged(filter, true);
+    }
+
+    private void flipFilterVertically(final FlipFilter filter) {
+        flipVertical = !flipVertical;
+        filter.setFlip(flipHorizontal, flipVertical);
+        notifyFilterChanged(filter, true);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FullscreenToolView.java b/src/com/android/gallery3d/photoeditor/actions/FullscreenToolView.java
new file mode 100644 (file)
index 0000000..5396d16
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Full-screen tool view that gets photo display bounds and maps positions on photo display bounds
+ * back to exact coordinates on photo.
+ */
+abstract class FullscreenToolView extends View {
+
+    protected final RectF displayBounds = new RectF();
+    private final Matrix photoMatrix = new Matrix();
+    private RectF photoBounds;
+
+    public FullscreenToolView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Photo bounds must be set before onSizeChanged() and all other instance methods are invoked.
+     */
+    public void setPhotoBounds(RectF photoBounds) {
+        this.photoBounds = photoBounds;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        displayBounds.setEmpty();
+        photoMatrix.reset();
+        if (photoBounds.isEmpty()) {
+            return;
+        }
+
+        // Assumes photo-view is also full-screen as this tool-view and centers/scales photo to fit.
+        Matrix matrix = new Matrix();
+        if (matrix.setRectToRect(photoBounds, new RectF(0, 0, w, h), Matrix.ScaleToFit.CENTER)) {
+            matrix.mapRect(displayBounds, photoBounds);
+        }
+
+        matrix.invert(photoMatrix);
+    }
+
+    protected float getPhotoWidth() {
+        return photoBounds.width();
+    }
+
+    protected float getPhotoHeight() {
+        return photoBounds.height();
+    }
+
+    protected void mapPhotoPoint(float x, float y, PointF dst) {
+        if (photoBounds.isEmpty()) {
+            dst.set(0, 0);
+        } else {
+            float[] point = new float[] {x, y};
+            photoMatrix.mapPoints(point);
+            dst.set(point[0] / photoBounds.width(), point[1] / photoBounds.height());
+        }
+    }
+
+    protected void mapPhotoRect(RectF src, RectF dst) {
+        if (photoBounds.isEmpty()) {
+            dst.setEmpty();
+        } else {
+            photoMatrix.mapRect(dst, src);
+            dst.set(dst.left / photoBounds.width(), dst.top / photoBounds.height(),
+                    dst.right / photoBounds.width(), dst.bottom / photoBounds.height());
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/GrainAction.java b/src/com/android/gallery3d/photoeditor/actions/GrainAction.java
new file mode 100644 (file)
index 0000000..258eb8a
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.GrainFilter;
+
+/**
+ * An action handling the film-grain effect.
+ */
+public class GrainAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    private ScaleSeekBar scalePicker;
+
+    public GrainAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final GrainFilter filter = new GrainFilter();
+
+        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setScale(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scalePicker.setProgress(DEFAULT_SCALE);
+
+        filter.setScale(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void doEnd() {
+        scalePicker.setOnScaleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java b/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java
new file mode 100644 (file)
index 0000000..ac89cd1
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.GrayscaleFilter;
+
+/**
+ * An action handling grayscale effect.
+ */
+public class GrayscaleAction extends EffectAction {
+
+    public GrayscaleAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        notifyFilterChanged(new GrayscaleFilter(), true);
+        notifyDone();
+    }
+
+    @Override
+    public void doEnd() {
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java b/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java
new file mode 100644 (file)
index 0000000..06feedc
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.HighlightFilter;
+
+/**
+ * An action handling highlight effect.
+ */
+public class HighlightAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0f;
+
+    private ScaleSeekBar scalePicker;
+
+    public HighlightAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final HighlightFilter filter = new HighlightFilter();
+
+        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.HIGHLIGHT);
+        scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setHighlight(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scalePicker.setProgress(DEFAULT_SCALE);
+    }
+
+    @Override
+    public void doEnd() {
+        scalePicker.setOnScaleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java b/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java
new file mode 100644 (file)
index 0000000..44ffc52
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.LomoishFilter;
+
+/**
+ * An action handling the preset "Lomo-ish" effect.
+ */
+public class LomoishAction extends EffectAction {
+
+    public LomoishAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        notifyFilterChanged(new LomoishFilter(), true);
+        notifyDone();
+    }
+
+    @Override
+    public void doEnd() {
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java b/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java
new file mode 100644 (file)
index 0000000..5276421
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.NegativeFilter;
+
+/**
+ * An action handling negative effect.
+ */
+public class NegativeAction extends EffectAction {
+
+    public NegativeAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        notifyFilterChanged(new NegativeFilter(), true);
+        notifyDone();
+    }
+
+    @Override
+    public void doEnd() {
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java b/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java
new file mode 100644 (file)
index 0000000..760539d
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.PosterizeFilter;
+
+/**
+ * An action handling the "Posterize" effect.
+ */
+public class PosterizeAction extends EffectAction {
+
+    public PosterizeAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        notifyFilterChanged(new PosterizeFilter(), true);
+        notifyDone();
+    }
+
+    @Override
+    public void doEnd() {
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java b/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java
new file mode 100644 (file)
index 0000000..a472ad9
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.RedEyeFilter;
+
+/**
+ * An action handling red-eye removal.
+ */
+public class RedEyeAction extends EffectAction {
+
+    private TouchView touchView;
+
+    public RedEyeAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final RedEyeFilter filter = new RedEyeFilter();
+
+        touchView = factory.createTouchView();
+        touchView.setSingleTapListener(new TouchView.SingleTapListener() {
+
+            @Override
+            public void onSingleTap(PointF point) {
+                filter.addRedEyePosition(point);
+                notifyFilterChanged(filter, true);
+            }
+        });
+    }
+
+    @Override
+    public void doEnd() {
+        touchView.setSingleTapListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/RotateAction.java b/src/com/android/gallery3d/photoeditor/actions/RotateAction.java
new file mode 100644 (file)
index 0000000..dee78f1
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.photoeditor.PhotoView;
+import com.android.gallery3d.photoeditor.filters.RotateFilter;
+
+/**
+ * An action handling rotate effect.
+ */
+public class RotateAction extends EffectAction {
+
+    private static final float DEFAULT_ANGLE = 0.0f;
+    private static final float DEFAULT_ROTATE_SPAN = 360.0f;
+
+    private RotateFilter filter;
+    private float rotateDegrees;
+    private RotateView rotateView;
+
+    public RotateAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        filter = new RotateFilter();
+
+        rotateView = factory.createRotateView();
+        rotateView.setOnAngleChangeListener(new RotateView.OnRotateChangeListener() {
+
+            // Directly transform photo-view because running the rotation filter isn't fast enough.
+            PhotoView photoView = (PhotoView) rotateView.getRootView().findViewById(
+                    R.id.photo_view);
+
+            @Override
+            public void onAngleChanged(float degrees, boolean fromUser){
+                if (fromUser) {
+                    rotateDegrees = degrees;
+                    filter.setAngle(degrees);
+                    notifyFilterChanged(filter, false);
+                    transformPhotoView(degrees);
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch() {
+                // no-op
+            }
+
+            @Override
+            public void onStopTrackingTouch() {
+                if (roundFilterRotationDegrees()) {
+                    notifyFilterChanged(filter, false);
+                    transformPhotoView(rotateDegrees);
+                    rotateView.setRotatedAngle(rotateDegrees);
+                }
+            }
+
+            private void transformPhotoView(final float degrees) {
+                photoView.queue(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        photoView.rotatePhoto(degrees);
+                    }
+                });
+            }
+        });
+        rotateView.setRotatedAngle(DEFAULT_ANGLE);
+        rotateView.setRotateSpan(DEFAULT_ROTATE_SPAN);
+        rotateDegrees = 0;
+    }
+
+    @Override
+    public void doEnd() {
+        rotateView.setOnAngleChangeListener(null);
+        // Round the current rotation degrees in case rotation tracking has not stopped yet.
+        roundFilterRotationDegrees();
+        notifyFilterChanged(filter, true);
+    }
+
+    /**
+     * Rounds filter rotation degrees to multiples of 90 degrees.
+     *
+     * @return true if the rotation degrees has been changed.
+     */
+    private boolean roundFilterRotationDegrees() {
+        if (rotateDegrees % 90 != 0) {
+            rotateDegrees = Math.round(rotateDegrees / 90) * 90;
+            filter.setAngle(rotateDegrees);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/RotateView.java b/src/com/android/gallery3d/photoeditor/actions/RotateView.java
new file mode 100644 (file)
index 0000000..c7cee59
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+/**
+ * View that shows grids and handles touch-events to adjust angle of rotation.
+ */
+class RotateView extends FullscreenToolView {
+
+    /**
+     * Listens to rotate changes.
+     */
+    public interface OnRotateChangeListener {
+
+        void onAngleChanged(float degrees, boolean fromUser);
+
+        void onStartTrackingTouch();
+
+        void onStopTrackingTouch();
+    }
+
+    // All angles used are defined between PI and -PI.
+    private static final float MATH_PI = (float) Math.PI;
+    private static final float MATH_HALF_PI = MATH_PI / 2;
+    private static final float RADIAN_TO_DEGREE = 180f / MATH_PI;
+
+    private final Paint dashStrokePaint;
+    private final Path grids = new Path();
+    private final Path referenceLine = new Path();
+
+    private OnRotateChangeListener listener;
+    private boolean drawGrids;
+    private int centerX;
+    private int centerY;
+    private float maxRotatedAngle;
+    private float minRotatedAngle;
+    private float currentRotatedAngle;
+    private float lastRotatedAngle;
+    private float touchStartAngle;
+
+    public RotateView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        dashStrokePaint = new Paint();
+        dashStrokePaint.setAntiAlias(true);
+        dashStrokePaint.setStyle(Paint.Style.STROKE);
+        dashStrokePaint.setPathEffect(new DashPathEffect(new float[] {15.0f, 5.0f}, 1.0f));
+    }
+
+    public void setRotatedAngle(float degrees) {
+        refreshAngle(degrees, false);
+    }
+
+    /**
+     * Sets allowed degrees for rotation span before rotating the view.
+     */
+    public void setRotateSpan(float degrees) {
+        if (degrees >= 360f) {
+            maxRotatedAngle = Float.POSITIVE_INFINITY;
+        } else {
+            maxRotatedAngle = (degrees / RADIAN_TO_DEGREE) / 2;
+        }
+        minRotatedAngle = -maxRotatedAngle;
+    }
+
+    public void setOnAngleChangeListener(OnRotateChangeListener listener) {
+        this.listener = listener;
+    }
+
+    public void setDrawGrids(boolean drawGrids) {
+        this.drawGrids = drawGrids;
+        invalidate();
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        centerX = w / 2;
+        centerY = h / 2;
+
+        // Make reference line long enough to cross the bounds diagonally after being rotated.
+        referenceLine.reset();
+        float radius = (float) Math.hypot(centerX, centerY);
+        float delta = radius - centerX;
+        referenceLine.moveTo(-delta, centerY);
+        referenceLine.lineTo(getWidth() + delta, centerY);
+        delta = radius - centerY;
+        referenceLine.moveTo(centerX, -delta);
+        referenceLine.lineTo(centerX, getHeight() + delta);
+
+        // Set grids inside photo display bounds.
+        grids.reset();
+        delta = displayBounds.width() / 4.0f;
+        for (float x = displayBounds.left + delta; x < displayBounds.right; x += delta) {
+            grids.moveTo(x, displayBounds.top);
+            grids.lineTo(x, displayBounds.bottom);
+        }
+        delta = displayBounds.height() / 4.0f;
+        for (float y = displayBounds.top + delta; y < displayBounds.bottom; y += delta) {
+            grids.moveTo(displayBounds.left, y);
+            grids.lineTo(displayBounds.right, y);
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (drawGrids) {
+            canvas.save();
+            canvas.clipRect(displayBounds);
+            dashStrokePaint.setStrokeWidth(2f);
+            dashStrokePaint.setColor(0x99CCCCCC);
+            canvas.drawPath(grids, dashStrokePaint);
+
+            canvas.rotate(-currentRotatedAngle * RADIAN_TO_DEGREE, centerX, centerY);
+            dashStrokePaint.setStrokeWidth(2f);
+            dashStrokePaint.setColor(0x99FFCC77);
+            canvas.drawPath(referenceLine, dashStrokePaint);
+            canvas.restore();
+        }
+    }
+
+    private float calculateAngle(MotionEvent ev) {
+        float x = ev.getX() - centerX;
+        float y = centerY - ev.getY();
+
+        float angle;
+        if (x == 0) {
+            angle = (y >= 0) ? MATH_HALF_PI : -MATH_HALF_PI;
+        } else {
+            angle = (float) Math.atan(y / x);
+        }
+
+        if ((angle >= 0) && (x < 0)) {
+            angle = angle - MATH_PI;
+        } else if ((angle < 0) && (x < 0)) {
+            angle = MATH_PI + angle;
+        }
+        return angle;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        super.onTouchEvent(ev);
+
+        if (isEnabled()) {
+            switch (ev.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    lastRotatedAngle = currentRotatedAngle;
+                    touchStartAngle = calculateAngle(ev);
+
+                    if (listener != null) {
+                        listener.onStartTrackingTouch();
+                    }
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    float touchAngle = calculateAngle(ev);
+                    float rotatedAngle = touchAngle - touchStartAngle + lastRotatedAngle;
+
+                    if ((rotatedAngle > maxRotatedAngle) || (rotatedAngle < minRotatedAngle)) {
+                        // Angles are out of range; restart rotating.
+                        // TODO: Fix discontinuity around boundary.
+                        lastRotatedAngle = currentRotatedAngle;
+                        touchStartAngle = touchAngle;
+                    } else {
+                        refreshAngle(-rotatedAngle * RADIAN_TO_DEGREE, true);
+                    }
+                    break;
+
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    if (listener != null) {
+                        listener.onStopTrackingTouch();
+                    }
+                    break;
+            }
+        }
+        return true;
+    }
+
+    private void refreshAngle(float degrees, boolean fromUser) {
+        currentRotatedAngle = -degrees / RADIAN_TO_DEGREE;
+        if (listener != null) {
+            listener.onAngleChanged(degrees, fromUser);
+        }
+        invalidate();
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java b/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java
new file mode 100644 (file)
index 0000000..31bcfd6
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.SaturationFilter;
+
+/**
+ * An action handling saturation effect.
+ */
+public class SaturationAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    private ScaleSeekBar scalePicker;
+
+    public SaturationAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final SaturationFilter filter = new SaturationFilter();
+
+        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.COLOR);
+        scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setSaturation(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scalePicker.setProgress(DEFAULT_SCALE);
+    }
+
+    @Override
+    public void doEnd() {
+        scalePicker.setOnScaleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/ScaleSeekBar.java b/src/com/android/gallery3d/photoeditor/actions/ScaleSeekBar.java
new file mode 100644 (file)
index 0000000..6fb154c
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+/**
+ * Seek-bar that has a draggable thumb to set and get the normalized scale value from 0 to 1.
+ */
+class ScaleSeekBar extends AbstractSeekBar {
+
+    /**
+     * Listens to scale changes.
+     */
+    public interface OnScaleChangeListener {
+
+        void onProgressChanged(float progress, boolean fromUser);
+    }
+
+    public ScaleSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        setMax(100);
+    }
+
+    public void setOnScaleChangeListener(final OnScaleChangeListener listener) {
+        setOnSeekBarChangeListener((listener == null) ? null : new OnSeekBarChangeListener() {
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                listener.onProgressChanged((float) progress / getMax(), fromUser);
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+        });
+    }
+
+    public void setProgress(float progress) {
+        setProgress((int) (progress * getMax()));
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java b/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java
new file mode 100644 (file)
index 0000000..c431115
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.SepiaFilter;
+
+/**
+ * An action handling sepia effect.
+ */
+public class SepiaAction  extends EffectAction {
+
+    public SepiaAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        notifyFilterChanged(new SepiaFilter(), true);
+        notifyDone();
+    }
+
+    @Override
+    public void doEnd() {
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java b/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java
new file mode 100644 (file)
index 0000000..185febc
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.ShadowFilter;
+
+/**
+ * An action handling shadow effect.
+ */
+public class ShadowAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0f;
+
+    private ScaleSeekBar scalePicker;
+
+    public ShadowAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final ShadowFilter filter = new ShadowFilter();
+
+        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.SHADOW);
+        scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setShadow(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scalePicker.setProgress(DEFAULT_SCALE);
+    }
+
+    @Override
+    public void doEnd() {
+        scalePicker.setOnScaleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java b/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java
new file mode 100644 (file)
index 0000000..7524c76
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.SharpenFilter;
+
+/**
+ * An action handling sharpen effect.
+ */
+public class SharpenAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    private ScaleSeekBar scalePicker;
+
+    public SharpenAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final SharpenFilter filter = new SharpenFilter();
+
+        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setSharpen(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scalePicker.setProgress(DEFAULT_SCALE);
+
+        filter.setSharpen(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void doEnd() {
+        scalePicker.setOnScaleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java b/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java
new file mode 100644 (file)
index 0000000..2a8c549
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.StraightenFilter;
+
+/**
+ * An action handling straighten effect.
+ */
+public class StraightenAction extends EffectAction {
+
+    private static final float DEFAULT_ANGLE = 0.0f;
+    private static final float DEFAULT_ROTATE_SPAN = StraightenFilter.MAX_DEGREES * 2;
+
+    private RotateView rotateView;
+
+    public StraightenAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final StraightenFilter filter = new StraightenFilter();
+
+        rotateView = factory.createRotateView();
+        rotateView.setOnAngleChangeListener(new RotateView.OnRotateChangeListener() {
+
+            @Override
+            public void onAngleChanged(float degrees, boolean fromUser){
+                if (fromUser) {
+                    filter.setAngle(degrees);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch() {
+                // no-op
+            }
+
+            @Override
+            public void onStopTrackingTouch() {
+                // no-op
+            }
+        });
+        rotateView.setDrawGrids(true);
+        rotateView.setRotatedAngle(DEFAULT_ANGLE);
+        rotateView.setRotateSpan(DEFAULT_ROTATE_SPAN);
+    }
+
+    @Override
+    public void doEnd() {
+        rotateView.setOnAngleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/TintAction.java b/src/com/android/gallery3d/photoeditor/actions/TintAction.java
new file mode 100644 (file)
index 0000000..defd2a3
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.TintFilter;
+
+/**
+ * An action handling tint effect.
+ */
+public class TintAction extends EffectAction {
+
+    private static final int DEFAULT_COLOR_INDEX = 13;
+
+    private ColorSeekBar colorPicker;
+
+    public TintAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final TintFilter filter = new TintFilter();
+
+        colorPicker = factory.createColorPicker();
+        colorPicker.setOnColorChangeListener(new ColorSeekBar.OnColorChangeListener() {
+
+            @Override
+            public void onColorChanged(int color, boolean fromUser) {
+                if (fromUser) {
+                    filter.setTint(color);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        // Tint photo with the default color.
+        colorPicker.setColorIndex(DEFAULT_COLOR_INDEX);
+        filter.setTint(colorPicker.getColor());
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void doEnd() {
+        colorPicker.setOnColorChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/TouchView.java b/src/com/android/gallery3d/photoeditor/actions/TouchView.java
new file mode 100644 (file)
index 0000000..0548cc4
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+/**
+ * A view that detects user gestures and touch motions.
+ */
+class TouchView extends FullscreenToolView {
+
+    /**
+     * Listener of swipes.
+     */
+    public interface SwipeListener {
+
+        void onSwipeLeft();
+
+        void onSwipeRight();
+
+        void onSwipeUp();
+
+        void onSwipeDown();
+    }
+
+    /**
+     * Listener of single tap on a point (relative to photo coordinates).
+     */
+    public interface SingleTapListener {
+
+        void onSingleTap(PointF point);
+    }
+
+    private final GestureDetector gestureDetector;
+
+    private SwipeListener swipeListener;
+    private SingleTapListener singleTapListener;
+
+    public TouchView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final int swipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
+        gestureDetector = new GestureDetector(
+                context, new GestureDetector.SimpleOnGestureListener() {
+
+            @Override
+            public boolean onDown(MotionEvent e) {
+                // GestureDetector onTouchEvent returns true for fling events only when their
+                // preceding down events are consumed.
+                return true;
+            }
+
+            @Override
+            public boolean onSingleTapUp(MotionEvent e) {
+                if (singleTapListener != null) {
+                    PointF point = new PointF();
+                    mapPhotoPoint(e.getX(), e.getY(), point);
+                    singleTapListener.onSingleTap(point);
+                }
+                return true;
+            }
+
+            @Override
+            public boolean onFling(
+                    MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
+                if (swipeListener != null) {
+                    float absX = Math.abs(velocityX);
+                    float absY = Math.abs(velocityY);
+                    float deltaX = me2.getX() - me1.getX();
+                    float deltaY = me2.getY() - me1.getY();
+                    int travelX = getWidth() / 4;
+                    int travelY = getHeight() / 4;
+                    if (velocityX > swipeThreshold && absY < absX && deltaX > travelX) {
+                        swipeListener.onSwipeRight();
+                    } else if (velocityX < -swipeThreshold && absY < absX && deltaX < -travelX) {
+                        swipeListener.onSwipeLeft();
+                    } else if (velocityY < -swipeThreshold && absX < absY && deltaY < -travelY) {
+                        swipeListener.onSwipeUp();
+                    } else if (velocityY > swipeThreshold && absX < absY / 2 && deltaY > travelY) {
+                        swipeListener.onSwipeDown();
+                    }
+                }
+                return true;
+            }
+        });
+        gestureDetector.setIsLongpressEnabled(false);
+    }
+
+    public void setSwipeListener(SwipeListener listener) {
+        swipeListener = listener;
+    }
+
+    public void setSingleTapListener(SingleTapListener listener) {
+        singleTapListener = listener;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return isEnabled() && gestureDetector.onTouchEvent(event);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java b/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java
new file mode 100644 (file)
index 0000000..f59c636
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.photoeditor.filters.VignetteFilter;
+
+/**
+ * An action handling vignette effect.
+ */
+public class VignetteAction extends EffectAction {
+
+    private static final float DEFAULT_SCALE = 0.5f;
+
+    private ScaleSeekBar scalePicker;
+
+    public VignetteAction(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void doBegin() {
+        final VignetteFilter filter = new VignetteFilter();
+
+        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
+
+            @Override
+            public void onProgressChanged(float progress, boolean fromUser) {
+                if (fromUser) {
+                    filter.setScale(progress);
+                    notifyFilterChanged(filter, true);
+                }
+            }
+        });
+        scalePicker.setProgress(DEFAULT_SCALE);
+
+        filter.setScale(DEFAULT_SCALE);
+        notifyFilterChanged(filter, true);
+    }
+
+    @Override
+    public void doEnd() {
+        scalePicker.setOnScaleChangeListener(null);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/AutoFixFilter.java b/src/com/android/gallery3d/photoeditor/filters/AutoFixFilter.java
new file mode 100644 (file)
index 0000000..a219abe
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Auto-fix filter applied to the image.
+ */
+public class AutoFixFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Sets the auto-fix level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setScale(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_AUTOFIX);
+        effect.setParameter("scale", scale);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/ColorTemperatureFilter.java b/src/com/android/gallery3d/photoeditor/filters/ColorTemperatureFilter.java
new file mode 100644 (file)
index 0000000..dc6f1a7
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Color temperature filter applied to the image.
+ */
+public class ColorTemperatureFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Sets the color temperature level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setColorTemperature(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_TEMPERATURE);
+        effect.setParameter("scale", scale);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/CropFilter.java b/src/com/android/gallery3d/photoeditor/filters/CropFilter.java
new file mode 100644 (file)
index 0000000..372279e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.graphics.RectF;
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Crop filter applied to the image.
+ */
+public class CropFilter extends Filter {
+
+    private RectF bounds;
+
+    /**
+     * The rect coordinates used here should range from 0 to 1.
+     */
+    public void setCropBounds(RectF bounds) {
+        this.bounds = bounds;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        dst.changeDimension(Math.round(bounds.width() * src.width()),
+                Math.round(bounds.height() * src.height()));
+
+        Effect effect = getEffect(context, EffectFactory.EFFECT_CROP);
+        effect.setParameter("xorigin", Math.round(bounds.left * src.width()));
+        effect.setParameter("yorigin", Math.round(bounds.top * src.height()));
+        effect.setParameter("width", dst.width());
+        effect.setParameter("height", dst.height());
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java b/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java
new file mode 100644 (file)
index 0000000..f03bf5f
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Cross-process filter applied to the image.
+ */
+public class CrossProcessFilter extends Filter {
+
+    public CrossProcessFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        getEffect(context, EffectFactory.EFFECT_CROSSPROCESS).apply(
+                src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java b/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java
new file mode 100644 (file)
index 0000000..800d59a
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Documentary filter applied to the image.
+ */
+public class DocumentaryFilter extends Filter {
+
+    public DocumentaryFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        getEffect(context, EffectFactory.EFFECT_DOCUMENTARY).apply(
+                src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java b/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java
new file mode 100644 (file)
index 0000000..3c4bbf7
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+import com.android.gallery3d.photoeditor.actions.DoodlePaint;
+
+import java.util.Vector;
+
+/**
+ * Doodle filter applied to the image.
+ */
+public class DoodleFilter extends Filter {
+
+    private static class ColorPath {
+        private final int color;
+        private final Path path;
+
+        ColorPath(int color, Path path) {
+            this.color = color;
+            this.path = path;
+        }
+    }
+
+    private final Vector<ColorPath> doodles = new Vector<ColorPath>();
+
+    /**
+     * Signals once at least a doodle drawn within photo bounds; this filter is regarded as invalid
+     * (no-op on the photo) until not all its doodling is out of bounds.
+     */
+    public void setDoodledInPhotoBounds() {
+        validate();
+    }
+
+    /**
+     * The path coordinates used here should range from 0 to 1.
+     */
+    public void addPath(Path path, int color) {
+        doodles.add(new ColorPath(color, path));
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Bitmap bitmap = Bitmap.createBitmap(src.width(), src.height(), Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        Matrix matrix = new Matrix();
+        matrix.setRectToRect(new RectF(0, 0, 1, 1),
+                new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()), Matrix.ScaleToFit.FILL);
+
+        Path drawingPath = new Path();
+        Paint paint = new DoodlePaint();
+        for (ColorPath doodle : doodles) {
+            paint.setColor(doodle.color);
+            drawingPath.set(doodle.path);
+            drawingPath.transform(matrix);
+            canvas.drawPath(drawingPath, paint);
+        }
+
+        Effect effect = getEffect(context, EffectFactory.EFFECT_DOODLE);
+        effect.setParameter("doodle", bitmap);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java b/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java
new file mode 100644 (file)
index 0000000..68db831
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Duotone filter applied to the image.
+ */
+public class DuotoneFilter extends Filter {
+
+    private int firstColor;
+    private int secondColor;
+
+    public void setDuotone(int firstColor, int secondColor) {
+        this.firstColor = firstColor;
+        this.secondColor = secondColor;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_DUOTONE);
+        effect.setParameter("first_color", firstColor);
+        effect.setParameter("second_color", secondColor);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/FillLightFilter.java b/src/com/android/gallery3d/photoeditor/filters/FillLightFilter.java
new file mode 100644 (file)
index 0000000..a799e6f
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Fill-light filter applied to the image.
+ */
+public class FillLightFilter extends Filter {
+
+    private float backlight;
+
+    /**
+     * Sets the backlight level.
+     *
+     * @param backlight ranges from 0 to 1.
+     */
+    public void setBacklight(float backlight) {
+        this.backlight = backlight;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_FILLLIGHT);
+        effect.setParameter("backlight", backlight);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/Filter.java b/src/com/android/gallery3d/photoeditor/filters/Filter.java
new file mode 100644 (file)
index 0000000..c2d3fe5
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Image filter for photo editing.
+ */
+public abstract class Filter {
+
+    // TODO: This should be set in MFF instead.
+    private static final int DEFAULT_TILE_SIZE = 640;
+
+    private boolean isValid;
+    private EffectContext context;
+    private Effect effect;
+
+    protected void validate() {
+        isValid = true;
+    }
+
+    protected Effect getEffect(EffectContext context, String name) {
+        if (this.context != context) {
+            effect = context.getFactory().createEffect(name);
+            effect.setParameter("tile_size", DEFAULT_TILE_SIZE);
+            this.context = context;
+        }
+        return effect;
+    }
+
+    /**
+     * Some filters, e.g. lighting filters, are initially invalid until set up with parameters while
+     * others, e.g. Sepia or Posterize filters, are initially valid without parameters.
+     */
+    public boolean isValid() {
+        return isValid;
+    }
+
+    public void release() {
+        if (effect != null) {
+            effect.release();
+            effect = null;
+        }
+    }
+
+    /**
+     * Processes the source bitmap and matrix and output the destination bitmap and matrix.
+     *
+     * @param context effect context bound to a GL context to create GL effect.
+     * @param src source photo as the input.
+     * @param dst destination photo having the same dimension as source photo as the output.
+     */
+    public abstract void process(EffectContext context, Photo src, Photo dst);
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/FisheyeFilter.java b/src/com/android/gallery3d/photoeditor/filters/FisheyeFilter.java
new file mode 100644 (file)
index 0000000..b1eb2d1
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Fisheye filter applied to the image.
+ */
+public class FisheyeFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Sets the fisheye distortion level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setScale(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_FISHEYE);
+        effect.setParameter("scale", scale);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java b/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java
new file mode 100644 (file)
index 0000000..184f590
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Flip filter applied to the image.
+ */
+public class FlipFilter extends Filter {
+
+    private boolean flipHorizontal;
+    private boolean flipVertical;
+
+    public void setFlip(boolean flipHorizontal, boolean flipVertical) {
+        this.flipHorizontal = flipHorizontal;
+        this.flipVertical = flipVertical;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_FLIP);
+        effect.setParameter("horizontal", flipHorizontal);
+        effect.setParameter("vertical", flipVertical);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/GrainFilter.java b/src/com/android/gallery3d/photoeditor/filters/GrainFilter.java
new file mode 100644 (file)
index 0000000..1ee8a9c
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Film grain filter applied to the image.
+ */
+public class GrainFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Set the grain noise level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setScale(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_GRAIN);
+        effect.setParameter("scale", scale);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java b/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java
new file mode 100644 (file)
index 0000000..ce6cdef
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Grayscale filter applied to the image.
+ */
+public class GrayscaleFilter extends Filter {
+
+    public GrayscaleFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        getEffect(context, EffectFactory.EFFECT_GRAYSCALE).apply(
+                src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/HighlightFilter.java b/src/com/android/gallery3d/photoeditor/filters/HighlightFilter.java
new file mode 100644 (file)
index 0000000..3169d04
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Highlight filter applied to the image.
+ */
+public class HighlightFilter extends Filter {
+
+    private float white;
+
+    /**
+     * Sets the highlight level.
+     *
+     * @param highlight ranges from 0 to 1.
+     */
+    public void setHighlight(float highlight) {
+        white = 1f - highlight * 0.5f;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_BLACKWHITE);
+        effect.setParameter("black", 0f);
+        effect.setParameter("white", white);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java b/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java
new file mode 100644 (file)
index 0000000..95ff627
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Lomo-ish filter applied to the image.
+ */
+public class LomoishFilter extends Filter {
+
+    public LomoishFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        getEffect(context, EffectFactory.EFFECT_LOMOISH).apply(
+                src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java b/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java
new file mode 100644 (file)
index 0000000..0b3837f
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Negative filter applied to the image.
+ */
+public class NegativeFilter extends Filter {
+
+    public NegativeFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        getEffect(context, EffectFactory.EFFECT_NEGATIVE).apply(
+                src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java b/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java
new file mode 100644 (file)
index 0000000..51202dc
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Posterize filter applied to the image.
+ */
+public class PosterizeFilter extends Filter {
+
+    public PosterizeFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        getEffect(context, EffectFactory.EFFECT_POSTERIZE).apply(
+                src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java b/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java
new file mode 100644 (file)
index 0000000..559819d
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.graphics.PointF;
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+import java.util.Vector;
+
+/**
+ * Red-eye removal filter applied to the image.
+ */
+public class RedEyeFilter extends Filter {
+
+    private final Vector<PointF> redeyes = new Vector<PointF>();
+
+    /**
+     * The point coordinates used here should range from 0 to 1.
+     */
+    public void addRedEyePosition(PointF point) {
+        redeyes.add(point);
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_REDEYE);
+        float[] centers = new float[redeyes.size() * 2];
+        int i = 0;
+        for (PointF eye : redeyes) {
+            centers[i++] = eye.x;
+            centers[i++] = eye.y;
+        }
+        effect.setParameter("centers", centers);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java b/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java
new file mode 100644 (file)
index 0000000..2823466
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Rotate filter applied to the image.
+ */
+public class RotateFilter extends Filter {
+
+    private float degrees;
+
+    public void setAngle(float degrees) {
+        this.degrees = degrees;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        if (degrees % 180 != 0) {
+            dst.changeDimension(src.height(), src.width());
+        }
+        Effect effect = getEffect(context, EffectFactory.EFFECT_ROTATE);
+        effect.setParameter("degree", degrees);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/SaturationFilter.java b/src/com/android/gallery3d/photoeditor/filters/SaturationFilter.java
new file mode 100644 (file)
index 0000000..d3b5f70
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Saturation filter applied to the image.
+ */
+public class SaturationFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Sets the saturation level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setSaturation(float scale) {
+        this.scale = (scale - 0.5f) * 2;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_SATURATE);
+        effect.setParameter("scale", scale);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java b/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java
new file mode 100644 (file)
index 0000000..3fdda15
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Sepia filter applied to the image.
+ */
+public class SepiaFilter extends Filter {
+
+    public SepiaFilter() {
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        getEffect(context, EffectFactory.EFFECT_SEPIA).apply(
+                src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/ShadowFilter.java b/src/com/android/gallery3d/photoeditor/filters/ShadowFilter.java
new file mode 100644 (file)
index 0000000..b23ef1c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Shadow filter applied to the image.
+ */
+public class ShadowFilter extends Filter {
+
+    private float black;
+
+    /**
+     * Sets the shadow blackness level.
+     *
+     * @param shadow ranges from 0 to 1.
+     */
+    public void setShadow(float shadow) {
+        black = shadow * 0.5f;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_BLACKWHITE);
+        effect.setParameter("black", black);
+        effect.setParameter("white", 1f);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/SharpenFilter.java b/src/com/android/gallery3d/photoeditor/filters/SharpenFilter.java
new file mode 100644 (file)
index 0000000..a809712
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Sharpen filter applied to the image.
+ */
+public class SharpenFilter extends Filter {
+
+    private float scale;
+
+    /**
+     * Sets the sharpen level.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setSharpen(float scale) {
+        this.scale = scale;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_SHARPEN);
+        effect.setParameter("scale", scale);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java b/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java
new file mode 100644 (file)
index 0000000..ffeb445
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Straighten filter applied to the image.
+ */
+public class StraightenFilter extends Filter {
+
+    public static final float MAX_DEGREES = 30.0f;
+
+    private float angle;
+
+    public void setAngle(float degrees) {
+        angle = -degrees;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_STRAIGHTEN);
+        effect.setParameter("maxAngle", MAX_DEGREES);
+        effect.setParameter("angle", angle);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/TintFilter.java b/src/com/android/gallery3d/photoeditor/filters/TintFilter.java
new file mode 100644 (file)
index 0000000..bbaf9c7
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Tint filter applied to the image.
+ */
+public class TintFilter extends Filter {
+
+    private int tint;
+
+    public void setTint(int color) {
+        tint = color;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_TINT);
+        effect.setParameter("tint", tint);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/filters/VignetteFilter.java b/src/com/android/gallery3d/photoeditor/filters/VignetteFilter.java
new file mode 100644 (file)
index 0000000..d066c8b
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.filters;
+
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+import android.media.effect.EffectFactory;
+
+import com.android.gallery3d.photoeditor.Photo;
+
+/**
+ * Vignette filter applied to the image.
+ */
+public class VignetteFilter extends Filter {
+
+    private float range;
+
+    /**
+     * Sets the vignette range scale.
+     *
+     * @param scale ranges from 0 to 1.
+     */
+    public void setScale(float scale) {
+        // The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3
+        // which means no vignette at all because the luminousity difference is
+        // less than 1/256 and will cause nothing.
+        range = 1.30f - (float) Math.sqrt(scale) * 0.7f;
+        validate();
+    }
+
+    @Override
+    public void process(EffectContext context, Photo src, Photo dst) {
+        Effect effect = getEffect(context, EffectFactory.EFFECT_VIGNETTE);
+        effect.setParameter("range", range);
+        effect.apply(src.texture(), src.width(), src.height(), dst.texture());
+    }
+}