summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornicolasroard <nicolasroard@google.com>2012-09-25 14:27:56 -0700
committernicolasroard <nicolasroard@google.com>2012-09-26 16:13:45 -0700
commit0d7cdf8e763fb65c32bfad65245b3753deb75737 (patch)
treeb5ac324506895492cca3ee573ba2446473b81a7d
parent833f775afc9515567dc5aaf0cafe20e006e77545 (diff)
downloadandroid_packages_apps_Gallery2-0d7cdf8e763fb65c32bfad65245b3753deb75737.tar.gz
android_packages_apps_Gallery2-0d7cdf8e763fb65c32bfad65245b3753deb75737.tar.bz2
android_packages_apps_Gallery2-0d7cdf8e763fb65c32bfad65245b3753deb75737.zip
Initial import of the new image editor
bug:7165910 Change-Id: I756d6594f5bddd233772c979410362ca22e232a3
-rw-r--r--AndroidManifest.xml15
-rw-r--r--jni/Android.mk17
-rw-r--r--jni/filters/bw.c107
-rw-r--r--jni/filters/filters.h46
-rw-r--r--jni/filters/gradient.c65
-rw-r--r--jni/filters/saturated.c53
-rw-r--r--jni/filters/vignette.c69
-rw-r--r--res/drawable/filtershow_background.pngbin0 -> 653905 bytes
-rwxr-xr-xres/drawable/filtershow_border_black.9.pngbin0 -> 688 bytes
-rwxr-xr-xres/drawable/filtershow_border_film.pngbin0 -> 36176 bytes
-rw-r--r--res/drawable/filtershow_border_film2.9.pngbin0 -> 22656 bytes
-rw-r--r--res/drawable/filtershow_border_film3.9.pngbin0 -> 23574 bytes
-rwxr-xr-xres/drawable/filtershow_border_rounded_black.9.pngbin0 -> 1113 bytes
-rwxr-xr-xres/drawable/filtershow_border_rounded_white.9.pngbin0 -> 1219 bytes
-rw-r--r--res/drawable/filtershow_border_scratch3.9.pngbin0 -> 16043 bytes
-rwxr-xr-xres/drawable/filtershow_border_white.9.pngbin0 -> 687 bytes
-rw-r--r--res/drawable/filtershow_button_background.xml8
-rw-r--r--res/drawable/filtershow_button_border.pngbin0 -> 1021 bytes
-rw-r--r--res/drawable/filtershow_button_colors.pngbin0 -> 1277 bytes
-rw-r--r--res/drawable/filtershow_button_colors_contrast.pngbin0 -> 1218 bytes
-rw-r--r--res/drawable/filtershow_button_colors_curve.pngbin0 -> 1574 bytes
-rw-r--r--res/drawable/filtershow_button_colors_sharpen.pngbin0 -> 1378 bytes
-rw-r--r--res/drawable/filtershow_button_colors_vignette.pngbin0 -> 2147 bytes
-rw-r--r--res/drawable/filtershow_button_current.pngbin0 -> 1355 bytes
-rw-r--r--res/drawable/filtershow_button_fx.pngbin0 -> 1115 bytes
-rw-r--r--res/drawable/filtershow_button_geometry.pngbin0 -> 975 bytes
-rw-r--r--res/drawable/filtershow_button_geometry_crop.pngbin0 -> 999 bytes
-rw-r--r--res/drawable/filtershow_button_geometry_flip.pngbin0 -> 1809 bytes
-rw-r--r--res/drawable/filtershow_button_geometry_rotate.pngbin0 -> 2185 bytes
-rw-r--r--res/drawable/filtershow_button_geometry_straighten.pngbin0 -> 3104 bytes
-rw-r--r--res/drawable/filtershow_button_operations.pngbin0 -> 1270 bytes
-rw-r--r--res/drawable/filtershow_button_origin.pngbin0 -> 1069 bytes
-rw-r--r--res/drawable/filtershow_button_redo.pngbin0 -> 964 bytes
-rw-r--r--res/drawable/filtershow_button_selected_background.9.pngbin0 -> 2885 bytes
-rw-r--r--res/drawable/filtershow_button_settings.pngbin0 -> 1062 bytes
-rw-r--r--res/drawable/filtershow_button_show_original.pngbin0 -> 1738 bytes
-rw-r--r--res/drawable/filtershow_button_undo.pngbin0 -> 965 bytes
-rw-r--r--res/layout/filtershow_activity.xml323
-rw-r--r--res/layout/filtershow_history_operation_row.xml57
-rw-r--r--res/menu/filtershow_activity_menu.xml5
-rw-r--r--res/menu/filtershow_menu_curves.xml18
-rw-r--r--res/values/filtershow_color.xml24
-rw-r--r--res/values/filtershow_strings.xml49
-rw-r--r--res/values/filtershow_styles.xml55
-rw-r--r--res/values/filtershow_values_attrs.xml22
-rw-r--r--src/com/android/gallery3d/filtershow/FilterShowActivity.java725
-rw-r--r--src/com/android/gallery3d/filtershow/HistoryAdapter.java119
-rw-r--r--src/com/android/gallery3d/filtershow/cache/BitmapCache.java115
-rw-r--r--src/com/android/gallery3d/filtershow/cache/Cache.java19
-rw-r--r--src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java59
-rw-r--r--src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java144
-rw-r--r--src/com/android/gallery3d/filtershow/cache/ImageLoader.java260
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilter.java39
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java24
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java24
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java24
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java24
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java57
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java106
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java98
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java25
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java65
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java25
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java58
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageShow.java323
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java76
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java258
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java5
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePreset.java193
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java16
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java16
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java16
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java16
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java21
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java16
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java22
-rw-r--r--src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java246
-rw-r--r--src/com/android/gallery3d/filtershow/ui/ControlPoint.java39
-rw-r--r--src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java42
-rw-r--r--src/com/android/gallery3d/filtershow/ui/ImageCurves.java408
-rw-r--r--src/com/android/gallery3d/filtershow/ui/PieSlider.java181
-rw-r--r--src/com/android/gallery3d/filtershow/ui/PieSliderListener.java6
-rw-r--r--src/com/android/gallery3d/filtershow/ui/Spline.java126
83 files changed, 4969 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 884f27bd9..cf9f46134 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -375,5 +375,20 @@
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
+ <activity
+ android:name=".filtershow.FilterShowActivity"
+ android:label="@string/title_activity_filter_show"
+ android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >
+ <intent-filter>
+ <action android:name="android.intent.action.EDIT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="image/*" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="action_nextgen_edit" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="image/*" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/jni/Android.mk b/jni/Android.mk
index 099b9bacd..01dd728f0 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -14,4 +14,21 @@ LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := libjni_eglfence
+
+include $(BUILD_SHARED_LIBRARY)
+
+# Filtershow
+
+include $(CLEAR_VARS)
+
+LOCAL_LDLIBS := -llog -ljnigraphics
+LOCAL_MODULE := filters
+LOCAL_SRC_FILES := filters/bw.c \
+ filters/gradient.c \
+ filters/saturated.c \
+ filters/vignette.c
+
+LOCAL_CFLAGS += -ffast-math -O3 -funroll-loops
+LOCAL_ARM_MODE := arm
+
include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/filters/bw.c b/jni/filters/bw.c
new file mode 100644
index 000000000..f075d307e
--- /dev/null
+++ b/jni/filters/bw.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterBW, nativeApplyFilter, jobject bitmap, jint width, jint height)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ int i;
+ int len = width * height * 4;
+ float Rf = 0.2999f;
+ float Gf = 0.587f;
+ float Bf = 0.114f;
+
+ for (i = 0; i < len; i+=4)
+ {
+ int r = destination[RED];
+ int g = destination[GREEN];
+ int b = destination[BLUE];
+ int t = CLAMP(Rf * r + Gf * g + Bf *b);
+
+ destination[RED] = t;
+ destination[GREEN] = t;
+ destination[BLUE] = t;
+ }
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
+
+void JNIFUNCF(ImageFilterBWRed, nativeApplyFilter, jobject bitmap, jint width, jint height)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ int i;
+ int len = width * height * 4;
+ for (i = 0; i < len; i+=4)
+ {
+ int r = destination[RED];
+ int g = destination[GREEN];
+ int b = destination[BLUE];
+ int t = (g + b) / 2;
+ r = t;
+ g = t;
+ b = t;
+ destination[RED] = r;
+ destination[GREEN] = g;
+ destination[BLUE] = b;
+ }
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
+
+void JNIFUNCF(ImageFilterBWGreen, nativeApplyFilter, jobject bitmap, jint width, jint height)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ int i;
+ int len = width * height * 4;
+ for (i = 0; i < len; i+=4)
+ {
+ int r = destination[RED];
+ int g = destination[GREEN];
+ int b = destination[BLUE];
+ int t = (r + b) / 2;
+ r = t;
+ g = t;
+ b = t;
+ destination[RED] = r;
+ destination[GREEN] = g;
+ destination[BLUE] = b;
+ }
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
+
+void JNIFUNCF(ImageFilterBWBlue, nativeApplyFilter, jobject bitmap, jint width, jint height)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ int i;
+ int len = width * height * 4;
+ for (i = 0; i < len; i+=4)
+ {
+ int r = destination[RED];
+ int g = destination[GREEN];
+ int b = destination[BLUE];
+ int t = (r + g) / 2;
+ r = t;
+ g = t;
+ b = t;
+ destination[RED] = r;
+ destination[GREEN] = g;
+ destination[BLUE] = b;
+ }
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/jni/filters/filters.h b/jni/filters/filters.h
new file mode 100644
index 000000000..f8c4c5d08
--- /dev/null
+++ b/jni/filters/filters.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FILTERS_H
+#define FILTERS_H
+
+#include <jni.h>
+#include <string.h>
+#include <android/log.h>
+#include <android/bitmap.h>
+
+typedef unsigned int Color;
+
+#define SetColor(a, r, g, b) ((a << 24) | (b << 16) | (g << 8) | (r << 0));
+#define GetA(color) (((color) >> 24) & 0xFF)
+#define GetB(color) (((color) >> 16) & 0xFF)
+#define GetG(color) (((color) >> 8) & 0xFF)
+#define GetR(color) (((color) >> 0) & 0xFF)
+
+#define MIN(a, b) (a < b ? a : b)
+#define MAX(a, b) (a > b ? a : b)
+
+#define LOG(msg...) __android_log_print(ANDROID_LOG_VERBOSE, "NativeFilters", msg)
+
+#define JNIFUNCF(cls, name, vars...) Java_com_android_gallery3d_filtershow_filters_ ## cls ## _ ## name(JNIEnv* env, jobject this, vars)
+
+#define RED i
+#define GREEN i+1
+#define BLUE i+2
+#define ALPHA i+3
+#define CLAMP(c) (MAX(0, MIN(255, c)))
+
+#endif // FILTERS_H
diff --git a/jni/filters/gradient.c b/jni/filters/gradient.c
new file mode 100644
index 000000000..1a8569786
--- /dev/null
+++ b/jni/filters/gradient.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "filters.h"
+
+void JNIFUNCF(ImageFilter, nativeApplyGradientFilter, jobject bitmap, jint width, jint height,
+ jintArray redGradient, jintArray greenGradient, jintArray blueGradient)
+{
+ char* destination = 0;
+ jint* redGradientArray = 0;
+ jint* greenGradientArray = 0;
+ jint* blueGradientArray = 0;
+ if (redGradient)
+ redGradientArray = (*env)->GetIntArrayElements(env, redGradient, NULL);
+ if (greenGradient)
+ greenGradientArray = (*env)->GetIntArrayElements(env, greenGradient, NULL);
+ if (blueGradient)
+ blueGradientArray = (*env)->GetIntArrayElements(env, blueGradient, NULL);
+
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ int i;
+ int len = width * height * 4;
+ for (i = 0; i < len; i+=4)
+ {
+ if (redGradient)
+ {
+ int r = destination[RED];
+ r = redGradientArray[r];
+ destination[RED] = r;
+ }
+ if (greenGradient)
+ {
+ int g = destination[GREEN];
+ g = greenGradientArray[g];
+ destination[GREEN] = g;
+ }
+ if (blueGradient)
+ {
+ int b = destination[BLUE];
+ b = blueGradientArray[b];
+ destination[BLUE] = b;
+ }
+ }
+ if (redGradient)
+ (*env)->ReleaseIntArrayElements(env, redGradient, redGradientArray, 0);
+ if (greenGradient)
+ (*env)->ReleaseIntArrayElements(env, greenGradient, greenGradientArray, 0);
+ if (blueGradient)
+ (*env)->ReleaseIntArrayElements(env, blueGradient, blueGradientArray, 0);
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
+
diff --git a/jni/filters/saturated.c b/jni/filters/saturated.c
new file mode 100644
index 000000000..1bc0cc56b
--- /dev/null
+++ b/jni/filters/saturated.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterSaturated, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloat saturation)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ int i;
+ int len = width * height * 4;
+ float Rf = 0.2999f;
+ float Gf = 0.587f;
+ float Bf = 0.114f;
+ float S = saturation;;
+ float MS = 1.0f - S;
+ float Rt = Rf * MS;
+ float Gt = Gf * MS;
+ float Bt = Bf * MS;
+ float R, G, B;
+ for (i = 0; i < len; i+=4)
+ {
+ int r = destination[RED];
+ int g = destination[GREEN];
+ int b = destination[BLUE];
+ int t = (r + g) / 2;
+ R = r;
+ G = g;
+ B = b;
+
+ float Rc = R * (Rt + S) + G * Gt + B * Bt;
+ float Gc = R * Rt + G * (Gt + S) + B * Bt;
+ float Bc = R * Rt + G * Gt + B * (Bt + S);
+
+ destination[RED] = CLAMP(Rc);
+ destination[GREEN] = CLAMP(Gc);
+ destination[BLUE] = CLAMP(Bc);
+ }
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/jni/filters/vignette.c b/jni/filters/vignette.c
new file mode 100644
index 000000000..7cff517b6
--- /dev/null
+++ b/jni/filters/vignette.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "filters.h"
+
+static int* gVignetteMap = 0;
+static int gVignetteWidth = 0;
+static int gVignetteHeight = 0;
+
+void __inline__ createVignetteMap(int w, int h)
+{
+ if (gVignetteMap && (gVignetteWidth != w || gVignetteHeight != h))
+ {
+ free(gVignetteMap);
+ gVignetteMap = 0;
+ }
+ if (gVignetteMap == 0)
+ {
+ gVignetteWidth = w;
+ gVignetteHeight = h;
+
+ int cx = w / 2;
+ int cy = h / 2;
+ int i, j;
+
+ gVignetteMap = malloc(w * h * sizeof(int));
+ float maxDistance = cx * cx * 2.0f;
+ for (i = 0; i < w; i++)
+ {
+ for (j = 0; j < h; j++)
+ {
+ float distance = (cx - i) * (cx - i) + (cy - j) * (cy - j);
+ gVignetteMap[j * w + i] = (int) (distance / maxDistance * 255);
+ }
+ }
+ }
+}
+
+void JNIFUNCF(ImageFilterVignette, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloat strength)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ createVignetteMap(width, height);
+ int i;
+ int len = width * height * 4;
+ int vignette = 0;
+
+ for (i = 0; i < len; i += 4)
+ {
+ vignette = (int) (strength * gVignetteMap[i / 4]);
+ destination[RED] = CLAMP(destination[RED] - vignette);
+ destination[GREEN] = CLAMP(destination[GREEN] - vignette);
+ destination[BLUE] = CLAMP(destination[BLUE] - vignette);
+ }
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/res/drawable/filtershow_background.png b/res/drawable/filtershow_background.png
new file mode 100644
index 000000000..e7646c33a
--- /dev/null
+++ b/res/drawable/filtershow_background.png
Binary files differ
diff --git a/res/drawable/filtershow_border_black.9.png b/res/drawable/filtershow_border_black.9.png
new file mode 100755
index 000000000..24bb5e11d
--- /dev/null
+++ b/res/drawable/filtershow_border_black.9.png
Binary files differ
diff --git a/res/drawable/filtershow_border_film.png b/res/drawable/filtershow_border_film.png
new file mode 100755
index 000000000..9fbd637c1
--- /dev/null
+++ b/res/drawable/filtershow_border_film.png
Binary files differ
diff --git a/res/drawable/filtershow_border_film2.9.png b/res/drawable/filtershow_border_film2.9.png
new file mode 100644
index 000000000..bef5e1061
--- /dev/null
+++ b/res/drawable/filtershow_border_film2.9.png
Binary files differ
diff --git a/res/drawable/filtershow_border_film3.9.png b/res/drawable/filtershow_border_film3.9.png
new file mode 100644
index 000000000..8ce70cb94
--- /dev/null
+++ b/res/drawable/filtershow_border_film3.9.png
Binary files differ
diff --git a/res/drawable/filtershow_border_rounded_black.9.png b/res/drawable/filtershow_border_rounded_black.9.png
new file mode 100755
index 000000000..590a34393
--- /dev/null
+++ b/res/drawable/filtershow_border_rounded_black.9.png
Binary files differ
diff --git a/res/drawable/filtershow_border_rounded_white.9.png b/res/drawable/filtershow_border_rounded_white.9.png
new file mode 100755
index 000000000..4ddc97a19
--- /dev/null
+++ b/res/drawable/filtershow_border_rounded_white.9.png
Binary files differ
diff --git a/res/drawable/filtershow_border_scratch3.9.png b/res/drawable/filtershow_border_scratch3.9.png
new file mode 100644
index 000000000..7552785cd
--- /dev/null
+++ b/res/drawable/filtershow_border_scratch3.9.png
Binary files differ
diff --git a/res/drawable/filtershow_border_white.9.png b/res/drawable/filtershow_border_white.9.png
new file mode 100755
index 000000000..d8c88267e
--- /dev/null
+++ b/res/drawable/filtershow_border_white.9.png
Binary files differ
diff --git a/res/drawable/filtershow_button_background.xml b/res/drawable/filtershow_button_background.xml
new file mode 100644
index 000000000..ee8a92dd9
--- /dev/null
+++ b/res/drawable/filtershow_button_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:drawable="@android:color/holo_blue_light" android:state_pressed="true"/>
+ <item android:drawable="@drawable/filtershow_button_selected_background" android:state_selected="true"/>
+ <item android:drawable="@android:color/transparent" android:state_selected="false"/>
+
+</selector> \ No newline at end of file
diff --git a/res/drawable/filtershow_button_border.png b/res/drawable/filtershow_button_border.png
new file mode 100644
index 000000000..69195a902
--- /dev/null
+++ b/res/drawable/filtershow_button_border.png
Binary files differ
diff --git a/res/drawable/filtershow_button_colors.png b/res/drawable/filtershow_button_colors.png
new file mode 100644
index 000000000..566773dbe
--- /dev/null
+++ b/res/drawable/filtershow_button_colors.png
Binary files differ
diff --git a/res/drawable/filtershow_button_colors_contrast.png b/res/drawable/filtershow_button_colors_contrast.png
new file mode 100644
index 000000000..ccb2dc630
--- /dev/null
+++ b/res/drawable/filtershow_button_colors_contrast.png
Binary files differ
diff --git a/res/drawable/filtershow_button_colors_curve.png b/res/drawable/filtershow_button_colors_curve.png
new file mode 100644
index 000000000..7046bd8dd
--- /dev/null
+++ b/res/drawable/filtershow_button_colors_curve.png
Binary files differ
diff --git a/res/drawable/filtershow_button_colors_sharpen.png b/res/drawable/filtershow_button_colors_sharpen.png
new file mode 100644
index 000000000..2bd0fff6d
--- /dev/null
+++ b/res/drawable/filtershow_button_colors_sharpen.png
Binary files differ
diff --git a/res/drawable/filtershow_button_colors_vignette.png b/res/drawable/filtershow_button_colors_vignette.png
new file mode 100644
index 000000000..ac3d53fa9
--- /dev/null
+++ b/res/drawable/filtershow_button_colors_vignette.png
Binary files differ
diff --git a/res/drawable/filtershow_button_current.png b/res/drawable/filtershow_button_current.png
new file mode 100644
index 000000000..8c4b379b5
--- /dev/null
+++ b/res/drawable/filtershow_button_current.png
Binary files differ
diff --git a/res/drawable/filtershow_button_fx.png b/res/drawable/filtershow_button_fx.png
new file mode 100644
index 000000000..c887fe4d1
--- /dev/null
+++ b/res/drawable/filtershow_button_fx.png
Binary files differ
diff --git a/res/drawable/filtershow_button_geometry.png b/res/drawable/filtershow_button_geometry.png
new file mode 100644
index 000000000..4b8f3b881
--- /dev/null
+++ b/res/drawable/filtershow_button_geometry.png
Binary files differ
diff --git a/res/drawable/filtershow_button_geometry_crop.png b/res/drawable/filtershow_button_geometry_crop.png
new file mode 100644
index 000000000..eb7da1b21
--- /dev/null
+++ b/res/drawable/filtershow_button_geometry_crop.png
Binary files differ
diff --git a/res/drawable/filtershow_button_geometry_flip.png b/res/drawable/filtershow_button_geometry_flip.png
new file mode 100644
index 000000000..dd74813a7
--- /dev/null
+++ b/res/drawable/filtershow_button_geometry_flip.png
Binary files differ
diff --git a/res/drawable/filtershow_button_geometry_rotate.png b/res/drawable/filtershow_button_geometry_rotate.png
new file mode 100644
index 000000000..fa50ce26f
--- /dev/null
+++ b/res/drawable/filtershow_button_geometry_rotate.png
Binary files differ
diff --git a/res/drawable/filtershow_button_geometry_straighten.png b/res/drawable/filtershow_button_geometry_straighten.png
new file mode 100644
index 000000000..309eb5ae2
--- /dev/null
+++ b/res/drawable/filtershow_button_geometry_straighten.png
Binary files differ
diff --git a/res/drawable/filtershow_button_operations.png b/res/drawable/filtershow_button_operations.png
new file mode 100644
index 000000000..79e9a446b
--- /dev/null
+++ b/res/drawable/filtershow_button_operations.png
Binary files differ
diff --git a/res/drawable/filtershow_button_origin.png b/res/drawable/filtershow_button_origin.png
new file mode 100644
index 000000000..0cd0bc28b
--- /dev/null
+++ b/res/drawable/filtershow_button_origin.png
Binary files differ
diff --git a/res/drawable/filtershow_button_redo.png b/res/drawable/filtershow_button_redo.png
new file mode 100644
index 000000000..9daa01c34
--- /dev/null
+++ b/res/drawable/filtershow_button_redo.png
Binary files differ
diff --git a/res/drawable/filtershow_button_selected_background.9.png b/res/drawable/filtershow_button_selected_background.9.png
new file mode 100644
index 000000000..bb412454c
--- /dev/null
+++ b/res/drawable/filtershow_button_selected_background.9.png
Binary files differ
diff --git a/res/drawable/filtershow_button_settings.png b/res/drawable/filtershow_button_settings.png
new file mode 100644
index 000000000..df3925aca
--- /dev/null
+++ b/res/drawable/filtershow_button_settings.png
Binary files differ
diff --git a/res/drawable/filtershow_button_show_original.png b/res/drawable/filtershow_button_show_original.png
new file mode 100644
index 000000000..925954beb
--- /dev/null
+++ b/res/drawable/filtershow_button_show_original.png
Binary files differ
diff --git a/res/drawable/filtershow_button_undo.png b/res/drawable/filtershow_button_undo.png
new file mode 100644
index 000000000..0a7e0d1b8
--- /dev/null
+++ b/res/drawable/filtershow_button_undo.png
Binary files differ
diff --git a/res/layout/filtershow_activity.xml b/res/layout/filtershow_activity.xml
new file mode 100644
index 000000000..1521585ad
--- /dev/null
+++ b/res/layout/filtershow_activity.xml
@@ -0,0 +1,323 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:id="@+id/historyPanel"
+ android:layout_width="200dip"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:visibility="invisible" >
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"
+ android:gravity="center"
+ android:padding="2dip"
+ android:text="@string/history"
+ android:textColor="@android:color/white"
+ android:textSize="24sp"
+ android:textStyle="bold" />
+
+ <ListView
+ android:id="@+id/operationsList"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+ </ListView>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/resetOperationsButton"
+ style="@style/FilterShowHistoryButton"
+ android:text="@string/reset" />
+
+ <Button
+ android:id="@+id/saveOperationsButton"
+ style="@style/FilterShowHistoryButton"
+ android:text="@string/save" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/mainPanel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layerType="hardware"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="48dip" >
+
+ <Button
+ android:id="@+id/saveButton"
+ style="@style/FilterShowTopButton"
+ android:layout_weight="1"
+ android:gravity="center_vertical|left"
+ android:text="@string/done" />
+
+ <ImageButton
+ android:id="@+id/showOriginalButton"
+ style="@style/FilterShowTopButton"
+ android:src="@drawable/filtershow_button_show_original" />
+
+ <ImageButton
+ android:id="@+id/undoButton"
+ style="@style/FilterShowTopButton"
+ android:src="@drawable/filtershow_button_undo" />
+
+ <ImageButton
+ android:id="@+id/redoButton"
+ style="@style/FilterShowTopButton"
+ android:src="@drawable/filtershow_button_redo" />
+
+ <ImageButton
+ android:id="@+id/operationsButton"
+ style="@style/FilterShowTopButton"
+ android:src="@drawable/filtershow_button_operations" />
+
+ </LinearLayout>
+
+ <com.android.gallery3d.filtershow.imageshow.ImageShow
+ android:id="@+id/imageShow"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ <com.android.gallery3d.filtershow.imageshow.ImageStraighten
+ android:id="@+id/imageStraighten"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:visibility="gone" />
+
+ <com.android.gallery3d.filtershow.ui.ImageCurves
+ android:id="@+id/imageCurves"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:visibility="gone" />
+
+ <com.android.gallery3d.filtershow.imageshow.ImageBorder
+ android:id="@+id/imageBorder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:visibility="gone" />
+
+ <HorizontalScrollView
+ android:id="@+id/fxList"
+ android:layout_width="match_parent"
+ android:layout_height="96dip" >
+
+ <LinearLayout
+ android:id="@+id/listFilters"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <HorizontalScrollView
+ android:id="@+id/bordersList"
+ android:layout_width="match_parent"
+ android:layout_height="96dip"
+ android:visibility="gone" >
+
+ <LinearLayout
+ android:id="@+id/listBorders"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <HorizontalScrollView
+ android:id="@+id/gemoetryList"
+ android:layout_width="fill_parent"
+ android:layout_height="96dip"
+ android:background="@color/background_toolbar"
+ android:visibility="gone" >
+
+ <LinearLayout
+ android:id="@+id/listGeometry"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal" >
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/straightenButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_geometry_straighten"
+ android:text="@string/straighten" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/cropButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_geometry_crop"
+ android:text="@string/crop" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/rotateButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_geometry_rotate"
+ android:text="@string/rotate" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/flipButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_geometry_flip"
+ android:text="@string/flip" />
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <HorizontalScrollView
+ android:id="@+id/colorsFxList"
+ android:layout_width="fill_parent"
+ android:layout_height="96dip"
+ android:background="@color/background_toolbar"
+ android:visibility="gone" >
+
+ <LinearLayout
+ android:id="@+id/listColorsFx"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/vignetteButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_vignette"
+ android:text="@string/vignette" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/curvesButtonRGB"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_curve"
+ android:text="@string/curvesRGB" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/curvesButtonRed"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_curve"
+ android:text="@string/curvesRed" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/curvesButtonGreen"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_curve"
+ android:text="@string/curvesGreen" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/curvesButtonBlue"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_curve"
+ android:text="@string/curvesBlue" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/sharpenButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_sharpen"
+ android:text="@string/sharpen" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/vibranceButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_contrast"
+ android:text="@string/vibrance" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/contrastButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_contrast"
+ android:text="@string/contrast" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/saturationButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_contrast"
+ android:text="@string/saturation" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/exposureButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_contrast"
+ android:text="@string/exposure" />
+
+ <com.android.gallery3d.filtershow.ui.ImageButtonTitle
+ android:id="@+id/shadowRecoveryButton"
+ style="@style/FilterShowBottomButton"
+ android:src="@drawable/filtershow_button_colors_contrast"
+ android:text="@string/shadow_recovery" />
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="48dip"
+ android:background="@color/background_main_toolbar" >
+
+ <ImageButton
+ android:id="@+id/fxButton"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/filtershow_button_background"
+ android:scaleType="centerInside"
+ android:src="@drawable/filtershow_button_fx" />
+
+ <ImageButton
+ android:id="@+id/borderButton"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/filtershow_button_background"
+ android:padding="2dip"
+ android:scaleType="centerInside"
+ android:src="@drawable/filtershow_button_border" />
+
+ <ImageButton
+ android:id="@+id/geometryButton"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/filtershow_button_background"
+ android:padding="2dip"
+ android:scaleType="centerInside"
+ android:src="@drawable/filtershow_button_geometry" />
+
+ <ImageButton
+ android:id="@+id/colorsButton"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/filtershow_button_background"
+ android:padding="2dip"
+ android:scaleType="centerInside"
+ android:src="@drawable/filtershow_button_colors" />
+ </LinearLayout>
+ </LinearLayout>
+
+</AbsoluteLayout>
diff --git a/res/layout/filtershow_history_operation_row.xml b/res/layout/filtershow_history_operation_row.xml
new file mode 100644
index 000000000..6f9ea1b8f
--- /dev/null
+++ b/res/layout/filtershow_history_operation_row.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:background="@drawable/filtershow_button_background">
+
+ <ImageView
+ android:id="@+id/selectedMark"
+ android:src="@drawable/filtershow_button_current"
+ android:background="@android:color/transparent"
+ android:layout_width="32dip"
+ android:layout_height="match_parent"
+ android:scaleType="centerInside"
+ android:visibility="invisible"
+ >
+ </ImageView>
+
+ <TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/rowTextView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:padding="10dip"
+ android:textSize="16dip" >
+ </TextView>
+
+ <ImageView
+ android:id="@+id/typeMark"
+ android:src="@drawable/filtershow_button_origin"
+ android:background="@android:color/transparent"
+ android:layout_width="32dip"
+ android:layout_height="match_parent"
+ android:scaleType="centerInside"
+ android:paddingRight="4dip"
+ android:visibility="visible"
+ >
+ </ImageView>
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/menu/filtershow_activity_menu.xml b/res/menu/filtershow_activity_menu.xml
new file mode 100644
index 000000000..44a11e7db
--- /dev/null
+++ b/res/menu/filtershow_activity_menu.xml
@@ -0,0 +1,5 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/menu_settings"
+ android:title="@string/menu_settings"
+ android:orderInCategory="100" />
+</menu>
diff --git a/res/menu/filtershow_menu_curves.xml b/res/menu/filtershow_menu_curves.xml
new file mode 100644
index 000000000..fe190213a
--- /dev/null
+++ b/res/menu/filtershow_menu_curves.xml
@@ -0,0 +1,18 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <group android:id="@+id/group_popupmenu" >
+ <item
+ android:id="@+id/menu1"
+ android:title="@string/curves_channel_rgb"/>
+ <item
+ android:id="@+id/menu2"
+ android:title="@string/curves_channel_red"/>
+ <item
+ android:id="@+id/menu3"
+ android:title="@string/curves_channel_green"/>
+ <item
+ android:id="@+id/menu4"
+ android:title="@string/curves_channel_blue"/>
+ </group>
+
+</menu> \ No newline at end of file
diff --git a/res/values/filtershow_color.xml b/res/values/filtershow_color.xml
new file mode 100644
index 000000000..2d097edaf
--- /dev/null
+++ b/res/values/filtershow_color.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <color name="yellow">#FFFF00</color>
+ <color name="green">#00FF00</color>
+ <color name="red">#FF0000</color>
+ <color name="blue">#0000FF</color>
+ <color name="text_toolbar">#FFFFFF</color>
+ <color name="background_toolbar">#363949</color>
+ <color name="background_main_toolbar">#1e2028</color>
+</resources> \ No newline at end of file
diff --git a/res/values/filtershow_strings.xml b/res/values/filtershow_strings.xml
new file mode 100644
index 000000000..3a0a3afba
--- /dev/null
+++ b/res/values/filtershow_strings.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <string name="app_name">FilterShow</string>
+ <string name="menu_settings">Settings</string>
+ <string name="title_activity_filter_show">FilterShowActivity</string>
+ <string name="save">Save</string>
+ <string name="done">Done</string>
+ <string name="history">History</string>
+ <string name="reset">Reset</string>
+ <string name="undo">Undo</string>
+ <string name="redo">Redo</string>
+ <string name="straighten">Straighten</string>
+ <string name="crop">Crop</string>
+ <string name="rotate">Rotate</string>
+ <string name="flip">Flip</string>
+ <string name="vignette">Vignette</string>
+ <string name="curvesRGB">Curves (RGB)</string>
+ <string name="curvesRed">Curves (Red)</string>
+ <string name="curvesGreen">Curves (Green)</string>
+ <string name="curvesBlue">Curves (Blue)</string>
+ <string name="sharpen">Sharpen</string>
+ <string name="contrast">Contrast</string>
+ <string name="vibrance">Vibrance</string>
+ <string name="exposure">Exposure</string>
+ <string name="saturation">Saturation</string>
+ <string name="shadow_recovery">Shadow Recovery</string>
+ <string name="curves_channel">Channel</string>
+ <string name="curves_channel_rgb">RGB</string>
+ <string name="curves_channel_red">Red</string>
+ <string name="curves_channel_green">Green</string>
+ <string name="curves_channel_blue">Blue</string>
+
+</resources> \ No newline at end of file
diff --git a/res/values/filtershow_styles.xml b/res/values/filtershow_styles.xml
new file mode 100644
index 000000000..14d0d9446
--- /dev/null
+++ b/res/values/filtershow_styles.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+
+ <style name="FilterShowHistoryButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">48dip</item>
+ <item name="android:layout_weight">1</item>
+ <item name="android:background">@drawable/filtershow_button_background</item>
+ <item name="android:gravity">center</item>
+ <item name="android:padding">2dip</item>
+ <item name="android:textColor">@android:color/white</item>
+ <item name="android:textSize">18dip</item>
+ <item name="android:textStyle">bold</item>
+ </style>
+
+ <style name="FilterShowTopButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:background">@drawable/filtershow_button_background</item>
+ <item name="android:gravity">center</item>
+ <item name="android:padding">8dip</item>
+ <item name="android:textColor">@android:color/white</item>
+ <item name="android:textSize">18dip</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:scaleType">centerInside</item>
+ </style>
+
+ <style name="FilterShowBottomButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:background">@drawable/filtershow_button_background</item>
+ <item name="android:gravity">center</item>
+ <item name="android:paddingBottom">16dip</item>
+ <item name="android:textColor">@android:color/white</item>
+ <item name="android:textSize">18dip</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:scaleType">centerInside</item>
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/res/values/filtershow_values_attrs.xml b/res/values/filtershow_values_attrs.xml
new file mode 100644
index 000000000..67c645dd8
--- /dev/null
+++ b/res/values/filtershow_values_attrs.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <declare-styleable name="ImageButtonTitle">
+ <attr name="android:text"/>
+ <attr name="android:textColor"/>
+ </declare-styleable>
+</resources> \ No newline at end of file
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
new file mode 100644
index 000000000..bc6015b52
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -0,0 +1,725 @@
+
+package com.android.gallery3d.filtershow;
+
+import java.util.Vector;
+
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.*;
+import com.android.gallery3d.filtershow.imageshow.ImageBorder;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter;
+import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
+import com.android.gallery3d.filtershow.presets.*;
+import com.android.gallery3d.filtershow.ui.ImageCurves;
+import com.android.gallery3d.R;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+import android.widget.AbsoluteLayout;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.Toast;
+
+@TargetApi(16)
+public class FilterShowActivity extends Activity implements OnItemClickListener {
+
+ private ImageLoader mImageLoader = null;
+ private ImageShow mImageShow = null;
+ private ImageCurves mImageCurves = null;
+ private ImageBorder mImageBorders = null;
+ private ImageStraighten mImageStraighten = null;
+
+ private View mListFx = null;
+ private View mListBorders = null;
+ private View mListGeometry = null;
+ private View mListColors = null;
+
+ private ImageButton mFxButton = null;
+ private ImageButton mBorderButton = null;
+ private ImageButton mGeometryButton = null;
+ private ImageButton mColorsButton = null;
+
+ private ImageButton mVignetteButton = null;
+ private ImageButton mCurvesButtonRGB = null;
+ private ImageButton mCurvesButtonRed = null;
+ private ImageButton mCurvesButtonGreen = null;
+ private ImageButton mCurvesButtonBlue = null;
+ private ImageButton mSharpenButton = null;
+ private ImageButton mContrastButton = null;
+
+ private static final int SELECT_PICTURE = 1;
+ private static final String LOGTAG = "FilterShowActivity";
+ protected static final boolean ANIMATE_PANELS = false;
+
+ private boolean mShowingHistoryPanel = false;
+ private Vector<ImageShow> mImageViews = new Vector<ImageShow>();
+ private Vector<View> mListViews = new Vector<View>();
+ private Vector<ImageButton> mBottomPanelButtons = new Vector<ImageButton>();
+ private Vector<ImageButton> mColorsPanelButtons = new Vector<ImageButton>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.filtershow_activity);
+
+ mImageLoader = new ImageLoader(getApplicationContext());
+
+ LinearLayout listFilters = (LinearLayout) findViewById(R.id.listFilters);
+ LinearLayout listBorders = (LinearLayout) findViewById(R.id.listBorders);
+
+ mImageShow = (ImageShow) findViewById(R.id.imageShow);
+ mImageCurves = (ImageCurves) findViewById(R.id.imageCurves);
+ mImageBorders = (ImageBorder) findViewById(R.id.imageBorder);
+ mImageStraighten = (ImageStraighten) findViewById(R.id.imageStraighten);
+
+ mImageViews.add(mImageShow);
+ mImageViews.add(mImageCurves);
+ mImageViews.add(mImageBorders);
+ mImageViews.add(mImageStraighten);
+
+ mListFx = findViewById(R.id.fxList);
+ mListBorders = findViewById(R.id.bordersList);
+ mListGeometry = findViewById(R.id.gemoetryList);
+ mListColors = findViewById(R.id.colorsFxList);
+ mListViews.add(mListFx);
+ mListViews.add(mListBorders);
+ mListViews.add(mListGeometry);
+ mListViews.add(mListColors);
+
+ mFxButton = (ImageButton) findViewById(R.id.fxButton);
+ mBorderButton = (ImageButton) findViewById(R.id.borderButton);
+ mGeometryButton = (ImageButton) findViewById(R.id.geometryButton);
+ mColorsButton = (ImageButton) findViewById(R.id.colorsButton);
+ mBottomPanelButtons.add(mFxButton);
+ mBottomPanelButtons.add(mBorderButton);
+ mBottomPanelButtons.add(mGeometryButton);
+ mBottomPanelButtons.add(mColorsButton);
+ mFxButton.setSelected(true);
+
+ mVignetteButton = (ImageButton) findViewById(R.id.vignetteButton);
+ mCurvesButtonRGB = (ImageButton) findViewById(R.id.curvesButtonRGB);
+ mCurvesButtonRed = (ImageButton) findViewById(R.id.curvesButtonRed);
+ mCurvesButtonGreen = (ImageButton) findViewById(R.id.curvesButtonGreen);
+ mCurvesButtonBlue = (ImageButton) findViewById(R.id.curvesButtonBlue);
+ mSharpenButton = (ImageButton) findViewById(R.id.sharpenButton);
+ mContrastButton = (ImageButton) findViewById(R.id.contrastButton);
+ mColorsPanelButtons.add(mVignetteButton);
+ mColorsPanelButtons.add(mCurvesButtonRGB);
+ mColorsPanelButtons.add(mCurvesButtonRed);
+ mColorsPanelButtons.add(mCurvesButtonGreen);
+ mColorsPanelButtons.add(mCurvesButtonBlue);
+ mColorsPanelButtons.add(mSharpenButton);
+ mColorsPanelButtons.add(mContrastButton);
+
+ mCurvesButtonRGB.setSelected(true);
+
+ // TODO: instead of click listeners, make the activity the single listener and
+ // do a dispatch in the listener callback method.
+ findViewById(R.id.saveButton).setOnClickListener(createOnClickSaveButton());
+ findViewById(R.id.showOriginalButton).setOnTouchListener(createOnTouchShowOriginalButton());
+ findViewById(R.id.straightenButton).setOnClickListener(createOnClickStraightenButton());
+ findViewById(R.id.cropButton).setOnClickListener(createOnClickCropButton());
+ findViewById(R.id.rotateButton).setOnClickListener(createOnClickRotateButton());
+ findViewById(R.id.flipButton).setOnClickListener(createOnClickFlipButton());
+
+ mVignetteButton.setOnClickListener(createOnClickVignetteButton());
+ mCurvesButtonRGB.setOnClickListener(createOnClickCurvesRGBButton());
+ mCurvesButtonRed.setOnClickListener(createOnClickCurvesRedButton());
+ mCurvesButtonGreen.setOnClickListener(createOnClickCurvesGreenButton());
+ mCurvesButtonBlue.setOnClickListener(createOnClickCurvesBlueButton());
+
+ mSharpenButton.setOnClickListener(createOnClickSharpenButton());
+ mContrastButton.setOnClickListener(createOnClickContrastButton());
+ findViewById(R.id.undoButton).setOnClickListener(createOnClickUndoButton());
+ findViewById(R.id.redoButton).setOnClickListener(createOnClickRedoButton());
+
+ mFxButton.setOnClickListener(createOnClickFxButton());
+ mBorderButton.setOnClickListener(createOnClickBorderButton());
+ mGeometryButton.setOnClickListener(createOnClickGeometryButton());
+ mColorsButton.setOnClickListener(createOnClickColorsButton());
+
+ findViewById(R.id.operationsButton).setOnClickListener(createOnClickOperationsButton());
+ findViewById(R.id.resetOperationsButton).setOnClickListener(
+ createOnClickResetOperationsButton());
+
+ ListView operationsList = (ListView) findViewById(R.id.operationsList);
+ operationsList.setAdapter(mImageShow.getListAdapter());
+ operationsList.setOnItemClickListener(this);
+ mImageLoader.setAdapter((HistoryAdapter) mImageShow.getListAdapter());
+
+ fillListImages(listFilters);
+ fillListBorders(listBorders);
+
+ mImageShow.setImageLoader(mImageLoader);
+ mImageCurves.setImageLoader(mImageLoader);
+ mImageCurves.setMaster(mImageShow);
+ mImageBorders.setImageLoader(mImageLoader);
+ mImageBorders.setMaster(mImageShow);
+ mImageStraighten.setImageLoader(mImageLoader);
+ mImageStraighten.setMaster(mImageShow);
+
+ Intent intent = getIntent();
+ String data = intent.getDataString();
+ if (data != null) {
+ Uri uri = Uri.parse(data);
+ mImageLoader.loadBitmap(uri);
+ } else {
+ pickImage();
+ }
+ }
+
+ private void fillListImages(LinearLayout listFilters) {
+ // TODO: use listview
+ // TODO: load the filters straight from the filesystem
+ ImagePreset[] preset = new ImagePreset[9];
+ int p = 0;
+ preset[p++] = new ImagePreset();
+ preset[p++] = new ImagePresetSaturated();
+ preset[p++] = new ImagePresetOld();
+ preset[p++] = new ImagePresetXProcessing();
+ preset[p++] = new ImagePresetBW();
+ preset[p++] = new ImagePresetBWRed();
+ preset[p++] = new ImagePresetBWGreen();
+ preset[p++] = new ImagePresetBWBlue();
+
+ for (int i = 0; i < p; i++) {
+ ImageSmallFilter filter = new ImageSmallFilter(getBaseContext());
+ preset[i].setIsFx(true);
+ filter.setImagePreset(preset[i]);
+ filter.setController(this);
+ filter.setImageLoader(mImageLoader);
+ listFilters.addView(filter);
+ }
+
+ // Default preset (original)
+ mImageShow.setImagePreset(preset[0]);
+ }
+
+ private void fillListBorders(LinearLayout listBorders) {
+ // TODO: use listview
+ // TODO: load the borders straight from the filesystem
+ int p = 0;
+ ImageFilter[] borders = new ImageFilter[8];
+ borders[p++] = new ImageFilterBorder(null);
+
+ Drawable npd3 = getResources().getDrawable(R.drawable.filtershow_border_film3);
+ borders[p++] = new ImageFilterBorder(npd3);
+ Drawable npd = getResources().getDrawable(
+ R.drawable.filtershow_border_scratch3);
+ borders[p++] = new ImageFilterBorder(npd);
+ Drawable npd2 = getResources().getDrawable(R.drawable.filtershow_border_black);
+ borders[p++] = new ImageFilterBorder(npd2);
+ Drawable npd6 = getResources().getDrawable(
+ R.drawable.filtershow_border_rounded_black);
+ borders[p++] = new ImageFilterBorder(npd6);
+ Drawable npd4 = getResources().getDrawable(R.drawable.filtershow_border_white);
+ borders[p++] = new ImageFilterBorder(npd4);
+ Drawable npd5 = getResources().getDrawable(
+ R.drawable.filtershow_border_rounded_white);
+ borders[p++] = new ImageFilterBorder(npd5);
+
+ for (int i = 0; i < p; i++) {
+ ImageSmallFilter filter = new ImageSmallFilter(getBaseContext());
+ filter.setImageFilter(borders[i]);
+ filter.setController(this);
+ filter.setImageLoader(mImageLoader);
+ listBorders.addView(filter);
+ }
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+ // Some utility functions
+
+ public void showOriginalViews(boolean value) {
+ for (ImageShow views : mImageViews) {
+ views.showOriginal(value);
+ }
+ }
+
+ public void invalidateViews() {
+ for (ImageShow views : mImageViews) {
+ views.invalidate();
+ }
+ }
+
+ public void hideListViews() {
+ for (View view : mListViews) {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ public void hideImageViews() {
+ mImageShow.setShowControls(false); // reset
+ for (View view : mImageViews) {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ public void unselectBottomPanelButtons() {
+ for (ImageButton button : mBottomPanelButtons) {
+ button.setSelected(false);
+ }
+ }
+
+ public void unselectPanelButtons(Vector<ImageButton> buttons) {
+ for (ImageButton button : buttons) {
+ button.setSelected(false);
+ }
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+ // Click handlers for the top row buttons
+
+ private OnClickListener createOnClickSaveButton() {
+ return new View.OnClickListener() {
+ public void onClick(View v) {
+ saveImage();
+ }
+ };
+ }
+
+ private OnTouchListener createOnTouchShowOriginalButton() {
+ return new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ boolean show = false;
+ if ((event.getActionMasked() != MotionEvent.ACTION_UP)
+ || (event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
+ show = true;
+ }
+ showOriginalViews(show);
+ return true;
+ }
+ };
+ }
+
+ private OnClickListener createOnClickUndoButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ HistoryAdapter adapter = (HistoryAdapter) mImageShow
+ .getListAdapter();
+ int position = adapter.undo();
+ mImageShow.onItemClick(position);
+ mImageShow.showToast("Undo");
+ invalidateViews();
+ }
+ };
+ }
+
+ private OnClickListener createOnClickRedoButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ HistoryAdapter adapter = (HistoryAdapter) mImageShow
+ .getListAdapter();
+ int position = adapter.redo();
+ mImageShow.onItemClick(position);
+ mImageShow.showToast("Redo");
+ invalidateViews();
+ }
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+ // history panel...
+
+ private OnClickListener createOnClickOperationsButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final View view = findViewById(R.id.mainPanel);
+ final View viewList = findViewById(R.id.historyPanel);
+ View rootView = viewList.getRootView();
+
+ // TODO: use a custom layout instead of absolutelayout...
+ final AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) view
+ .getLayoutParams();
+ final AbsoluteLayout.LayoutParams lph = (AbsoluteLayout.LayoutParams) viewList
+ .getLayoutParams();
+ final int positionHistoryPanel = (int) (rootView.getWidth() - viewList
+ .getWidth());
+ if (!mShowingHistoryPanel) {
+ mShowingHistoryPanel = true;
+ view.animate().setDuration(200).x(-viewList.getWidth())
+ .withLayer().withEndAction(new Runnable() {
+ public void run() {
+ view.setLayoutParams(lp);
+ lph.x = positionHistoryPanel;
+ viewList.setLayoutParams(lph);
+ viewList.setAlpha(0);
+ viewList.setVisibility(View.VISIBLE);
+ viewList.animate().setDuration(100)
+ .alpha(1.0f).start();
+ }
+ }).start();
+ } else {
+ mShowingHistoryPanel = false;
+ viewList.setVisibility(View.INVISIBLE);
+ view.animate().setDuration(200).x(0).withLayer()
+ .withEndAction(new Runnable() {
+ public void run() {
+ lp.x = 0;
+ view.setLayoutParams(lp);
+ }
+ }).start();
+
+ }
+ }
+ };
+ }
+
+ // reset button in the history panel.
+ private OnClickListener createOnClickResetOperationsButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ HistoryAdapter adapter = (HistoryAdapter) mImageShow
+ .getListAdapter();
+ adapter.reset();
+ ImagePreset original = new ImagePreset(adapter.getItem(0));
+ mImageShow.setImagePreset(original);
+ invalidateViews();
+ }
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+ // Now, let's deal with the bottom panel.
+
+ private OnClickListener createOnClickFxButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ hideListViews();
+ unselectBottomPanelButtons();
+ mImageShow.setVisibility(View.VISIBLE);
+ mListFx.setVisibility(View.VISIBLE);
+ mFxButton.setSelected(true);
+ }
+ };
+ }
+
+ private OnClickListener createOnClickBorderButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ hideListViews();
+ unselectBottomPanelButtons();
+ mImageBorders.setVisibility(View.VISIBLE);
+ mListBorders.setVisibility(View.VISIBLE);
+ mBorderButton.setSelected(true);
+ }
+ };
+ }
+
+ private OnClickListener createOnClickGeometryButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ hideListViews();
+ unselectBottomPanelButtons();
+ mImageStraighten.setVisibility(View.VISIBLE);
+ mListGeometry.setVisibility(View.VISIBLE);
+ mGeometryButton.setSelected(true);
+
+ if (ANIMATE_PANELS) {
+ mListGeometry.setX(mListGeometry.getWidth());
+ mListGeometry.animate().setDuration(200).x(0).withLayer().start();
+ }
+ }
+ };
+ }
+
+ private OnClickListener createOnClickColorsButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ hideListViews();
+ unselectBottomPanelButtons();
+ mListColors.setVisibility(View.VISIBLE);
+ mImageCurves.setVisibility(View.VISIBLE);
+ mColorsButton.setSelected(true);
+
+ if (ANIMATE_PANELS) {
+ View view = findViewById(R.id.listColorsFx);
+ view.setX(mListColors.getWidth());
+ view.animate().setDuration(200).x(0).withLayer().start();
+ }
+ }
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+ // Geometry sub-panel
+
+ private OnClickListener createOnClickStraightenButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageStraighten.setVisibility(View.VISIBLE);
+ mImageStraighten.showToast("Straighten", true);
+ }
+ };
+ }
+
+ private OnClickListener createOnClickCropButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageShow.setVisibility(View.VISIBLE);
+ mImageShow.showToast("Crop", true);
+ }
+ };
+ }
+
+ private OnClickListener createOnClickRotateButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageShow.setVisibility(View.VISIBLE);
+ mImageShow.showToast("Rotate", true);
+ }
+ };
+ }
+
+ private OnClickListener createOnClickFlipButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageShow.setVisibility(View.VISIBLE);
+ mImageShow.showToast("Flip", true);
+ }
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+ // Filters sub-panel
+
+ private OnClickListener createOnClickVignetteButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageShow.setVisibility(View.VISIBLE);
+ mImageShow.setShowControls(true);
+ ImagePreset preset = mImageShow.getImagePreset();
+ ImageFilter filter = preset.getFilter("Vignette");
+ if (filter == null) {
+ ImageFilterVignette vignette = new ImageFilterVignette();
+ ImagePreset copy = new ImagePreset(preset);
+ copy.add(vignette);
+ copy.setHistoryName(vignette.name());
+ copy.setIsFx(false);
+ mImageShow.setImagePreset(copy);
+ }
+ unselectPanelButtons(mColorsPanelButtons);
+ mVignetteButton.setSelected(true);
+ invalidateViews();
+ }
+ };
+ }
+
+ private OnClickListener createOnClickCurvesRGBButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageCurves.setVisibility(View.VISIBLE);
+ unselectPanelButtons(mColorsPanelButtons);
+ mCurvesButtonRGB.setSelected(true);
+ mImageCurves.setUseRed(true);
+ mImageCurves.setUseGreen(true);
+ mImageCurves.setUseBlue(true);
+ mImageCurves.reloadCurve();
+ }
+ };
+ }
+
+ private OnClickListener createOnClickCurvesRedButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageCurves.setVisibility(View.VISIBLE);
+ unselectPanelButtons(mColorsPanelButtons);
+ mCurvesButtonRed.setSelected(true);
+ mImageCurves.setUseRed(true);
+ mImageCurves.setUseGreen(false);
+ mImageCurves.setUseBlue(false);
+ mImageCurves.reloadCurve();
+ }
+ };
+ }
+
+ private OnClickListener createOnClickCurvesGreenButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageCurves.setVisibility(View.VISIBLE);
+ unselectPanelButtons(mColorsPanelButtons);
+ mCurvesButtonGreen.setSelected(true);
+ mImageCurves.setUseRed(false);
+ mImageCurves.setUseGreen(true);
+ mImageCurves.setUseBlue(false);
+ mImageCurves.reloadCurve();
+ }
+ };
+ }
+
+ private OnClickListener createOnClickCurvesBlueButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageCurves.setVisibility(View.VISIBLE);
+ unselectPanelButtons(mColorsPanelButtons);
+ mCurvesButtonBlue.setSelected(true);
+ mImageCurves.setUseRed(false);
+ mImageCurves.setUseGreen(false);
+ mImageCurves.setUseBlue(true);
+ mImageCurves.reloadCurve();
+ }
+ };
+ }
+
+ private OnClickListener createOnClickSharpenButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageShow.setVisibility(View.VISIBLE);
+ unselectPanelButtons(mColorsPanelButtons);
+ mSharpenButton.setSelected(true);
+ mImageShow.showToast("Sharpen", true);
+ }
+ };
+ }
+
+ private OnClickListener createOnClickContrastButton() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideImageViews();
+ mImageShow.setVisibility(View.VISIBLE);
+ unselectPanelButtons(mColorsPanelButtons);
+ mContrastButton.setSelected(true);
+ mImageShow.showToast("Contrast", true);
+ }
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+
+ public float getPixelsFromDip(float value) {
+ Resources r = getResources();
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
+ r.getDisplayMetrics());
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
+ return true;
+ }
+
+ public void useImagePreset(ImagePreset preset) {
+ if (preset == null) {
+ return;
+ }
+ ImagePreset copy = new ImagePreset(preset);
+ mImageShow.setImagePreset(copy);
+ if (preset.isFx()) {
+ // if it's an FX we rest the curve adjustment too
+ mImageCurves.resetCurve();
+ }
+ invalidateViews();
+ }
+
+ public void useImageFilter(ImageFilter imageFilter) {
+ if (imageFilter == null) {
+ return;
+ }
+ ImagePreset oldPreset = mImageShow.getImagePreset();
+ ImagePreset copy = new ImagePreset(oldPreset);
+ // TODO: use a numerical constant instead.
+ if (imageFilter.name().equalsIgnoreCase("Border")) {
+ copy.remove("Border");
+ copy.setHistoryName("Border");
+ }
+ copy.add(imageFilter);
+ invalidateViews();
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ mImageShow.onItemClick(position);
+ invalidateViews();
+ }
+
+ public void pickImage() {
+ Intent intent = new Intent();
+ intent.setType("image/*");
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
+ SELECT_PICTURE);
+ }
+
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.v(LOGTAG, "onActivityResult");
+ if (resultCode == RESULT_OK) {
+ if (requestCode == SELECT_PICTURE) {
+ Uri selectedImageUri = data.getData();
+ mImageLoader.loadBitmap(selectedImageUri);
+ }
+ }
+ }
+
+ public void saveImage() {
+ Toast toast = Toast.makeText(getBaseContext(), getString(R.string.saving_image),
+ Toast.LENGTH_SHORT);
+ toast.setGravity(Gravity.CENTER, 0, 0);
+ toast.show();
+
+ mImageShow.saveImage(this);
+ }
+
+ public void completeSaveImage(Uri saveUri) {
+ setResult(RESULT_OK, new Intent().setData(saveUri));
+ finish();
+ }
+
+ static {
+ System.loadLibrary("filters");
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/HistoryAdapter.java b/src/com/android/gallery3d/filtershow/HistoryAdapter.java
new file mode 100644
index 000000000..ed5b26b34
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/HistoryAdapter.java
@@ -0,0 +1,119 @@
+
+package com.android.gallery3d.filtershow;
+
+import java.util.Vector;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class HistoryAdapter extends ArrayAdapter<ImagePreset> {
+ private static final String LOGTAG = "HistoryAdapter";
+ private int mCurrentPresetPosition = 0;
+
+ public void setCurrentPreset(int n) {
+ mCurrentPresetPosition = n;
+ this.notifyDataSetChanged();
+ }
+
+ public HistoryAdapter(Context context, int resource, int textViewResourceId) {
+ super(context, resource, textViewResourceId);
+ }
+
+ public void reset() {
+ if (getCount() == 0) {
+ return;
+ }
+ ImagePreset first = getItem(getCount() - 1);
+ clear();
+ insert(first, 0);
+ }
+
+ public void insert(ImagePreset preset, int position) {
+ if (getCount() > position && getItem(position).same(preset)) {
+ return;
+ }
+ if (mCurrentPresetPosition != 0) {
+ // in this case, let's discount the presets before the current one
+ Vector<ImagePreset> oldItems = new Vector<ImagePreset>();
+ for (int i = mCurrentPresetPosition; i < getCount(); i++) {
+ oldItems.add(getItem(i));
+ }
+ clear();
+ for (int i = 0; i < oldItems.size(); i++) {
+ add(oldItems.elementAt(i));
+ }
+ mCurrentPresetPosition = position;
+ this.notifyDataSetChanged();
+ }
+ if (getCount() > position && getItem(position).same(preset)) {
+ return;
+ }
+ super.insert(preset, position);
+ mCurrentPresetPosition = position;
+ this.notifyDataSetChanged();
+ }
+
+ public int redo() {
+ mCurrentPresetPosition--;
+ if (mCurrentPresetPosition < 0) {
+ mCurrentPresetPosition = 0;
+ }
+ this.notifyDataSetChanged();
+ return mCurrentPresetPosition;
+ }
+
+ public int undo() {
+ mCurrentPresetPosition++;
+ if (mCurrentPresetPosition >= getCount()) {
+ mCurrentPresetPosition = getCount() - 1;
+ }
+ this.notifyDataSetChanged();
+ return mCurrentPresetPosition;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ view = inflater.inflate(R.layout.filtershow_history_operation_row, null);
+ }
+
+ ImagePreset item = getItem(position);
+ if (item != null) {
+ TextView itemView = (TextView) view.findViewById(R.id.rowTextView);
+ if (itemView != null) {
+ // do whatever you want with your string and long
+ itemView.setText(item.historyName());
+ }
+ ImageView markView = (ImageView) view.findViewById(R.id.selectedMark);
+ if (position == mCurrentPresetPosition) {
+ markView.setVisibility(View.VISIBLE);
+ } else {
+ markView.setVisibility(View.INVISIBLE);
+ }
+ ImageView typeView = (ImageView) view.findViewById(R.id.typeMark);
+ if (position == getCount() - 1) {
+ typeView.setImageResource(R.drawable.filtershow_button_origin);
+ } else if (item.historyName().equalsIgnoreCase("Border")) {
+ typeView.setImageResource(R.drawable.filtershow_button_border);
+ } else if (item.isFx()) {
+ typeView.setImageResource(R.drawable.filtershow_button_fx);
+ } else {
+ typeView.setImageResource(R.drawable.filtershow_button_settings);
+ }
+ }
+
+ return view;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/BitmapCache.java b/src/com/android/gallery3d/filtershow/cache/BitmapCache.java
new file mode 100644
index 000000000..93d57ed80
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/BitmapCache.java
@@ -0,0 +1,115 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import java.nio.ByteBuffer;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+public class BitmapCache {
+
+ private static final String LOGTAG = "BitmapCache";
+ static int mNbItems = 20;
+ private Bitmap[] mBitmaps = new Bitmap[mNbItems];
+ private Object[] mKeys = new Object[mNbItems];
+ private long[] mIndices = new long[mNbItems];
+ private boolean[] mBusyStatus = new boolean[mNbItems];
+
+ private Bitmap mOriginalBitmap = null;
+ private ByteBuffer mBuffer = null;
+ private Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
+ private long mIndex = 0;
+
+ public void setOriginalBitmap(Bitmap original) {
+ if (original == null) {
+ return;
+ }
+ mOriginalBitmap = original.copy(mConfig, true);
+ int size = 4 * original.getWidth() * original.getHeight();
+ mBuffer = ByteBuffer.allocate(size);
+ mOriginalBitmap.copyPixelsToBuffer(mBuffer);
+ mBuffer.rewind();
+ mOriginalBitmap.copyPixelsFromBuffer(mBuffer);
+ for (int i = 0; i < mNbItems; i++) {
+ mBitmaps[i] = mOriginalBitmap.copy(mConfig, true);
+ }
+ }
+
+ private int getOldestPosition() {
+ long minIndex = mIndices[0];
+ int current = 0;
+ for (int i = 1; i < mNbItems; i++) {
+ if (!mBusyStatus[i] && minIndex > mIndices[i]) {
+ minIndex = mIndices[i];
+ current = i;
+ }
+ }
+ return current;
+ }
+
+ public Bitmap put(ImagePreset preset) {
+ int pos = getOldestPosition();
+ return put(preset, pos);
+ }
+
+ public Bitmap put(ImagePreset preset, int pos) {
+ mBitmaps[pos] = mOriginalBitmap.copy(mConfig, true);
+ Bitmap bitmap = mBitmaps[pos];
+
+ preset.apply(bitmap);
+ mKeys[pos] = preset;
+ mIndices[pos] = mIndex++;
+ return bitmap;
+ }
+
+ public int reservePosition(ImagePreset preset) {
+ for (int i = 1; i < mNbItems; i++) {
+ if (mKeys[i] == preset && mBusyStatus[i]) {
+ return -1;
+ }
+ }
+ int pos = getOldestPosition();
+ mBusyStatus[pos] = true;
+ mKeys[pos] = preset;
+ return pos;
+ }
+
+ public void processPosition(int pos) {
+ ImagePreset preset = (ImagePreset) mKeys[pos];
+ mBitmaps[pos] = mOriginalBitmap.copy(mConfig, true);
+ Bitmap bitmap = mBitmaps[pos];
+ preset.apply(bitmap);
+ mIndices[pos] = mIndex++;
+ }
+
+ public void unlockPosition(int pos) {
+ mBusyStatus[pos] = false;
+ }
+
+ public Bitmap get(ImagePreset preset) {
+ int foundPosition = -1;
+ int currentIndice = 0;
+ for (int i = 0; i < mNbItems; i++) {
+ if (mKeys[i] == preset && mBitmaps[i] != null) {
+ if (mIndices[i] > currentIndice) {
+ foundPosition = i;
+ }
+ }
+ }
+ if (foundPosition != -1) {
+ mIndices[foundPosition] = mIndex++;
+ return mBitmaps[foundPosition];
+ }
+ return null;
+ }
+
+ public void reset(ImagePreset preset) {
+ for (int i = 0; i < mNbItems; i++) {
+ if (mKeys[i] == preset && !mBusyStatus[i]) {
+ mBitmaps[i] = null;
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/Cache.java b/src/com/android/gallery3d/filtershow/cache/Cache.java
new file mode 100644
index 000000000..364c9c5f1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/Cache.java
@@ -0,0 +1,19 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public interface Cache {
+ public void setOriginalBitmap(Bitmap bitmap);
+
+ public void reset(ImagePreset preset);
+
+ public void prepare(ImagePreset preset);
+
+ public Bitmap get(ImagePreset preset);
+
+ public void addObserver(ImageShow observer);
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java b/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java
new file mode 100644
index 000000000..8acb539b6
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java
@@ -0,0 +1,59 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+
+public class DelayedPresetCache extends DirectPresetCache implements Callback {
+ private HandlerThread mHandlerThread = null;
+
+ private final static int NEW_PRESET = 0;
+ private final static int COMPUTE_PRESET = 1;
+
+ private Handler mProcessingHandler = null;
+ private Handler mUIHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case NEW_PRESET: {
+ CachedPreset cache = (CachedPreset) msg.obj;
+ didCompute(cache);
+ break;
+ }
+ }
+ }
+ };
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case COMPUTE_PRESET: {
+ CachedPreset cache = (CachedPreset) msg.obj;
+ compute(cache);
+ Message uimsg = mUIHandler.obtainMessage(NEW_PRESET, cache);
+ mUIHandler.sendMessage(uimsg);
+ break;
+ }
+ }
+ return false;
+ }
+
+ public DelayedPresetCache(int size) {
+ super(size);
+ mHandlerThread = new HandlerThread("ImageProcessing", Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ mProcessingHandler = new Handler(mHandlerThread.getLooper(), this);
+ }
+
+ protected void willCompute(CachedPreset cache) {
+ if (cache == null) {
+ return;
+ }
+ cache.setBusy(true);
+ Message msg = mProcessingHandler.obtainMessage(COMPUTE_PRESET, cache);
+ mProcessingHandler.sendMessage(msg);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java b/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java
new file mode 100644
index 000000000..1ba835673
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java
@@ -0,0 +1,144 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import java.util.Vector;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public class DirectPresetCache implements Cache {
+
+ private static final String LOGTAG = "DirectPresetCache";
+ private Bitmap mOriginalBitmap = null;
+ private Vector<ImageShow> mObservers = new Vector<ImageShow>();
+ private Vector<CachedPreset> mCache = new Vector<CachedPreset>();
+ private int mCacheSize = 1;
+ private Bitmap.Config mBitmapConfig = Bitmap.Config.ARGB_8888;
+ private long mGlobalAge = 0;
+
+ protected class CachedPreset {
+ private Bitmap mBitmap = null;
+ private ImagePreset mPreset = null;
+ private long mAge = 0;
+ private boolean mBusy = false;
+
+ public void setBusy(boolean value) {
+ mBusy = value;
+ }
+
+ public boolean busy() {
+ return mBusy;
+ }
+ }
+
+ public DirectPresetCache(int size) {
+ mCacheSize = size;
+ }
+
+ public void setOriginalBitmap(Bitmap bitmap) {
+ mOriginalBitmap = bitmap;
+ notifyObservers();
+ }
+
+ public void notifyObservers() {
+ for (int i = 0; i < mObservers.size(); i++) {
+ ImageShow imageShow = mObservers.elementAt(i);
+ imageShow.invalidate();
+ }
+ }
+
+ public void addObserver(ImageShow observer) {
+ if (!mObservers.contains(observer)) {
+ mObservers.add(observer);
+ }
+ }
+
+ private CachedPreset getCachedPreset(ImagePreset preset) {
+ for (int i = 0; i < mCache.size(); i++) {
+ CachedPreset cache = mCache.elementAt(i);
+ if (cache.mPreset == preset && !cache.mBusy) {
+ return cache;
+ }
+ }
+ return null;
+ }
+
+ public Bitmap get(ImagePreset preset) {
+ // Log.v(LOGTAG, "get preset " + preset.name() + " : " + preset);
+ CachedPreset cache = getCachedPreset(preset);
+ if (cache != null) {
+ return cache.mBitmap;
+ }
+ // Log.v(LOGTAG, "didn't find preset " + preset.name() + " : " + preset
+ // + " we have " + mCache.size() + " elts / " + mCacheSize);
+ return null;
+ }
+
+ public void reset(ImagePreset preset) {
+ CachedPreset cache = getCachedPreset(preset);
+ if (cache != null && !cache.mBusy) {
+ cache.mBitmap = null;
+ willCompute(cache);
+ }
+ }
+
+ private CachedPreset getOldestCachedPreset() {
+ CachedPreset found = null;
+ for (int i = 0; i < mCache.size(); i++) {
+ CachedPreset cache = mCache.elementAt(i);
+ if (cache.mBusy) {
+ continue;
+ }
+ if (found == null) {
+ found = cache;
+ } else {
+ if (found.mAge > cache.mAge) {
+ found = cache;
+ }
+ }
+ }
+ return found;
+ }
+
+ protected void willCompute(CachedPreset cache) {
+ if (cache == null) {
+ return;
+ }
+ cache.mBusy = true;
+ compute(cache);
+ didCompute(cache);
+ }
+
+ protected void didCompute(CachedPreset cache) {
+ cache.mBusy = false;
+ notifyObservers();
+ }
+
+ protected void compute(CachedPreset cache) {
+ cache.mBitmap = null;
+ cache.mBitmap = mOriginalBitmap.copy(mBitmapConfig, true);
+ cache.mPreset.apply(cache.mBitmap);
+ cache.mAge = mGlobalAge++;
+ }
+
+ public void prepare(ImagePreset preset) {
+ // Log.v(LOGTAG, "prepare preset " + preset.name() + " : " + preset);
+ CachedPreset cache = getCachedPreset(preset);
+ if (cache == null) {
+ if (mCache.size() < mCacheSize) {
+ cache = new CachedPreset();
+ mCache.add(cache);
+ } else {
+ cache = getOldestCachedPreset();
+ }
+ if (cache != null) {
+ cache.mPreset = preset;
+ }
+ }
+ willCompute(cache);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
new file mode 100644
index 000000000..19a841a4b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -0,0 +1,260 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import java.io.Closeable;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.HistoryAdapter;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.tools.SaveCopyTask;
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.Log;
+
+public class ImageLoader {
+
+ private static final String LOGTAG = "ImageLoader";
+ private Vector<ImageShow> mListeners = new Vector<ImageShow>();
+ private Bitmap mOriginalBitmapSmall = null;
+ private Bitmap mOriginalBitmapLarge = null;
+ private Bitmap mBackgroundBitmap = null;
+ private Bitmap mFullOriginalBitmap = null;
+ private Bitmap mSaveCopy = null;
+
+ private Cache mCache = new DelayedPresetCache(30);
+ private Cache mHiresCache = new DelayedPresetCache(2);
+
+ private int mOrientation = 0;
+ private HistoryAdapter mAdapter = null;
+ private static int PORTRAIT_ORIENTATION = 6;
+
+ private Context mContext = null;
+ private Uri mUri = null;
+
+ public ImageLoader(Context context) {
+ mContext = context;
+ }
+
+ public void loadBitmap(Uri uri) {
+ mUri = uri;
+ mOrientation = getOrientation(uri);
+ mOriginalBitmapSmall = loadScaledBitmap(uri, 160);
+ mOriginalBitmapLarge = loadScaledBitmap(uri, 320);
+ updateBitmaps();
+ }
+
+ private int getOrientation(Uri uri) {
+ Cursor cursor = mContext.getContentResolver().query(uri,
+ new String[] {
+ MediaStore.Images.ImageColumns.ORIENTATION
+ },
+ null, null, null);
+
+ if (cursor.getCount() != 1) {
+ return -1;
+ }
+
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ }
+
+ private int getOrientationFromPath(String path) {
+ int orientation = -1;
+ try {
+ ExifInterface EXIF = new ExifInterface(path);
+ orientation = EXIF.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+ 1);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return orientation;
+ }
+
+ private void updateBitmaps() {
+ mCache.setOriginalBitmap(mOriginalBitmapSmall);
+ mHiresCache.setOriginalBitmap(mOriginalBitmapLarge);
+ if (mOrientation == PORTRAIT_ORIENTATION) {
+ mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall);
+ mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge);
+ }
+ warnListeners();
+ }
+
+ private Bitmap rotateToPortrait(Bitmap bitmap) {
+ Matrix matrix = new Matrix();
+ matrix.postRotate(90);
+ return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
+ bitmap.getHeight(), matrix, true);
+ }
+
+ private void closeStream(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private Bitmap loadScaledBitmap(Uri uri, int size) {
+ InputStream is = null;
+ try {
+ is = mContext.getContentResolver().openInputStream(uri);
+ Log.v(LOGTAG, "loading uri " + uri.getPath() + " input stream: "
+ + is);
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ o.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, o);
+
+ int width_tmp = o.outWidth;
+ int height_tmp = o.outHeight;
+ int scale = 1;
+ while (true) {
+ if (width_tmp / 2 < size || height_tmp / 2 < size)
+ break;
+ width_tmp /= 2;
+ height_tmp /= 2;
+ scale *= 2;
+ }
+
+ // decode with inSampleSize
+ BitmapFactory.Options o2 = new BitmapFactory.Options();
+ o2.inSampleSize = scale;
+ closeStream(is);
+ is = mContext.getContentResolver().openInputStream(uri);
+ return BitmapFactory.decodeStream(is, null, o2);
+ } catch (FileNotFoundException e) {
+ Log.e(LOGTAG, "FileNotFoundException: " + uri);
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ closeStream(is);
+ }
+ return null;
+ }
+
+ public Bitmap getBackgroundBitmap(Resources resources) {
+ if (mBackgroundBitmap == null) {
+ mBackgroundBitmap = BitmapFactory.decodeResource(resources,
+ R.drawable.filtershow_background);
+ }
+ return mBackgroundBitmap;
+
+ }
+
+ public Bitmap getOriginalBitmapSmall() {
+ return mOriginalBitmapSmall;
+ }
+
+ public Bitmap getOriginalBitmapLarge() {
+ return mOriginalBitmapLarge;
+ }
+
+ public void addListener(ImageShow imageShow) {
+ if (!mListeners.contains(imageShow)) {
+ mListeners.add(imageShow);
+ }
+ }
+
+ public void warnListeners() {
+ for (int i = 0; i < mListeners.size(); i++) {
+ ImageShow imageShow = mListeners.elementAt(i);
+ imageShow.updateImage();
+ }
+ }
+
+ // Caching method
+ public Bitmap getImageForPreset(ImageShow caller, ImagePreset imagePreset,
+ boolean hiRes) {
+ if (mOriginalBitmapSmall == null) {
+ return null;
+ }
+ if (mOriginalBitmapLarge == null) {
+ return null;
+ }
+
+ Bitmap filteredImage = null;
+
+ if (hiRes) {
+ filteredImage = mHiresCache.get(imagePreset);
+ } else {
+ filteredImage = mCache.get(imagePreset);
+ }
+
+ if (filteredImage == null) {
+ if (hiRes) {
+ cachePreset(imagePreset, mHiresCache, caller);
+ } else {
+ cachePreset(imagePreset, mCache, caller);
+ }
+ }
+ return filteredImage;
+ }
+
+ public void resetImageForPreset(ImagePreset imagePreset, ImageShow caller) {
+ mHiresCache.reset(imagePreset);
+ mCache.reset(imagePreset);
+ }
+
+ public Uri saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+
+ if (mFullOriginalBitmap != null) {
+ mFullOriginalBitmap.recycle();
+ }
+
+ InputStream is = null;
+ Uri saveUri = null;
+ try {
+ is = mContext.getContentResolver().openInputStream(mUri);
+ mFullOriginalBitmap = BitmapFactory.decodeStream(is, null, options);
+ // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
+ // exist)
+ mSaveCopy = mFullOriginalBitmap;
+ preset.apply(mSaveCopy);
+ new SaveCopyTask(mContext, mUri, new SaveCopyTask.Callback() {
+
+ @Override
+ public void onComplete(Uri result) {
+ filterShowActivity.completeSaveImage(result);
+ }
+
+ }).execute(mSaveCopy);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } finally {
+ closeStream(is);
+ }
+
+ return saveUri;
+ }
+
+ public void setAdapter(HistoryAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ public HistoryAdapter getHistory() {
+ return mAdapter;
+ }
+
+ private void cachePreset(ImagePreset preset, Cache cache, ImageShow caller) {
+ cache.prepare(preset);
+ cache.addObserver(caller);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
new file mode 100644
index 000000000..6d504b8d4
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
@@ -0,0 +1,39 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilter {
+
+ protected int mParameter = 50;
+
+ public String name() {
+ return "Original";
+ }
+
+ // TODO: maybe use clone instead?
+ public ImageFilter copy() {
+ ImageFilter filter = new ImageFilter();
+ filter.setParameter(mParameter);
+ return filter;
+ }
+
+ public void apply(Bitmap bitmap) {
+ // do nothing here, subclasses will implement filtering here
+ }
+
+ public void setParameter(int value) {
+ mParameter = value;
+ }
+
+ public boolean same(ImageFilter filter) {
+ if (!filter.name().equalsIgnoreCase(name())) {
+ return false;
+ }
+ return true;
+ }
+
+ native protected void nativeApplyGradientFilter(Bitmap bitmap, int w, int h,
+ int[] redGradient, int[] greenGradient, int[] blueGradient);
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java
new file mode 100644
index 000000000..e997d3ee9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java
@@ -0,0 +1,24 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterBW extends ImageFilter {
+
+ public String name() {
+ return "Black & White";
+ }
+
+ public ImageFilter copy() {
+ return new ImageFilterBW();
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+ public void apply(Bitmap bitmap) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ nativeApplyFilter(bitmap, w, h);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java
new file mode 100644
index 000000000..685b8ff7a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java
@@ -0,0 +1,24 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterBWBlue extends ImageFilter {
+
+ public String name() {
+ return "Black & White (Blue)";
+ }
+
+ public ImageFilter copy() {
+ return new ImageFilterBWBlue();
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+ public void apply(Bitmap bitmap) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ nativeApplyFilter(bitmap, w, h);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java
new file mode 100644
index 000000000..9fa73866c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java
@@ -0,0 +1,24 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterBWGreen extends ImageFilter {
+
+ public String name() {
+ return "Black & White (Green)";
+ }
+
+ public ImageFilter copy() {
+ return new ImageFilterBWGreen();
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+ public void apply(Bitmap bitmap) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ nativeApplyFilter(bitmap, w, h);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java
new file mode 100644
index 000000000..c5e444f38
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java
@@ -0,0 +1,24 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterBWRed extends ImageFilter {
+
+ public String name() {
+ return "Black & White (Red)";
+ }
+
+ public ImageFilter copy() {
+ return new ImageFilterBWRed();
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+ public void apply(Bitmap bitmap) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ nativeApplyFilter(bitmap, w, h);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
new file mode 100644
index 000000000..6a714178b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
@@ -0,0 +1,57 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+
+public class ImageFilterBorder extends ImageFilter {
+ Drawable mNinePatch = null;
+
+ public ImageFilterBorder(Drawable ninePatch) {
+ mNinePatch = ninePatch;
+ }
+
+ public String name() {
+ return "Border";
+ }
+
+ public ImageFilter copy() {
+ return new ImageFilterBorder(mNinePatch);
+ }
+
+ public boolean same(ImageFilter filter) {
+ boolean isBorderFilter = super.same(filter);
+ if (!isBorderFilter) {
+ return false;
+ }
+ ImageFilterBorder borderFilter = (ImageFilterBorder) filter;
+ if (mNinePatch != borderFilter.mNinePatch) {
+ return false;
+ }
+ return true;
+ }
+
+ public void setDrawable(Drawable ninePatch) {
+ // TODO: for now we only use nine patch
+ mNinePatch = ninePatch;
+ }
+
+ public void apply(Bitmap bitmap) {
+ if (mNinePatch == null) {
+ return;
+ }
+
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+
+ Rect bounds = new Rect(0, 0, w, h);
+ Canvas canvas = new Canvas(bitmap);
+ mNinePatch.setBounds(bounds);
+ mNinePatch.draw(canvas);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
new file mode 100644
index 000000000..c21ae1f9f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
@@ -0,0 +1,106 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.filtershow.ui.Spline;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+public class ImageFilterCurves extends ImageFilter {
+
+ private static final String LOGTAG = "ImageFilterCurves";
+
+ private float[] mCurve = new float[256];
+
+ private boolean mUseRed = true;
+ private boolean mUseGreen = true;
+ private boolean mUseBlue = true;
+ private String mName = "Curves";
+ private Spline mSpline = null;
+
+ public String name() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public void setUseRed(boolean value) {
+ mUseRed = value;
+ }
+
+ public void setUseGreen(boolean value) {
+ mUseGreen = value;
+ }
+
+ public void setUseBlue(boolean value) {
+ mUseBlue = value;
+ }
+
+ public void setCurve(float[] curve) {
+ for (int i = 0; i < curve.length; i++) {
+ if (i < 256) {
+ mCurve[i] = curve[i];
+ }
+ }
+ }
+
+ public boolean same(ImageFilter filter) {
+ boolean isCurveFilter = super.same(filter);
+ if (!isCurveFilter) {
+ return false;
+ }
+ ImageFilterCurves curve = (ImageFilterCurves) filter;
+ for (int i = 0; i < 256; i++) {
+ if (curve.mCurve[i] != mCurve[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public ImageFilter copy() {
+ ImageFilterCurves curves = new ImageFilterCurves();
+ curves.setCurve(mCurve);
+ curves.setName(mName);
+ curves.setSpline(new Spline(mSpline));
+ return curves;
+ }
+
+ public void populateArray(int[] array) {
+ for (int i = 0; i < 256; i++) {
+ array[i] = (int) (mCurve[i]);
+ }
+ }
+
+ public void apply(Bitmap bitmap) {
+
+ int[] redGradient = null;
+ if (mUseRed) {
+ redGradient = new int[256];
+ populateArray(redGradient);
+ }
+ int[] greenGradient = null;
+ if (mUseGreen) {
+ greenGradient = new int[256];
+ populateArray(greenGradient);
+ }
+ int[] blueGradient = null;
+ if (mUseBlue) {
+ blueGradient = new int[256];
+ populateArray(blueGradient);
+ }
+
+ nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
+ redGradient, greenGradient, blueGradient);
+ }
+
+ public void setSpline(Spline spline) {
+ mSpline = spline;
+ }
+
+ public Spline getSpline() {
+ return mSpline;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java
new file mode 100644
index 000000000..baa85e13f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java
@@ -0,0 +1,98 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Shader.TileMode;
+
+public class ImageFilterGradient extends ImageFilter {
+
+ private Bitmap mGradientBitmap = null;
+ private int[] mColors = null;
+ private float[] mPositions = null;
+
+ public String name() {
+ return "Gradient";
+ }
+
+ public ImageFilter copy() {
+ ImageFilterGradient gradient = new ImageFilterGradient();
+ for (int i = 0; i < mColors.length; i++) {
+ gradient.addColor(mColors[i], mPositions[i]);
+ }
+ return gradient;
+ }
+
+ public void addColor(int color, float position) {
+ int length = 0;
+ if (mColors != null) {
+ length = mColors.length;
+ }
+ int[] colors = new int[length + 1];
+ float[] positions = new float[length + 1];
+
+ for (int i = 0; i < length; i++) {
+ colors[i] = mColors[i];
+ positions[i] = mPositions[i];
+ }
+
+ colors[length] = color;
+ positions[length] = position;
+
+ mColors = colors;
+ mPositions = positions;
+ }
+
+ public void apply(Bitmap bitmap) {
+
+ createGradient();
+ int[] gradient = new int[256];
+ int[] redGradient = new int[256];
+ int[] greenGradient = new int[256];
+ int[] blueGradient = new int[256];
+ mGradientBitmap.getPixels(gradient, 0, 256, 0, 0, 256, 1);
+
+ for (int i = 0; i < 256; i++) {
+ redGradient[i] = Color.red(gradient[i]);
+ greenGradient[i] = Color.green(gradient[i]);
+ blueGradient[i] = Color.blue(gradient[i]);
+ }
+ nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
+ redGradient, greenGradient, blueGradient);
+ }
+
+ public void createGradient() {
+ if (mGradientBitmap != null) {
+ return;
+ }
+
+ /* Create a 200 x 200 bitmap and fill it with black. */
+ Bitmap b = Bitmap.createBitmap(256, 1, Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ c.drawColor(Color.BLACK);
+
+ /* Create your gradient. */
+
+ /*
+ * int[] colors = new int[2]; colors[0] = Color.argb(255, 20, 20, 10);
+ * colors[0] = Color.BLACK; colors[1] = Color.argb(255, 228, 231, 193);
+ * float[] positions = new float[2]; positions[0] = 0; positions[1] = 1;
+ */
+
+ LinearGradient grad = new LinearGradient(0, 0, 255, 1, mColors,
+ mPositions, TileMode.CLAMP);
+
+ /* Draw your gradient to the top of your bitmap. */
+ Paint p = new Paint();
+ p.setStyle(Style.FILL);
+ p.setShader(grad);
+ c.drawRect(0, 0, 256, 1, p);
+ mGradientBitmap = b;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
new file mode 100644
index 000000000..7c03be68b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
@@ -0,0 +1,25 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterSaturated extends ImageFilter {
+
+ public String name() {
+ return "Saturated";
+ }
+
+ public ImageFilter copy() {
+ return new ImageFilterSaturated();
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float saturation);
+
+ public void apply(Bitmap bitmap) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ int p = 100;
+ float value = 2 * p / 100.0f;
+ nativeApplyFilter(bitmap, w, h, value);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java
new file mode 100644
index 000000000..09a3f22f9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java
@@ -0,0 +1,65 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import java.nio.ByteBuffer;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+public class ImageFilterStraighten extends ImageFilter {
+ private Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
+ private float mRotation;
+ private float mZoomFactor;
+
+ public String name() {
+ return "Straighten";
+ }
+
+ public void setRotation(float rotation) {
+ mRotation = rotation;
+ }
+
+ public void setRotationZoomFactor(float zoomFactor) {
+ mZoomFactor = zoomFactor;
+ }
+
+ public ImageFilterStraighten(float rotation, float zoomFactor) {
+ mRotation = rotation;
+ mZoomFactor = zoomFactor;
+ }
+
+ public ImageFilter copy() {
+ return new ImageFilterStraighten(mRotation, mZoomFactor);
+ }
+
+ public void apply(Bitmap bitmap) {
+ // TODO: implement bilinear or bicubic here... for now, just use
+ // canvas to do a simple implementation...
+ // TODO: and be more memory efficient! (do it in native?)
+
+ Bitmap temp = bitmap.copy(mConfig, true);
+ Canvas canvas = new Canvas(temp);
+ canvas.save();
+ Rect bounds = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ float w = temp.getWidth();
+ float h = temp.getHeight();
+ float mw = temp.getWidth() / 2.0f;
+ float mh = temp.getHeight() / 2.0f;
+
+ canvas.scale(mZoomFactor, mZoomFactor, mw, mh);
+ canvas.rotate(mRotation, mw, mh);
+ canvas.drawBitmap(bitmap, bounds, bounds, new Paint());
+ canvas.restore();
+
+ int[] pixels = new int[(int) (w * h)];
+ temp.getPixels(pixels, 0, (int) w, 0, 0, (int) w, (int) h);
+ bitmap.setPixels(pixels, 0, (int) w, 0, 0, (int) w, (int) h);
+ temp.recycle();
+ temp = null;
+ pixels = null;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
new file mode 100644
index 000000000..74d5527c2
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
@@ -0,0 +1,25 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterVignette extends ImageFilter {
+
+ public String name() {
+ return "Vignette";
+ }
+
+ public ImageFilter copy() {
+ return new ImageFilterVignette();
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength);
+
+ public void apply(Bitmap bitmap) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ float p = mParameter;
+ float value = p / 100.0f;
+ nativeApplyFilter(bitmap, w, h, value);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java b/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java
new file mode 100644
index 000000000..00be8267d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java
@@ -0,0 +1,58 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.NinePatchDrawable;
+import android.util.AttributeSet;
+
+public class ImageBorder extends ImageShow {
+ Paint gPaint = new Paint();
+ private ImageShow mMasterImageShow = null;
+
+ public ImageBorder(Context context) {
+ super(context);
+ }
+
+ public ImageBorder(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setMaster(ImageShow master) {
+ mMasterImageShow = master;
+ }
+
+ public ImagePreset getImagePreset() {
+ return mMasterImageShow.getImagePreset();
+ }
+
+ public void setImagePreset(ImagePreset preset, boolean addToHistory) {
+ mMasterImageShow.setImagePreset(preset, addToHistory);
+ }
+
+ public boolean showTitle() {
+ return false;
+ }
+
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ /*
+ * gPaint.setAntiAlias(true); gPaint.setFilterBitmap(true);
+ * gPaint.setDither(true); // Bitmap bmp =
+ * BitmapFactory.decodeResource(getResources(), //
+ * R.drawable.border_scratch); // canvas.drawBitmap(bmp, new Rect(0, 0,
+ * bmp.getWidth(), // bmp.getHeight()), mImageBounds, new Paint()); if
+ * (mImageBounds != null) { NinePatchDrawable npd = (NinePatchDrawable)
+ * getContext() .getResources().getDrawable(R.drawable.border_scratch2);
+ * npd.setBounds(mImageBounds); npd.draw(canvas); }
+ */
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
new file mode 100644
index 000000000..c22339dce
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
@@ -0,0 +1,323 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.HistoryAdapter;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.ui.PieSlider;
+import com.android.gallery3d.filtershow.ui.PieSliderListener;
+import com.android.gallery3d.R;
+import com.android.gallery3d.R.id;
+import com.android.gallery3d.R.layout;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ArrayAdapter;
+
+public class ImageShow extends View implements PieSliderListener {
+
+ private static final String LOGTAG = "ImageShow";
+
+ protected Paint mPaint = new Paint();
+ private static int mTextSize = 24;
+ private static int mTextPadding = 20;
+
+ protected ImagePreset mImagePreset = null;
+ protected ImageLoader mImageLoader = null;
+
+ private Bitmap mBackgroundImage = null;
+ protected Bitmap mForegroundImage = null;
+ protected Bitmap mFilteredImage = null;
+
+ protected PieSlider mPieSlider = new PieSlider();
+
+ private HistoryAdapter mAdapter = null;
+
+ protected Rect mImageBounds = null;
+ protected float mImageRotation = 0;
+ protected float mImageRotationZoomFactor = 0;
+
+ private boolean mShowControls = false;
+ private boolean mShowOriginal = false;
+ private String mToast = null;
+ private boolean mShowToast = false;
+ private boolean mImportantToast = false;
+
+ private Handler mHandler = new Handler();
+
+ public void onNewValue(int value) {
+ getImagePreset().setParameter(value);
+ mImageLoader.resetImageForPreset(getImagePreset(), this);
+ invalidate();
+ }
+
+ public ImageShow(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mPieSlider.setListener(this);
+ mAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row,
+ R.id.rowTextView);
+ }
+
+ public ImageShow(Context context) {
+ super(context);
+ mPieSlider.setListener(this);
+ mAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row,
+ R.id.rowTextView);
+ }
+
+ public void setAdapter(HistoryAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ public void showToast(String text) {
+ showToast(text, false);
+ }
+
+ public void showToast(String text, boolean important) {
+ mToast = text;
+ mShowToast = true;
+ mImportantToast = important;
+ invalidate();
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mShowToast = false;
+ invalidate();
+ }
+ }, 400);
+ }
+
+ public ImagePreset getImagePreset() {
+ return mImagePreset;
+ }
+
+ public Bitmap getOriginalFrontBitmap() {
+ if (mImageLoader != null) {
+ return mImageLoader.getOriginalBitmapLarge();
+ }
+ return null;
+ }
+
+ public void drawToast(Canvas canvas) {
+ if (mShowToast && mToast != null) {
+ Paint paint = new Paint();
+ paint.setTextSize(128);
+ float textWidth = paint.measureText(mToast);
+ int toastX = (int) ((getWidth() - textWidth) / 2.0f);
+ int toastY = (int) (getHeight() / 2.0f);
+
+ paint.setARGB(255, 0, 0, 0);
+ canvas.drawText(mToast, toastX - 2, toastY - 2, paint);
+ canvas.drawText(mToast, toastX - 2, toastY, paint);
+ canvas.drawText(mToast, toastX, toastY - 2, paint);
+ canvas.drawText(mToast, toastX + 2, toastY + 2, paint);
+ canvas.drawText(mToast, toastX + 2, toastY, paint);
+ canvas.drawText(mToast, toastX, toastY + 2, paint);
+ if (mImportantToast) {
+ paint.setARGB(255, 200, 0, 0);
+ } else {
+ paint.setARGB(255, 255, 255, 255);
+ }
+ canvas.drawText(mToast, toastX, toastY, paint);
+ }
+ }
+
+ public void onDraw(Canvas canvas) {
+ if (mBackgroundImage == null) {
+ mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources());
+ }
+ if (mBackgroundImage != null) {
+ Rect s = new Rect(0, 0, mBackgroundImage.getWidth(),
+ mBackgroundImage.getHeight());
+ Rect d = new Rect(0, 0, getWidth(), getHeight());
+ canvas.drawBitmap(mBackgroundImage, s, d, mPaint);
+ }
+
+ Bitmap filteredImage = null;
+ if (mImageLoader != null) {
+ filteredImage = mImageLoader.getImageForPreset(this,
+ getImagePreset(), showHires());
+// Log.v(LOGTAG, "getImageForPreset " + getImagePreset() + " is: " + filteredImage);
+ }
+
+ if (filteredImage == null) {
+ // if no image for the current preset, use the previous one
+ filteredImage = mFilteredImage;
+ } else {
+ mFilteredImage = filteredImage;
+ }
+
+ if (mShowOriginal || mFilteredImage == null) {
+ mFilteredImage = mForegroundImage;
+ }
+
+ if (mFilteredImage != null) {
+ Rect s = new Rect(0, 0, mFilteredImage.getWidth(),
+ mFilteredImage.getHeight());
+ float ratio = mFilteredImage.getWidth()
+ / (float) mFilteredImage.getHeight();
+ float w = getWidth();
+ float h = w / ratio;
+ float ty = (getHeight() - h) / 2.0f;
+ float tx = 0;
+ // t = 0;
+ if (ratio < 1.0f) { // portrait image
+ h = getHeight();
+ w = h * ratio;
+ tx = (getWidth() - w) / 2.0f;
+ ty = 0;
+ }
+ Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
+ (int) (h + ty));
+ mImageBounds = d;
+
+ canvas.drawBitmap(mFilteredImage, s, d, mPaint);
+ }
+
+ if (showTitle() && getImagePreset() != null) {
+ mPaint.setARGB(200, 0, 0, 0);
+ mPaint.setTextSize(mTextSize);
+
+ Rect textRect = new Rect(0, 0, getWidth(), mTextSize + mTextPadding);
+ canvas.drawRect(textRect, mPaint);
+ mPaint.setARGB(255, 200, 200, 200);
+ canvas.drawText(getImagePreset().name(), mTextPadding,
+ 10 + mTextPadding, mPaint);
+ }
+ mPaint.setARGB(255, 150, 150, 150);
+ mPaint.setStrokeWidth(4);
+ canvas.drawLine(0, 0, getWidth(), 0, mPaint);
+
+ if (showControls()) {
+ mPieSlider.onDraw(canvas);
+ }
+
+ drawToast(canvas);
+ }
+
+ public void setShowControls(boolean value) {
+ mShowControls = value;
+ }
+
+ public boolean showControls() {
+ return mShowControls;
+ }
+
+ public boolean showHires() {
+ return true;
+ }
+
+ public boolean showTitle() {
+ return true;
+ }
+
+ public void setImagePreset(ImagePreset preset) {
+ setImagePreset(preset, true);
+ }
+
+ public void setImagePreset(ImagePreset preset, boolean addToHistory) {
+ mImagePreset = preset;
+ if (getImagePreset() != null) {
+// Log.v(LOGTAG, "add " + getImagePreset().name() + " " + getImagePreset());
+ if (addToHistory) {
+ mAdapter.insert(getImagePreset(), 0);
+ }
+ getImagePreset().setEndpoint(this);
+ updateImage();
+ }
+// Log.v(LOGTAG, "invalidate from setImagePreset");
+ invalidate();
+ }
+
+ public void setImageLoader(ImageLoader loader) {
+ mImageLoader = loader;
+ if (mImageLoader != null) {
+ mImageLoader.addListener(this);
+ }
+ }
+
+ public void updateImage() {
+ mForegroundImage = getOriginalFrontBitmap();
+ /*
+ * if (mImageLoader != null) {
+ * mImageLoader.resetImageForPreset(getImagePreset(), this); }
+ */
+
+ /*
+ * if (mForegroundImage != null) { Bitmap filteredImage =
+ * mForegroundImage.copy(mConfig, true);
+ * getImagePreset().apply(filteredImage); invalidate(); }
+ */
+ }
+
+ public void updateFilteredImage(Bitmap bitmap) {
+ mFilteredImage = bitmap;
+ // Log.v(LOGTAG, "invalidate from updateFilteredImage");
+ // invalidate();
+ }
+
+ public void saveImage(FilterShowActivity filterShowActivity) {
+ mImageLoader.saveImage(getImagePreset(), filterShowActivity);
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+ mPieSlider.onTouchEvent(event);
+ // Log.v(LOGTAG, "invalidate from onTouchEvent");
+ invalidate();
+ return true;
+ }
+
+ // listview stuff
+
+ public ArrayAdapter getListAdapter() {
+ return mAdapter;
+ }
+
+ public void onItemClick(int position) {
+ Log.v(LOGTAG, "Click on item " + position);
+ Log.v(LOGTAG, "item " + position + " is " + mAdapter.getItem(position));
+ setImagePreset(new ImagePreset(mAdapter.getItem(position)), false); // we
+ // need
+ // a
+ // copy
+ // from
+ // the
+ // history
+ mAdapter.setCurrentPreset(position);
+ }
+
+ public void showOriginal(boolean show) {
+ mShowOriginal = show;
+ invalidate();
+ }
+
+ public float getImageRotation() {
+ return mImageRotation;
+ }
+
+ public float getImageRotationZoomFactor() {
+ return mImageRotationZoomFactor;
+ }
+
+ public void setImageRotation(float imageRotation,
+ float imageRotationZoomFactor) {
+ if (imageRotation != mImageRotation) {
+ invalidate();
+ }
+ mImageRotation = imageRotation;
+ mImageRotationZoomFactor = imageRotationZoomFactor;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java
new file mode 100644
index 000000000..ed8b17811
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java
@@ -0,0 +1,76 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.MeasureSpec;
+
+public class ImageSmallFilter extends ImageShow implements View.OnClickListener {
+
+ private FilterShowActivity mController = null;
+ private ImageFilter mImageFilter = null;
+
+ public ImageSmallFilter(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setOnClickListener(this);
+ }
+
+ public ImageSmallFilter(Context context) {
+ super(context);
+ setOnClickListener(this);
+ }
+
+ public void setImageFilter(ImageFilter filter) {
+ mImageFilter = filter;
+ mImagePreset = new ImagePreset();
+ mImagePreset.add(mImageFilter);
+ }
+
+ public void setController(FilterShowActivity activity) {
+ mController = activity;
+ }
+
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension((int) (parentHeight * 1.5), parentHeight);
+ }
+
+ /*
+ * protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ * setMeasuredDimension(256, 256); }
+ */
+
+ public void onClick(View v) {
+ if (mController != null) {
+ if (mImageFilter != null) {
+ mController.useImageFilter(mImageFilter);
+ } else if (mImagePreset != null) {
+ mController.useImagePreset(mImagePreset);
+ }
+ }
+ }
+
+ public Bitmap getOriginalFrontBitmap() {
+ if (mImageLoader == null) {
+ return null;
+ }
+ return mImageLoader.getOriginalBitmapSmall();
+ }
+
+ public boolean showControls() {
+ return false;
+ }
+
+ public boolean showHires() {
+ return false;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
new file mode 100644
index 000000000..fbc4666c0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
@@ -0,0 +1,258 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+
+public class ImageStraighten extends ImageShow {
+ private ImageShow mMasterImageShow = null;
+ private float mImageRotation = 0;
+ private float mImageRotationZoomFactor = 0;
+
+ private float mMinAngle = -45;
+ private float mMaxAngle = 45;
+ private float mBaseAngle = 0;
+ private float mAngle = 0;
+ private float mCenterX;
+ private float mCenterY;
+ private float mTouchCenterX;
+ private float mTouchCenterY;
+ private float mCurrentX;
+ private float mCurrentY;
+
+ private enum MODES {
+ NONE, DOWN, UP, MOVE
+ }
+
+ private MODES mMode = MODES.NONE;
+
+ private static final String LOGTAG = "ImageStraighten";
+ private static final Paint gPaint = new Paint();
+
+ public ImageStraighten(Context context) {
+ super(context);
+ }
+
+ public ImageStraighten(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void updateAngle() {
+ mMasterImageShow.setImageRotation(mImageRotation, mImageRotationZoomFactor);
+ }
+
+ public void setMaster(ImageShow master) {
+ mMasterImageShow = master;
+ }
+
+ public boolean showTitle() {
+ return false;
+ }
+
+ public ImagePreset getImagePreset() {
+ return mMasterImageShow.getImagePreset();
+ }
+
+ public void setImagePreset(ImagePreset preset, boolean addToHistory) {
+ mMasterImageShow.setImagePreset(preset, addToHistory);
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////
+ // touch event handler
+
+ public void setActionDown(float x, float y) {
+ mTouchCenterX = x;
+ mTouchCenterY = y;
+ mCurrentX = x;
+ mCurrentY = y;
+ mBaseAngle = mAngle;
+ mMode = MODES.DOWN;
+ }
+
+ public void setActionMove(float x, float y) {
+ mCurrentX = x;
+ mCurrentY = y;
+ mMode = MODES.MOVE;
+ computeValue();
+ }
+
+ public void setActionUp() {
+ mMode = MODES.UP;
+ }
+
+ public void setNoAction() {
+ mMode = MODES.NONE;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case (MotionEvent.ACTION_DOWN):
+ setActionDown(event.getX(), event.getY());
+ break;
+ case (MotionEvent.ACTION_UP):
+ setActionUp();
+ break;
+ case (MotionEvent.ACTION_MOVE):
+ setActionMove(event.getX(), event.getY());
+ break;
+ default:
+ setNoAction();
+ }
+ mImageRotation = mAngle;
+ updateAngle();
+ invalidate();
+ return true;
+ }
+
+ private float angleFor(float dx, float dy) {
+ return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
+ }
+
+ private void computeValue() {
+ if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
+ return;
+ }
+ float dX1 = mTouchCenterX - mCenterX;
+ float dY1 = mTouchCenterY - mCenterY;
+ float dX2 = mCurrentX - mCenterX;
+ float dY2 = mCurrentY - mCenterY;
+
+ float angleA = angleFor(dX1, dY1);
+ float angleB = angleFor(dX2, dY2);
+ float angle = (angleB - angleA) % 360;
+ mAngle = (mBaseAngle - angle) % 360;
+ mAngle = Math.max(mMinAngle, mAngle);
+ mAngle = Math.min(mMaxAngle, mAngle);
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////
+
+ public void onNewValue(int value) {
+ mImageRotation = value;
+ invalidate();
+ }
+
+ public void onDraw(Canvas canvas) {
+ mCenterX = getWidth() / 2;
+ mCenterY = getHeight() / 2;
+ drawStraighten(canvas);
+ }
+
+ public void drawStraighten(Canvas canvas) {
+ gPaint.setAntiAlias(true);
+ gPaint.setFilterBitmap(true);
+ gPaint.setDither(true);
+ gPaint.setARGB(255, 255, 255, 255);
+
+ // canvas.drawARGB(255, 255, 0, 0);
+
+ // TODO: have the concept of multiple image passes (geometry, filter)
+ // so that we can fake the rotation, etc.
+ Bitmap image = null; // mMasterImageShow.mFilteredImage;
+ if (image == null) {
+ image = mMasterImageShow.mForegroundImage;
+ }
+ if (image == null) {
+ return;
+ }
+
+ double iw = image.getWidth();
+ float zoom = (float) (getWidth() / iw);
+ // iw = getWidth(); // we will apply the zoom
+ double ih = image.getHeight();
+ if (ih > iw) {
+ zoom = (float) (getHeight() / ih);
+ }
+ float offset = (float) ((getHeight() - ih) / 2.0f);
+
+ canvas.save();
+ float dx = (float) ((getWidth() - iw) / 2.0f);
+ float dy = offset;
+
+ canvas.rotate(mImageRotation, mCenterX, mCenterY);
+ canvas.scale(zoom, zoom, mCenterX, mCenterY);
+ canvas.translate(dx, dy);
+ canvas.drawBitmap(image, 0, 0, gPaint);
+
+ canvas.restore();
+
+ double deg = mImageRotation;
+ if (deg < 0) {
+ deg = -deg;
+ }
+ double a = Math.toRadians(deg);
+ double sina = Math.sin(a);
+ double cosa = Math.cos(a);
+
+ double rw = image.getWidth();
+ double rh = image.getHeight();
+ double h1 = rh * rh / (rw * sina + rh * cosa);
+ double h2 = rh * rw / (rw * cosa + rh * sina);
+ double hh = Math.min(h1, h2);
+ double ww = hh * rw / rh;
+
+ float left = (float) ((rw - ww) * 0.5f);
+ float top = (float) ((rh - hh) * 0.5f);
+ float right = (float) (left + ww);
+ float bottom = (float) (top + hh);
+
+ RectF boundsRect = new RectF(left, top, right, bottom);
+ Matrix m = new Matrix();
+ m.setScale(zoom, zoom, mCenterX, mCenterY);
+ m.preTranslate(dx, dy);
+ m.mapRect(boundsRect);
+
+ gPaint.setARGB(128, 0, 0, 0);
+ gPaint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(0, 0, getWidth(), boundsRect.top, gPaint);
+ canvas.drawRect(0, boundsRect.bottom, getWidth(), getHeight(), gPaint);
+ canvas.drawRect(0, boundsRect.top, boundsRect.left, boundsRect.bottom,
+ gPaint);
+ canvas.drawRect(boundsRect.right, boundsRect.top, getWidth(),
+ boundsRect.bottom, gPaint);
+
+ Path path = new Path();
+ path.addRect(boundsRect, Path.Direction.CCW);
+ gPaint.setARGB(255, 255, 255, 255);
+ gPaint.setStrokeWidth(3);
+ gPaint.setStyle(Paint.Style.STROKE);
+ canvas.drawPath(path, gPaint);
+ gPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ mImageRotationZoomFactor = (float) (rw / boundsRect.width());
+
+ ImagePreset copy = new ImagePreset(getImagePreset());
+ Log.v(LOGTAG, "creating a new image preset with rotation " + mImageRotation
+ + " and zoom factor: " + mImageRotationZoomFactor);
+
+ copy.setStraightenRotation(mImageRotation, mImageRotationZoomFactor);
+ setImagePreset(copy);
+
+ if (mMode == MODES.MOVE) {
+ canvas.save();
+ canvas.clipPath(path);
+ int n = 16;
+ float step = getWidth() / n;
+ float p = 0;
+ for (int i = 1; i < n; i++) {
+ p = i * step;
+ gPaint.setARGB(60, 255, 255, 255);
+ canvas.drawLine(p, 0, p, getHeight(), gPaint);
+ canvas.drawLine(0, p, getWidth(), p, gPaint);
+ }
+ canvas.restore();
+ }
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
new file mode 100644
index 000000000..8c0e22711
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
@@ -0,0 +1,5 @@
+package com.android.gallery3d.filtershow.imageshow;
+
+public class ImageVignette {
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
new file mode 100644
index 000000000..302b94bed
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
@@ -0,0 +1,193 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import java.util.Vector;
+
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.filters.ImageFilterStraighten;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+
+public class ImagePreset {
+
+ private static final String LOGTAG = "ImagePreset";
+ ImageShow mEndPoint = null;
+ protected int mParameter = 50;
+ protected Vector<ImageFilter> mFilters = new Vector<ImageFilter>();
+ protected String mName = "Original";
+ protected String mHistoryName = "Original";
+ protected boolean mIsFxPreset = false;
+
+ enum FullRotate {
+ ZERO, NINETY, HUNDRED_HEIGHTY, TWO_HUNDRED_SEVENTY
+ }
+
+ protected FullRotate mFullRotate = FullRotate.ZERO;
+ protected float mStraightenRotate = 0;
+ protected float mStraightenZoom = 0;
+ protected boolean mHorizontalFlip = false;
+ protected boolean mVerticalFlip = false;
+ protected RectF mCrop = null;
+
+ public ImagePreset() {
+ setup();
+ }
+
+ public ImagePreset(ImagePreset source) {
+ for (int i = 0; i < source.mFilters.size(); i++) {
+ add(source.mFilters.elementAt(i).copy());
+ }
+ mName = source.name();
+ mHistoryName = source.name();
+ mIsFxPreset = source.isFx();
+
+ mStraightenRotate = source.mStraightenRotate;
+ mStraightenZoom = source.mStraightenZoom;
+ }
+
+ public void setStraightenRotation(float rotate, float zoom) {
+ mStraightenRotate = rotate;
+ mStraightenZoom = zoom;
+ }
+
+ private Bitmap applyGeometry(Bitmap original) {
+ Bitmap bitmap = original;
+
+ if (mFullRotate != FullRotate.ZERO) {
+ // TODO
+ }
+
+// Log.v(LOGTAG, "applyGeometry with rotate " + mStraightenRotate + " and zoom "
+ // + mStraightenZoom);
+
+ if (mStraightenRotate != 0) {
+ // TODO: keep the instances around
+ ImageFilter straighten = new ImageFilterStraighten(mStraightenRotate, mStraightenZoom);
+ straighten.apply(bitmap);
+ straighten = null;
+ }
+
+ return bitmap;
+ }
+
+ public boolean isFx() {
+ return mIsFxPreset;
+ }
+
+ public void setIsFx(boolean value) {
+ mIsFxPreset = value;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ mHistoryName = name;
+ }
+
+ public void setHistoryName(String name) {
+ mHistoryName = name;
+ }
+
+ public boolean same(ImagePreset preset) {
+ if (preset.mFilters.size() != mFilters.size()) {
+ return false;
+ }
+ if (!mName.equalsIgnoreCase(preset.name())) {
+ return false;
+ }
+ if (mStraightenRotate != preset.mStraightenRotate) {
+ return false;
+ }
+ for (int i = 0; i < preset.mFilters.size(); i++) {
+ ImageFilter a = preset.mFilters.elementAt(i);
+ ImageFilter b = mFilters.elementAt(i);
+ if (!a.same(b)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public String name() {
+ return mName;
+ }
+
+ public String historyName() {
+ return mHistoryName;
+ }
+
+ public void add(ImageFilter preset) {
+ mFilters.add(preset);
+ }
+
+ public void remove(String filterName) {
+ ImageFilter filter = getFilter(filterName);
+ if (filter != null) {
+ mFilters.remove(filter);
+ }
+ }
+
+ public int getCount() {
+ return mFilters.size();
+ }
+
+ public ImageFilter getFilter(String name) {
+ for (int i = 0; i < mFilters.size(); i++) {
+ ImageFilter filter = mFilters.elementAt(i);
+ if (filter.name().equalsIgnoreCase(name)) {
+ return filter;
+ }
+ }
+ return null;
+ }
+
+ public void setup() {
+ // do nothing here
+ }
+
+ public void setEndpoint(ImageShow image) {
+ mEndPoint = image;
+ }
+
+ public Bitmap apply(Bitmap original) {
+ // First we apply any transform -- 90 rotate, flip, straighten, crop
+ Bitmap bitmap = applyGeometry(original);
+
+ // TODO -- apply borders separately
+ ImageFilter borderFilter = null;
+ for (int i = 0; i < mFilters.size(); i++) {
+ ImageFilter filter = mFilters.elementAt(i);
+ if (filter.name().equalsIgnoreCase("Border")) {
+ // TODO don't use the name as an id
+ borderFilter = filter;
+ } else {
+ filter.apply(bitmap);
+ }
+ }
+ if (borderFilter != null) {
+ borderFilter.apply(bitmap);
+ }
+ if (mEndPoint != null) {
+ mEndPoint.updateFilteredImage(bitmap);
+ }
+ return bitmap;
+ }
+
+ /*
+ * public void applyFilter(Bitmap bitmap) { // do nothing here, subclasses
+ * will implement filtering here } native protected void
+ * nativeApplyGradientFilter(Bitmap bitmap, int w, int h, int[] redGradient,
+ * int[] greenGradient, int[] blueGradient);
+ */
+
+ public void setParameter(int value) {
+ mParameter = value;
+ for (int i = 0; i < mFilters.size(); i++) {
+ ImageFilter filter = mFilters.elementAt(i);
+ filter.setParameter(value);
+ }
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java
new file mode 100644
index 000000000..a270080f9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterBW;
+
+public class ImagePresetBW extends ImagePreset {
+
+ public String name() {
+ return "Black & White";
+ }
+
+ public void setup() {
+ mFilters.add(new ImageFilterBW());
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java
new file mode 100644
index 000000000..1783b482b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterBWBlue;
+
+public class ImagePresetBWBlue extends ImagePreset {
+
+ public String name() {
+ return "Black & White (Blue)";
+ }
+
+ public void setup() {
+ mFilters.add(new ImageFilterBWBlue());
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java
new file mode 100644
index 000000000..5b317a1e0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterBWGreen;
+
+public class ImagePresetBWGreen extends ImagePreset {
+
+ public String name() {
+ return "Black & White (Green)";
+ }
+
+ public void setup() {
+ mFilters.add(new ImageFilterBWGreen());
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java
new file mode 100644
index 000000000..7b9f0e141
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterBWRed;
+
+public class ImagePresetBWRed extends ImagePreset {
+
+ public String name() {
+ return "Black & White (Red)";
+ }
+
+ public void setup() {
+ mFilters.add(new ImageFilterBWRed());
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java
new file mode 100644
index 000000000..56e1dab45
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java
@@ -0,0 +1,21 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterGradient;
+
+import android.graphics.Color;
+
+public class ImagePresetOld extends ImagePreset {
+
+ public String name() {
+ return "Old";
+ }
+
+ public void setup() {
+ ImageFilterGradient filter = new ImageFilterGradient();
+ filter.addColor(Color.BLACK, 0.0f);
+ filter.addColor(Color.argb(255, 228, 231, 193), 1.0f);
+ mFilters.add(filter);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java
new file mode 100644
index 000000000..cf280ee47
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterSaturated;
+
+public class ImagePresetSaturated extends ImagePreset {
+
+ public String name() {
+ return "Saturated";
+ }
+
+ public void setup() {
+ mFilters.add(new ImageFilterSaturated());
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java
new file mode 100644
index 000000000..3e744be77
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java
@@ -0,0 +1,22 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterGradient;
+
+import android.graphics.Color;
+
+public class ImagePresetXProcessing extends ImagePreset {
+
+ public String name() {
+ return "X-Process";
+ }
+
+ public void setup() {
+ ImageFilterGradient filter = new ImageFilterGradient();
+ filter.addColor(Color.BLACK, 0.0f);
+ filter.addColor(Color.argb(255, 29, 82, 83), 0.4f);
+ filter.addColor(Color.argb(255, 211, 217, 186), 1.0f);
+ mFilters.add(filter);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
new file mode 100644
index 000000000..8ca21e285
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
@@ -0,0 +1,246 @@
+/*
+ * 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.filtershow.tools;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+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 com.android.gallery3d.util.BucketNames;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+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> {
+
+ public static final String DOWNLOAD = "download";
+ public static final String DEFAULT_SAVE_DIRECTORY = "Download";
+ private static final int DEFAULT_COMPRESS_QUALITY = 95;
+
+ /**
+ * 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, File directory, String filename,
+ CompressFormat format) {
+
+ if (directory == null) {
+ directory = context.getCacheDir();
+ } else {
+ // Check if the given directory exists or try to create it.
+ if (!directory.isDirectory() && !directory.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;
+ }
+
+ private void closeStream(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Callback for the completed asynchronous task.
+ */
+ public interface Callback {
+
+ void onComplete(Uri result);
+ }
+
+ private interface ContentResolverQueryCallback {
+
+ void onCursorResult(Cursor cursor);
+ }
+
+ private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
+
+ private final Context context;
+ private final Uri sourceUri;
+ private final Callback callback;
+ private final String saveFileName;
+ private String saveFolderName;
+
+ public SaveCopyTask(Context context, Uri sourceUri, Callback callback) {
+ this.context = context;
+ this.sourceUri = sourceUri;
+ this.callback = callback;
+
+ 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;
+ }
+ // Use the default save directory if the source directory cannot be
+ // saved.
+ File saveDirectory = getSaveDirectory();
+ if ((saveDirectory == null) || !saveDirectory.canWrite()) {
+ saveDirectory = new File(Environment.getExternalStorageDirectory(),
+ DOWNLOAD);
+ saveFolderName = DEFAULT_SAVE_DIRECTORY;
+ } else {
+ saveFolderName = saveDirectory.getName();
+ }
+
+ Bitmap bitmap = params[0];
+
+ File file = saveBitmap(bitmap, saveDirectory, saveFileName,
+ Bitmap.CompressFormat.JPEG);
+
+ 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, saveFolderName); Toast toast
+ * = Toast.makeText(context, message, Toast.LENGTH_SHORT);
+ * toast.setGravity(Gravity.CENTER, 0, 0); toast.show();
+ */
+ if (callback != null) {
+ callback.onComplete(result);
+ }
+ }
+
+ private void querySource(String[] projection,
+ ContentResolverQueryCallback callback) {
+ ContentResolver contentResolver = context.getContentResolver();
+ Cursor cursor = null;
+ try {
+ cursor = contentResolver.query(sourceUri, projection, null, null,
+ null);
+ if ((cursor != null) && cursor.moveToNext()) {
+ callback.onCursorResult(cursor);
+ }
+ } catch (Exception e) {
+ // Ignore error for lacking the data column from the source.
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private File getSaveDirectory() {
+ final File[] dir = new File[1];
+ querySource(new String[] {
+ ImageColumns.DATA
+ },
+ new ContentResolverQueryCallback() {
+
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ dir[0] = new File(cursor.getString(0)).getParentFile();
+ }
+ });
+ return dir[0];
+ }
+
+ /**
+ * Insert the content (saved file) with proper source photo properties.
+ */
+ private Uri insertContent(File file) {
+ long now = System.currentTimeMillis() / 1000;
+
+ final ContentValues values = new ContentValues();
+ values.put(Images.Media.TITLE, saveFileName);
+ values.put(Images.Media.DISPLAY_NAME, file.getName());
+ values.put(Images.Media.MIME_TYPE, "image/jpeg");
+ values.put(Images.Media.DATE_TAKEN, now);
+ 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());
+
+ String[] projection = new String[] {
+ ImageColumns.DATE_TAKEN,
+ ImageColumns.LATITUDE, ImageColumns.LONGITUDE,
+ };
+ querySource(projection, new ContentResolverQueryCallback() {
+
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
+
+ double latitude = cursor.getDouble(1);
+ double longitude = cursor.getDouble(2);
+ // TODO: Change || to && after the default location issue is
+ // fixed.
+ if ((latitude != 0f) || (longitude != 0f)) {
+ values.put(Images.Media.LATITUDE, latitude);
+ values.put(Images.Media.LONGITUDE, longitude);
+ }
+ }
+ });
+
+ return context.getContentResolver().insert(
+ Images.Media.EXTERNAL_CONTENT_URI, values);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/ControlPoint.java b/src/com/android/gallery3d/filtershow/ui/ControlPoint.java
new file mode 100644
index 000000000..6daa96182
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/ControlPoint.java
@@ -0,0 +1,39 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+class ControlPoint implements Comparable {
+ public ControlPoint(float px, float py) {
+ x = px;
+ y = py;
+ }
+
+ public ControlPoint multiply(float m) {
+ return new ControlPoint(x * m, y * m);
+ }
+
+ public ControlPoint add(ControlPoint v) {
+ return new ControlPoint(x + v.x, y + v.y);
+ }
+
+ public ControlPoint sub(ControlPoint v) {
+ return new ControlPoint(x - v.x, y - v.y);
+ }
+
+ public float x;
+ public float y;
+
+ public ControlPoint copy() {
+ return new ControlPoint(x, y);
+ }
+
+ @Override
+ public int compareTo(Object another) {
+ ControlPoint p = (ControlPoint) another;
+ if (p.x < x) {
+ return 1;
+ } else if (p.x > x) {
+ return -1;
+ }
+ return 0;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java b/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java
new file mode 100644
index 000000000..7f0b0437d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java
@@ -0,0 +1,42 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.ImageButton;
+
+public class ImageButtonTitle extends ImageButton {
+ private static final String LOGTAG = "ImageButtonTitle";
+ private String mText = null;
+ private static int mTextSize = 24;
+ private static int mTextPadding = 20;
+ private static Paint gPaint = new Paint();
+
+ public ImageButtonTitle(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = getContext().obtainStyledAttributes(
+ attrs, R.styleable.ImageButtonTitle);
+
+ mText = a.getString(R.styleable.ImageButtonTitle_android_text);
+ }
+
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mText != null) {
+ gPaint.setARGB(255, 255, 255, 255);
+ gPaint.setTextSize(mTextSize);
+ float textWidth = gPaint.measureText(mText);
+ int x = (int) ((getWidth() - textWidth) / 2);
+ int y = getHeight() - mTextPadding;
+
+ canvas.drawText(mText, x, y, gPaint);
+ }
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
new file mode 100644
index 000000000..8091c1e7b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
@@ -0,0 +1,408 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.ui.ControlPoint;
+import com.android.gallery3d.filtershow.ui.Spline;
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.PopupMenu;
+import android.widget.Toast;
+
+public class ImageCurves extends ImageShow {
+
+ private static final String LOGTAG = "ImageCurves";
+ Paint gPaint = new Paint();
+ Spline mSpline = null;
+ Path gPathSpline = new Path();
+ float[] mAppliedCurve = new float[256];
+ private boolean mDidAddPoint = false;
+ private boolean mDidDelete = false;
+ private ImageShow mMasterImageShow = null;
+ private ControlPoint mCurrentControlPoint = null;
+ private boolean mUseRed = true;
+ private boolean mUseGreen = true;
+ private boolean mUseBlue = true;
+
+ public ImageCurves(Context context) {
+ super(context);
+ resetCurve();
+ }
+
+ public ImageCurves(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ resetCurve();
+ }
+
+ public void setMaster(ImageShow master) {
+ mMasterImageShow = master;
+ }
+
+ public boolean showTitle() {
+ return false;
+ }
+
+ public void setUseRed(boolean value) {
+ mUseRed = value;
+ }
+
+ public void setUseGreen(boolean value) {
+ mUseGreen = value;
+ }
+
+ public void setUseBlue(boolean value) {
+ mUseBlue = value;
+ }
+
+ public void reloadCurve() {
+ if (mMasterImageShow != null) {
+ String filterName = getFilterName();
+ ImageFilterCurves filter = (ImageFilterCurves) getImagePreset()
+ .getFilter(filterName);
+ if (filter == null) {
+ resetCurve();
+ return;
+ }
+ mSpline = new Spline(filter.getSpline());
+ applyNewCurve();
+ }
+ }
+
+ public void resetCurve() {
+ mSpline = new Spline();
+
+ mSpline.addPoint(0.0f, 1.0f);
+ mSpline.addPoint(1.0f, 0.0f);
+ if (mMasterImageShow != null) {
+ applyNewCurve();
+ }
+ }
+
+ public ImagePreset getImagePreset() {
+ return mMasterImageShow.getImagePreset();
+ }
+
+ public void setImagePreset(ImagePreset preset, boolean addToHistory) {
+ mMasterImageShow.setImagePreset(preset, addToHistory);
+ }
+
+ public float getImageRotation() {
+ return mMasterImageShow.getImageRotation();
+ }
+
+ public float getImageRotationZoomFactor() {
+ return mMasterImageShow.getImageRotationZoomFactor();
+ }
+
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ gPaint.setAntiAlias(true);
+ gPaint.setFilterBitmap(true);
+ gPaint.setDither(true);
+
+ drawGrid(canvas);
+ drawSpline(canvas);
+
+ drawToast(canvas);
+ }
+
+ private void drawGrid(Canvas canvas) {
+ float w = getWidth();
+ float h = getHeight();
+
+ // Grid
+ gPaint.setARGB(128, 150, 150, 150);
+ gPaint.setStrokeWidth(1);
+
+ float stepH = h / 9;
+ float stepW = w / 9;
+
+ // central diagonal
+ gPaint.setARGB(255, 100, 100, 100);
+ gPaint.setStrokeWidth(2);
+ canvas.drawLine(0, h, w, 0, gPaint);
+
+ gPaint.setARGB(128, 200, 200, 200);
+ gPaint.setStrokeWidth(4);
+ stepH = h / 3;
+ stepW = w / 3;
+ for (int j = 1; j < 3; j++) {
+ canvas.drawLine(0, j * stepH, w, j * stepH, gPaint);
+ canvas.drawLine(j * stepW, 0, j * stepW, h, gPaint);
+ }
+ }
+
+ private void drawSpline(Canvas canvas) {
+ float w = getWidth();
+ float h = getHeight();
+
+ gPathSpline.reset();
+ for (int x = 0; x < w; x += 11) {
+ float fx = x / w;
+ ControlPoint drawPoint = mSpline.getPoint(fx);
+ float newX = drawPoint.x * w;
+ float newY = drawPoint.y * h;
+ if (x == 0) {
+ gPathSpline.moveTo(newX, newY);
+ } else {
+ gPathSpline.lineTo(newX, newY);
+ }
+ }
+
+ gPaint.setStrokeWidth(10);
+ gPaint.setStyle(Paint.Style.STROKE);
+ gPaint.setARGB(255, 50, 50, 50);
+ canvas.drawPath(gPathSpline, gPaint);
+ gPaint.setStrokeWidth(5);
+ gPaint.setARGB(255, 150, 150, 150);
+ canvas.drawPath(gPathSpline, gPaint);
+
+ gPaint.setARGB(255, 150, 150, 150);
+ for (int j = 1; j < mSpline.getNbPoints() - 1; j++) {
+ ControlPoint point = mSpline.getPoint(j);
+ gPaint.setStrokeWidth(10);
+ gPaint.setARGB(255, 50, 50, 100);
+ canvas.drawCircle(point.x * w, point.y * h, 30, gPaint);
+ gPaint.setStrokeWidth(5);
+ gPaint.setARGB(255, 150, 150, 200);
+ canvas.drawCircle(point.x * w, point.y * h, 30, gPaint);
+ }
+ }
+
+ private int pickControlPoint(float x, float y) {
+ int pick = 0;
+ float px = mSpline.getPoint(0).x;
+ float py = mSpline.getPoint(0).y;
+ double delta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y));
+ for (int i = 1; i < mSpline.getNbPoints(); i++) {
+ px = mSpline.getPoint(i).x;
+ py = mSpline.getPoint(i).y;
+ double currentDelta = Math.sqrt((px - x) * (px - x) + (py - y)
+ * (py - y));
+ if (currentDelta < delta) {
+ delta = currentDelta;
+ pick = i;
+ }
+ }
+
+ if (!mDidAddPoint && (delta * getWidth() > 100)
+ && (mSpline.getNbPoints() < 10)) {
+ return -1;
+ }
+
+ return pick;// mSpline.getPoint(pick);
+ }
+
+ public void showPopupMenu(View v) {
+ // TODO: sort out the popup menu UI for curves
+ final Context context = v.getContext();
+ PopupMenu popupMenu = new PopupMenu(v.getContext(), v);
+ popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves,
+ popupMenu.getMenu());
+
+ popupMenu
+ .setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ Toast.makeText(context, item.toString(),
+ Toast.LENGTH_LONG).show();
+ return true;
+ }
+ });
+
+ popupMenu.show();
+ }
+
+ private String getFilterName() {
+ String filterName = "Curves";
+ if (mUseRed && !mUseGreen && !mUseBlue) {
+ filterName = "CurvesRed";
+ } else if (!mUseRed && mUseGreen && !mUseBlue) {
+ filterName = "CurvesGreen";
+ } else if (!mUseRed && !mUseGreen && mUseBlue) {
+ filterName = "CurvesBlue";
+ }
+ return filterName;
+ }
+
+ @Override
+ public synchronized boolean onTouchEvent(MotionEvent e) {
+ float posX = e.getX() / getWidth();
+ float posY = e.getY() / getHeight();
+
+ /*
+ * if (true) { showPopupMenu(this); return true; }
+ */
+
+ // ControlPoint point = null;
+
+ // Log.v(LOGTAG, "onTouchEvent - " + e + " action masked : " +
+ // e.getActionMasked());
+
+ if (e.getActionMasked() == MotionEvent.ACTION_UP) {
+ applyNewCurve();
+ // Log.v(LOGTAG, "ACTION UP, mCurrentControlPoint set to null!");
+ mCurrentControlPoint = null;
+ ImagePreset copy = new ImagePreset(getImagePreset());
+
+ if (mUseRed && mUseGreen && mUseBlue) {
+ copy.setHistoryName("Curves (RGB)");
+ } else if (mUseRed) {
+ copy.setHistoryName("Curves (Red)");
+ } else if (mUseGreen) {
+ copy.setHistoryName("Curves (Green)");
+ } else if (mUseBlue) {
+ copy.setHistoryName("Curves (Blue)");
+ }
+ copy.setIsFx(false);
+ mImageLoader.getHistory().insert(copy, 0);
+
+ invalidate();
+ mDidAddPoint = false;
+ if (mDidDelete) {
+ mDidDelete = false;
+ }
+ return true;
+ }
+
+ if (mDidDelete) {
+ return true;
+ }
+ // Log.v(LOGTAG, "ACTION DOWN, mCurrentControlPoint is " +
+ // mCurrentControlPoint);
+
+ int pick = pickControlPoint(posX, posY);
+ // Log.v(LOGTAG, "ACTION DOWN, pick is " + pick);
+ if (mCurrentControlPoint == null) {
+ if (pick == -1) {
+ mCurrentControlPoint = new ControlPoint(posX, posY);
+ mSpline.addPoint(mCurrentControlPoint);
+ mDidAddPoint = true;
+ // Log.v(LOGTAG, "ACTION DOWN - 2, added a new control point! "
+ // + mCurrentControlPoint);
+
+ } else {
+ mCurrentControlPoint = mSpline.getPoint(pick);
+ // Log.v(LOGTAG, "ACTION DOWN - 2, picking up control point " +
+ // mCurrentControlPoint + " at pick " + pick);
+ }
+ }
+ // Log.v(LOGTAG, "ACTION DOWN - 3, pick is " + pick);
+
+ if (!((mCurrentControlPoint.x == 0 && mCurrentControlPoint.y == 1) || (mCurrentControlPoint.x == 1 && mCurrentControlPoint.y == 0))) {
+ if (mSpline.isPointContained(posX, pick)) {
+ mCurrentControlPoint.x = posX;
+ mCurrentControlPoint.y = posY;
+ // Log.v(LOGTAG, "ACTION DOWN - 4, move control point " +
+ // mCurrentControlPoint);
+ } else if (pick != -1) {
+ // Log.v(LOGTAG, "ACTION DOWN - 4, delete pick " + pick);
+ mSpline.deletePoint(pick);
+ mDidDelete = true;
+ }
+ }
+ // Log.v(LOGTAG, "ACTION DOWN - 5, DONE");
+ applyNewCurve();
+ invalidate();
+ return true;
+ }
+
+ public synchronized void applyNewCurve() {
+ ControlPoint[] points = new ControlPoint[256];
+ for (int i = 0; i < 256; i++) {
+ float v = i / 255.0f;
+ ControlPoint p = mSpline.getPoint(v);
+ points[i] = p;
+ }
+ for (int i = 0; i < 256; i++) {
+ mAppliedCurve[i] = -1;
+ }
+ for (int i = 0; i < 256; i++) {
+ int index = (int) (points[i].x * 255);
+ if (index >= 0 && index <= 255) {
+ float v = 1.0f - points[i].y;
+ if (v < 0) {
+ v = 0;
+ }
+ if (v > 1.0f) {
+ v = 1.0f;
+ }
+ mAppliedCurve[index] = v;
+ }
+ }
+ float prev = 0;
+ for (int i = 0; i < 256; i++) {
+ if (mAppliedCurve[i] == -1) {
+ // need to interpolate...
+ int j = i + 1;
+ if (j > 255) {
+ j = 255;
+ }
+ for (; j < 256; j++) {
+ if (mAppliedCurve[j] != -1) {
+ break;
+ }
+ }
+ if (j > 255) {
+ j = 255;
+ }
+ // interpolate linearly between i and j - 1
+ float start = prev;
+ float end = mAppliedCurve[j];
+ float delta = (end - start) / (j - i + 1);
+ for (int k = i; k < j; k++) {
+ start = start + delta;
+ mAppliedCurve[k] = start;
+ }
+ i = j;
+ }
+ prev = mAppliedCurve[i];
+ }
+ for (int i = 0; i < 256; i++) {
+ mAppliedCurve[i] = mAppliedCurve[i] * 255;
+ }
+ float[] appliedCurve = new float[256];
+ for (int i = 0; i < 256; i++) {
+ appliedCurve[i] = mAppliedCurve[i];
+ }
+ // update image
+ if (getImagePreset() != null) {
+ String filterName = getFilterName();
+ ImageFilterCurves filter = (ImageFilterCurves) getImagePreset()
+ .getFilter(filterName);
+ if (filter == null) {
+ filter = new ImageFilterCurves();
+ filter.setName(filterName);
+ ImagePreset copy = new ImagePreset(getImagePreset());
+ copy.add(filter);
+ setImagePreset(copy, false);
+ }
+
+ if (filter != null) {
+ filter.setSpline(new Spline(mSpline));
+ filter.setCurve(appliedCurve);
+ filter.setUseRed(mUseRed);
+ filter.setUseGreen(mUseGreen);
+ filter.setUseBlue(mUseBlue);
+ }
+ mImageLoader.resetImageForPreset(getImagePreset(), this);
+ invalidate();
+ }
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/PieSlider.java b/src/com/android/gallery3d/filtershow/ui/PieSlider.java
new file mode 100644
index 000000000..0f3702d37
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/PieSlider.java
@@ -0,0 +1,181 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Path.FillType;
+import android.view.MotionEvent;
+
+public class PieSlider {
+ private float mCenterX;
+ private float mCenterY;
+ private float mCurrentX;
+ private float mCurrentY;
+ private int mStartAngle = 210;
+ private int mEndAngle = 120;
+ private int mValue = 100;
+
+ private Paint mPaint = new Paint();
+ private Path mBasePath = new Path();
+ private Path mCirclePath = new Path();
+ private RectF mOvalRect = new RectF();
+ private Rect mTextBounds = new Rect();
+
+ private PieSliderListener mListener = null;
+
+ private MODES mMode = MODES.NONE;
+ private static int mMenuRadius = 140;
+ private static int mTextSize = 32;
+ private static int mFanDistance = 2 * mMenuRadius;
+ private static int mTextDistance = (int) (2.2f * mMenuRadius);
+ private static int mLineDistance = (int) (2.5f * mMenuRadius);
+
+ private enum MODES {
+ NONE, DOWN, UP, MOVE
+ }
+
+ public void onDraw(Canvas canvas) {
+ if (mMode == MODES.NONE || mMode == MODES.UP) {
+ return;
+ }
+ drawFan(canvas);
+ }
+
+ public void setActionDown(float x, float y) {
+ mCenterX = x;
+ mCenterY = y;
+ mCurrentX = x;
+ mCurrentY = y;
+ mMode = MODES.DOWN;
+ }
+
+ public void setActionMove(float x, float y) {
+ mCurrentX = x;
+ mCurrentY = y;
+ mMode = MODES.MOVE;
+ computeValue();
+ if (mListener != null) {
+ mListener.onNewValue(mValue);
+ }
+ }
+
+ public void setActionUp() {
+ mMode = MODES.UP;
+ }
+
+ public void setNoAction() {
+ mMode = MODES.NONE;
+ }
+
+ private void drawFan(Canvas canvas) {
+ mPaint.setARGB(200, 200, 200, 200);
+ mPaint.setStrokeWidth(4);
+ mBasePath.reset();
+ mCirclePath.reset();
+
+ int mf = 2;
+
+ mOvalRect.left = mCenterX - mFanDistance;
+ mOvalRect.top = mCenterY - mFanDistance;
+ mOvalRect.right = mOvalRect.left + mf * mFanDistance;
+ mOvalRect.bottom = mOvalRect.top + mf * mFanDistance;
+
+ canvas.save();
+ mBasePath.moveTo(mCenterX, mCenterY);
+ mBasePath.arcTo(mOvalRect, mStartAngle, mEndAngle);
+
+ mCirclePath.moveTo(mCenterX, mCenterY);
+ mOvalRect.left = mCenterX - mMenuRadius;
+ mOvalRect.top = mCenterY - mMenuRadius;
+ mOvalRect.right = mOvalRect.left + mf * mMenuRadius;
+ mOvalRect.bottom = mOvalRect.top + mf * mMenuRadius;
+ mCirclePath.arcTo(mOvalRect, mStartAngle, mEndAngle);
+
+ mBasePath.setFillType(FillType.EVEN_ODD);
+ mBasePath.addPath(mCirclePath);
+ mBasePath.close();
+
+ canvas.drawPath(mBasePath, mPaint);
+ canvas.restore();
+
+ canvas.save();
+ canvas.rotate(-60, mCenterX, mCenterY);
+ canvas.drawLine(mCenterX, mCenterY - mMenuRadius, mCenterX, mCenterY - mLineDistance,
+ mPaint);
+ canvas.restore();
+ canvas.save();
+ canvas.rotate(mEndAngle - 60, mCenterX, mCenterY);
+ canvas.drawLine(mCenterX, mCenterY - mMenuRadius, mCenterX, mCenterY - mLineDistance,
+ mPaint);
+ canvas.restore();
+
+ canvas.save();
+ canvas.rotate(mEndAngle / 2 - 60, mCenterX, mCenterY);
+ String txt = "" + mValue;
+ mPaint.setTextSize(mTextSize);
+ mPaint.getTextBounds(txt, 0, txt.length(), mTextBounds);
+ mPaint.setARGB(255, 20, 20, 20);
+ canvas.drawText(txt, mCenterX - mTextBounds.width() / 2 - 2, mCenterY - mTextDistance - 2,
+ mPaint);
+ mPaint.setARGB(200, 200, 200, 200);
+ canvas.drawText(txt, mCenterX - mTextBounds.width() / 2, mCenterY - mTextDistance, mPaint);
+ canvas.restore();
+ }
+
+ private void computeValue() {
+ float dX = mCurrentX - mCenterX;
+ float dY = mCurrentY - mCenterY;
+ float distance = (float) Math.sqrt(dX * dX + dY * dY);
+
+ if (mCenterY > mCurrentY && distance > mMenuRadius) {
+ float angle = (float) (Math.atan2(dX, dY) * 180 / Math.PI);
+ if (angle < 0) {
+ // from -90 to -180
+ angle = -angle;
+ angle = Math.max(90, angle);
+ angle = Math.min(180, angle);
+ angle -= 90;
+ } else {
+ angle = Math.max(90, angle);
+ angle = Math.min(180, angle);
+ angle -= 90;
+ angle = 90 + (90 - angle);
+ }
+ angle /= 180.0f;
+ angle = Math.max(0, angle);
+ angle = Math.min(1, angle);
+ mEndAngle = (int) (120 * angle);
+ mValue = (int) (100 * (mEndAngle / 120.0f));
+ }
+ }
+
+ public void setListener(PieSliderListener listener) {
+ mListener = listener;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ setNoAction();
+ switch (event.getActionMasked()) {
+ case (MotionEvent.ACTION_DOWN): {
+ setActionDown(event.getX(), event.getY());
+ break;
+ }
+ case (MotionEvent.ACTION_UP): {
+ setActionUp();
+ break;
+ }
+ case (MotionEvent.ACTION_CANCEL): {
+ setActionUp();
+ break;
+ }
+ case (MotionEvent.ACTION_MOVE): {
+ setActionMove(event.getX(), event.getY());
+ break;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/PieSliderListener.java b/src/com/android/gallery3d/filtershow/ui/PieSliderListener.java
new file mode 100644
index 000000000..6020ad8a9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/PieSliderListener.java
@@ -0,0 +1,6 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+public interface PieSliderListener {
+ public void onNewValue(int value);
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/Spline.java b/src/com/android/gallery3d/filtershow/ui/Spline.java
new file mode 100644
index 000000000..a272d288f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/Spline.java
@@ -0,0 +1,126 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+import java.util.Collections;
+import java.util.Vector;
+
+public class Spline {
+ public Spline() {
+ mPoints = new Vector<ControlPoint>();
+ }
+
+ public Spline(Spline spline) {
+ mPoints = new Vector<ControlPoint>();
+ for (int i = 0; i < spline.mPoints.size(); i++) {
+ ControlPoint p = spline.mPoints.elementAt(i);
+ mPoints.add(p);
+ }
+ Collections.sort(mPoints);
+ delta_t = 1.0f / mPoints.size();
+ }
+
+ public ControlPoint interpolate(float t, ControlPoint p1,
+ ControlPoint p2, ControlPoint p3, ControlPoint p4) {
+
+ float t3 = t * t * t;
+ float t2 = t * t;
+ float b1 = 0.5f * (-t3 + 2 * t2 - t);
+ float b2 = 0.5f * (3 * t3 - 5 * t2 + 2);
+ float b3 = 0.5f * (-3 * t3 + 4 * t2 + t);
+ float b4 = 0.5f * (t3 - t2);
+
+ ControlPoint b1p1 = p1.multiply(b1);
+ ControlPoint b2p2 = p2.multiply(b2);
+ ControlPoint b3p3 = p3.multiply(b3);
+ ControlPoint b4p4 = p4.multiply(b4);
+
+ return b1p1.add(b2p2.add(b3p3.add(b4p4)));
+ }
+
+ public void addPoint(float x, float y) {
+ addPoint(new ControlPoint(x, y));
+ }
+
+ public void addPoint(ControlPoint v) {
+ mPoints.add(v);
+ Collections.sort(mPoints);
+ delta_t = 1.0f / mPoints.size();
+ }
+
+ public ControlPoint getPoint(float t) {
+ int p = (int) (t / delta_t);
+ int p0 = p - 1;
+ int max = mPoints.size() - 1;
+
+ if (p0 < 0) {
+ p0 = 0;
+ } else if (p0 >= max) {
+ p0 = max;
+ }
+ int p1 = p;
+ if (p1 < 0) {
+ p1 = 0;
+ } else if (p1 >= max) {
+ p1 = max;
+ }
+ int p2 = p + 1;
+ if (p2 < 0) {
+ p2 = 0;
+ } else if (p2 >= max) {
+ p2 = max;
+ }
+ int p3 = p + 2;
+ if (p3 < 0) {
+ p3 = 0;
+ } else if (p3 >= max) {
+ p3 = max;
+ }
+ float lt = (t - delta_t * (float) p) / delta_t;
+ return interpolate(lt, mPoints.elementAt(p0),
+ mPoints.elementAt(p1), mPoints.elementAt(p2),
+ mPoints.elementAt(p3));
+
+ }
+
+ public int getNbPoints() {
+ return mPoints.size();
+ }
+
+ public ControlPoint getPoint(int n) {
+ return mPoints.elementAt(n);
+ }
+
+ public boolean isPointContained(float x, int n) {
+ for (int i = 0; i < n; i++) {
+ ControlPoint point = mPoints.elementAt(i);
+ if (point.x > x) {
+ return false;
+ }
+ }
+ for (int i = n + 1; i < mPoints.size(); i++) {
+ ControlPoint point = mPoints.elementAt(i);
+ if (point.x < x) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void deletePoint(int n) {
+ mPoints.remove(n);
+ Collections.sort(mPoints);
+ delta_t = 1.0f / (mPoints.size() - 1f);
+ }
+
+ private Vector<ControlPoint> mPoints;
+ private float delta_t;
+
+ public Spline copy() {
+ Spline spline = new Spline();
+ for (int i = 0; i < mPoints.size(); i++) {
+ ControlPoint point = mPoints.elementAt(i);
+ spline.addPoint(point.copy());
+ }
+ return spline;
+ }
+}