diff options
| author | nicolasroard <nicolasroard@google.com> | 2012-09-25 14:27:56 -0700 |
|---|---|---|
| committer | nicolasroard <nicolasroard@google.com> | 2012-09-26 16:13:45 -0700 |
| commit | 0d7cdf8e763fb65c32bfad65245b3753deb75737 (patch) | |
| tree | b5ac324506895492cca3ee573ba2446473b81a7d | |
| parent | 833f775afc9515567dc5aaf0cafe20e006e77545 (diff) | |
| download | android_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
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 Binary files differnew file mode 100644 index 000000000..e7646c33a --- /dev/null +++ b/res/drawable/filtershow_background.png diff --git a/res/drawable/filtershow_border_black.9.png b/res/drawable/filtershow_border_black.9.png Binary files differnew file mode 100755 index 000000000..24bb5e11d --- /dev/null +++ b/res/drawable/filtershow_border_black.9.png diff --git a/res/drawable/filtershow_border_film.png b/res/drawable/filtershow_border_film.png Binary files differnew file mode 100755 index 000000000..9fbd637c1 --- /dev/null +++ b/res/drawable/filtershow_border_film.png diff --git a/res/drawable/filtershow_border_film2.9.png b/res/drawable/filtershow_border_film2.9.png Binary files differnew file mode 100644 index 000000000..bef5e1061 --- /dev/null +++ b/res/drawable/filtershow_border_film2.9.png diff --git a/res/drawable/filtershow_border_film3.9.png b/res/drawable/filtershow_border_film3.9.png Binary files differnew file mode 100644 index 000000000..8ce70cb94 --- /dev/null +++ b/res/drawable/filtershow_border_film3.9.png diff --git a/res/drawable/filtershow_border_rounded_black.9.png b/res/drawable/filtershow_border_rounded_black.9.png Binary files differnew file mode 100755 index 000000000..590a34393 --- /dev/null +++ b/res/drawable/filtershow_border_rounded_black.9.png diff --git a/res/drawable/filtershow_border_rounded_white.9.png b/res/drawable/filtershow_border_rounded_white.9.png Binary files differnew file mode 100755 index 000000000..4ddc97a19 --- /dev/null +++ b/res/drawable/filtershow_border_rounded_white.9.png diff --git a/res/drawable/filtershow_border_scratch3.9.png b/res/drawable/filtershow_border_scratch3.9.png Binary files differnew file mode 100644 index 000000000..7552785cd --- /dev/null +++ b/res/drawable/filtershow_border_scratch3.9.png diff --git a/res/drawable/filtershow_border_white.9.png b/res/drawable/filtershow_border_white.9.png Binary files differnew file mode 100755 index 000000000..d8c88267e --- /dev/null +++ b/res/drawable/filtershow_border_white.9.png 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 Binary files differnew file mode 100644 index 000000000..69195a902 --- /dev/null +++ b/res/drawable/filtershow_button_border.png diff --git a/res/drawable/filtershow_button_colors.png b/res/drawable/filtershow_button_colors.png Binary files differnew file mode 100644 index 000000000..566773dbe --- /dev/null +++ b/res/drawable/filtershow_button_colors.png diff --git a/res/drawable/filtershow_button_colors_contrast.png b/res/drawable/filtershow_button_colors_contrast.png Binary files differnew file mode 100644 index 000000000..ccb2dc630 --- /dev/null +++ b/res/drawable/filtershow_button_colors_contrast.png diff --git a/res/drawable/filtershow_button_colors_curve.png b/res/drawable/filtershow_button_colors_curve.png Binary files differnew file mode 100644 index 000000000..7046bd8dd --- /dev/null +++ b/res/drawable/filtershow_button_colors_curve.png diff --git a/res/drawable/filtershow_button_colors_sharpen.png b/res/drawable/filtershow_button_colors_sharpen.png Binary files differnew file mode 100644 index 000000000..2bd0fff6d --- /dev/null +++ b/res/drawable/filtershow_button_colors_sharpen.png diff --git a/res/drawable/filtershow_button_colors_vignette.png b/res/drawable/filtershow_button_colors_vignette.png Binary files differnew file mode 100644 index 000000000..ac3d53fa9 --- /dev/null +++ b/res/drawable/filtershow_button_colors_vignette.png diff --git a/res/drawable/filtershow_button_current.png b/res/drawable/filtershow_button_current.png Binary files differnew file mode 100644 index 000000000..8c4b379b5 --- /dev/null +++ b/res/drawable/filtershow_button_current.png diff --git a/res/drawable/filtershow_button_fx.png b/res/drawable/filtershow_button_fx.png Binary files differnew file mode 100644 index 000000000..c887fe4d1 --- /dev/null +++ b/res/drawable/filtershow_button_fx.png diff --git a/res/drawable/filtershow_button_geometry.png b/res/drawable/filtershow_button_geometry.png Binary files differnew file mode 100644 index 000000000..4b8f3b881 --- /dev/null +++ b/res/drawable/filtershow_button_geometry.png diff --git a/res/drawable/filtershow_button_geometry_crop.png b/res/drawable/filtershow_button_geometry_crop.png Binary files differnew file mode 100644 index 000000000..eb7da1b21 --- /dev/null +++ b/res/drawable/filtershow_button_geometry_crop.png diff --git a/res/drawable/filtershow_button_geometry_flip.png b/res/drawable/filtershow_button_geometry_flip.png Binary files differnew file mode 100644 index 000000000..dd74813a7 --- /dev/null +++ b/res/drawable/filtershow_button_geometry_flip.png diff --git a/res/drawable/filtershow_button_geometry_rotate.png b/res/drawable/filtershow_button_geometry_rotate.png Binary files differnew file mode 100644 index 000000000..fa50ce26f --- /dev/null +++ b/res/drawable/filtershow_button_geometry_rotate.png diff --git a/res/drawable/filtershow_button_geometry_straighten.png b/res/drawable/filtershow_button_geometry_straighten.png Binary files differnew file mode 100644 index 000000000..309eb5ae2 --- /dev/null +++ b/res/drawable/filtershow_button_geometry_straighten.png diff --git a/res/drawable/filtershow_button_operations.png b/res/drawable/filtershow_button_operations.png Binary files differnew file mode 100644 index 000000000..79e9a446b --- /dev/null +++ b/res/drawable/filtershow_button_operations.png diff --git a/res/drawable/filtershow_button_origin.png b/res/drawable/filtershow_button_origin.png Binary files differnew file mode 100644 index 000000000..0cd0bc28b --- /dev/null +++ b/res/drawable/filtershow_button_origin.png diff --git a/res/drawable/filtershow_button_redo.png b/res/drawable/filtershow_button_redo.png Binary files differnew file mode 100644 index 000000000..9daa01c34 --- /dev/null +++ b/res/drawable/filtershow_button_redo.png diff --git a/res/drawable/filtershow_button_selected_background.9.png b/res/drawable/filtershow_button_selected_background.9.png Binary files differnew file mode 100644 index 000000000..bb412454c --- /dev/null +++ b/res/drawable/filtershow_button_selected_background.9.png diff --git a/res/drawable/filtershow_button_settings.png b/res/drawable/filtershow_button_settings.png Binary files differnew file mode 100644 index 000000000..df3925aca --- /dev/null +++ b/res/drawable/filtershow_button_settings.png diff --git a/res/drawable/filtershow_button_show_original.png b/res/drawable/filtershow_button_show_original.png Binary files differnew file mode 100644 index 000000000..925954beb --- /dev/null +++ b/res/drawable/filtershow_button_show_original.png diff --git a/res/drawable/filtershow_button_undo.png b/res/drawable/filtershow_button_undo.png Binary files differnew file mode 100644 index 000000000..0a7e0d1b8 --- /dev/null +++ b/res/drawable/filtershow_button_undo.png 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; + } +} |
