diff --git a/Android.mk b/Android.mk
index 0045ca186f7cde0428fd2d2bbaf36cec03a9349f..ecb6296867d67774b0674fda1cbc95212ce5067f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -49,24 +49,34 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13
-LOCAL_STATIC_JAVA_LIBRARIES += com.android.gallery3d.common2
+LOCAL_STATIC_JAVA_LIBRARIES += com.android.gallery3d.common2
+LOCAL_STATIC_JAVA_LIBRARIES += xmp_toolkit
+LOCAL_STATIC_JAVA_LIBRARIES += mp4parser
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, src_pd)
LOCAL_SRC_FILES += $(call all-java-files-under, ../Camera/src)
-LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res packages/apps/Camera/res
-LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.camera
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res
+LOCAL_RESOURCE_DIR += packages/apps/Camera/res
+
+LOCAL_AAPT_FLAGS := --auto-add-overlay \
+ --extra-packages com.android.camera
LOCAL_PACKAGE_NAME := Gallery2
LOCAL_OVERRIDES_PACKAGES := Gallery Gallery3D GalleryNew3D
-#LOCAL_SDK_VERSION := current
-
-LOCAL_JNI_SHARED_LIBRARIES := libjni_mosaic libjni_eglfence
+LOCAL_SDK_VERSION := current
-LOCAL_REQUIRED_MODULES := libjni_mosaic libjni_eglfence
+# If this is an unbundled build (to install seprately) then include
+# the libraries in the APK, otherwise just put them in /system/lib and
+# leave them out of the APK
+ifneq (,$(TARGET_BUILD_APPS))
+ LOCAL_JNI_SHARED_LIBRARIES := libjni_mosaic libjni_eglfence libjni_filtershow_filters
+else
+ LOCAL_REQUIRED_MODULES := libjni_mosaic libjni_eglfence libjni_filtershow_filters
+endif
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5f30113ce937d1ae29fbaa15da42ec11b7fb1d65..b7e785ca0cab7b5c94df446aef6205db1a1c4eb6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,13 +1,16 @@
-
-
+
+
+
@@ -38,7 +41,8 @@
android:name="com.android.gallery3d.app.GalleryAppImpl"
android:theme="@style/Theme.Gallery"
android:logo="@mipmap/ic_launcher_gallery"
- android:hardwareAccelerated="true">
+ android:hardwareAccelerated="true"
+ android:largeHeap="true">
+
@@ -208,39 +213,63 @@
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:theme="@style/Theme.Gallery.Dialog"/>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:targetActivity="com.android.camera.CameraActivity" >
@@ -282,14 +334,20 @@
-
+
+
+
+
+
+
+
+ android:name="com.android.camera.VideoCamera"
+ android:targetActivity="com.android.camera.CameraActivity" >
@@ -298,15 +356,8 @@
-
-
-
+
+
@@ -323,6 +374,7 @@
+
@@ -342,5 +394,8 @@
+
+
diff --git a/gallerycommon/Android.mk b/gallerycommon/Android.mk
index a942de28977e93e343f5838dd00d0aefd139b422..1d341472c0d570c178a0a5b18dd97c01e95d56c4 100644
--- a/gallerycommon/Android.mk
+++ b/gallerycommon/Android.mk
@@ -22,6 +22,6 @@ include $(CLEAR_VARS)
LOCAL_MODULE := com.android.gallery3d.common2
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := 8
+LOCAL_SDK_VERSION := 16
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..837777e518b2163e64ce702a3389b38b91235686
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
@@ -0,0 +1,212 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.common;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.hardware.Camera;
+import android.os.Build;
+import android.provider.MediaStore.MediaColumns;
+import android.view.View;
+
+import java.lang.reflect.Field;
+
+public class ApiHelper {
+ public static interface VERSION_CODES {
+ // These value are copied from Build.VERSION_CODES
+ public static final int GINGERBREAD_MR1 = 10;
+ public static final int HONEYCOMB = 11;
+ public static final int HONEYCOMB_MR1 = 12;
+ public static final int HONEYCOMB_MR2 = 13;
+ public static final int ICE_CREAM_SANDWICH = 14;
+ public static final int ICE_CREAM_SANDWICH_MR1 = 15;
+ public static final int JELLY_BEAN = 16;
+ public static final int JELLY_BEAN_MR1 = 17;
+ }
+
+ public static final boolean USE_888_PIXEL_FORMAT =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static final boolean ENABLE_PHOTO_EDITOR =
+ Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
+
+ public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE =
+ hasField(View.class, "SYSTEM_UI_FLAG_LAYOUT_STABLE");
+
+ public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION =
+ hasField(View.class, "SYSTEM_UI_FLAG_HIDE_NAVIGATION");
+
+ public static final boolean HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT =
+ hasField(MediaColumns.class, "WIDTH");
+
+ public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_FACTORY =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_SET_BEAM_PUSH_URIS =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static final boolean HAS_SET_DEFALT_BUFFER_SIZE = hasMethod(
+ "android.graphics.SurfaceTexture", "setDefaultBufferSize",
+ int.class, int.class);
+
+ public static final boolean HAS_RELEASE_SURFACE_TEXTURE = hasMethod(
+ "android.graphics.SurfaceTexture", "release");
+
+ public static final boolean HAS_SURFACE_TEXTURE =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_MTP =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
+
+ public static final boolean HAS_AUTO_FOCUS_MOVE_CALLBACK =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static final boolean HAS_REMOTE_VIEWS_SERVICE =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_INTENT_EXTRA_LOCAL_ONLY =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_SET_SYSTEM_UI_VISIBILITY =
+ hasMethod(View.class, "setSystemUiVisibility", int.class);
+
+ public static final boolean HAS_FACE_DETECTION;
+ static {
+ boolean hasFaceDetection = false;
+ try {
+ Class> listenerClass = Class.forName(
+ "android.hardware.Camera$FaceDetectionListener");
+ hasFaceDetection =
+ hasMethod(Camera.class, "setFaceDetectionListener", listenerClass) &&
+ hasMethod(Camera.class, "startFaceDetection") &&
+ hasMethod(Camera.class, "stopFaceDetection") &&
+ hasMethod(Camera.Parameters.class, "getMaxNumDetectedFaces");
+ } catch (Throwable t) {
+ }
+ HAS_FACE_DETECTION = hasFaceDetection;
+ }
+
+ public static final boolean HAS_GET_CAMERA_DISABLED =
+ hasMethod(DevicePolicyManager.class, "getCameraDisabled", ComponentName.class);
+
+ public static final boolean HAS_MEDIA_ACTION_SOUND =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static final boolean HAS_OLD_PANORAMA =
+ Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
+
+ public static final boolean HAS_TIME_LAPSE_RECORDING =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_ZOOM_WHEN_RECORDING =
+ Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
+
+ public static final boolean HAS_CAMERA_FOCUS_AREA =
+ Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
+
+ public static final boolean HAS_CAMERA_METERING_AREA =
+ Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
+
+ public static final boolean HAS_FINE_RESOLUTION_QUALITY_LEVELS =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_MOTION_EVENT_TRANSFORM =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_EFFECTS_RECORDING = false;
+
+ // "Background" filter does not have "context" input port in jelly bean.
+ public static final boolean HAS_EFFECTS_RECORDING_CONTEXT_INPUT =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1;
+
+ public static final boolean HAS_GET_SUPPORTED_VIDEO_SIZE =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_SET_ICON_ATTRIBUTE =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_MEDIA_PROVIDER_FILES_TABLE =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_SURFACE_TEXTURE_RECORDING =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static final boolean HAS_ACTION_BAR =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ // Ex: View.setTranslationX.
+ public static final boolean HAS_VIEW_TRANSFORM_PROPERTIES =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_CAMERA_HDR =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1;
+
+ public static final boolean HAS_OPTIONS_IN_MUTABLE =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean CAN_START_PREVIEW_IN_JPEG_CALLBACK =
+ Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
+
+ public static final boolean HAS_VIEW_PROPERTY_ANIMATOR =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
+
+ public static final boolean HAS_POST_ON_ANIMATION =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static int getIntFieldIfExists(Class> klass, String fieldName,
+ Class> obj, int defaultVal) {
+ try {
+ Field f = klass.getDeclaredField(fieldName);
+ return f.getInt(obj);
+ } catch (Exception e) {
+ return defaultVal;
+ }
+ }
+
+ private static boolean hasField(Class> klass, String fieldName) {
+ try {
+ klass.getDeclaredField(fieldName);
+ return true;
+ } catch (NoSuchFieldException e) {
+ return false;
+ }
+ }
+
+ private static boolean hasMethod(String className, String methodName,
+ Class>... parameterTypes) {
+ try {
+ Class> klass = Class.forName(className);
+ klass.getDeclaredMethod(methodName, parameterTypes);
+ return true;
+ } catch (Throwable th) {
+ return false;
+ }
+ }
+
+ private static boolean hasMethod(
+ Class> klass, String methodName, Class> ... paramTypes) {
+ try {
+ klass.getDeclaredMethod(methodName, paramTypes);
+ return true;
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/common/AsyncTaskUtil.java b/gallerycommon/src/com/android/gallery3d/common/AsyncTaskUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..b70c4d365c6b1215149d98523488fab4262e8f92
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/AsyncTaskUtil.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.common;
+
+import android.os.AsyncTask;
+import android.os.Build;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class to execute an AsyncTask in parallel if SDK version is 11 or newer.
+ */
+public class AsyncTaskUtil {
+ private static Method sMethodExecuteOnExecutor;
+ private static Executor sExecutor;
+ static {
+ if (Build.VERSION.SDK_INT >= 11) {
+ try {
+ sExecutor = (Executor) AsyncTask.class.getField("THREAD_POOL_EXECUTOR")
+ .get(null);
+ sMethodExecuteOnExecutor = AsyncTask.class.getMethod(
+ "executeOnExecutor", Executor.class, Object[].class);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public static void executeInParallel(AsyncTask task, Param... params) {
+ if (Build.VERSION.SDK_INT < 11) {
+ task.execute(params);
+ } else {
+ try {
+ sMethodExecuteOnExecutor.invoke(task, sExecutor, params);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private AsyncTaskUtil() {
+ }
+}
+
diff --git a/gallerycommon/src/com/android/gallery3d/common/BlobCache.java b/gallerycommon/src/com/android/gallery3d/common/BlobCache.java
index 7788e61f5cad0a3906e425fae2845567a0dd7cb0..3c131e59146f668b8867706404c75b8d313a62de 100644
--- a/gallerycommon/src/com/android/gallery3d/common/BlobCache.java
+++ b/gallerycommon/src/com/android/gallery3d/common/BlobCache.java
@@ -74,6 +74,7 @@ import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
+import java.util.Arrays;
import java.util.zip.Adler32;
public class BlobCache implements Closeable {
@@ -379,6 +380,16 @@ public class BlobCache implements Closeable {
updateIndexHeader();
}
+ public void clearEntry(long key) throws IOException {
+ if (!lookupInternal(key, mActiveHashStart)) {
+ return; // Nothing to clear
+ }
+ byte[] header = mBlobHeader;
+ Arrays.fill(header, (byte) 0);
+ mActiveDataFile.seek(mFileOffset);
+ mActiveDataFile.write(header);
+ }
+
// Appends the data to the active file. It also updates the hash entry.
// The proper hash entry (suitable for insertion or replacement) must be
// pointed by mSlotOffset.
@@ -485,6 +496,9 @@ public class BlobCache implements Closeable {
return false;
}
long blobKey = readLong(header, BH_KEY);
+ if (blobKey == 0) {
+ return false; // This entry has been cleared.
+ }
if (blobKey != req.key) {
Log.w(TAG, "blob key does not match: " + blobKey);
return false;
diff --git a/gallerycommon/src/com/android/gallery3d/common/ExifTags.java b/gallerycommon/src/com/android/gallery3d/common/ExifTags.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b11fe4169a68c7b0fd6bfedaa8be888a85644b2
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/ExifTags.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.common;
+
+/**
+ * The class holds the EXIF tag names that are not available in
+ * {@link android.media.ExifInterface} prior to API level 11.
+ */
+public interface ExifTags {
+ static final String TAG_ISO = "ISOSpeedRatings";
+ static final String TAG_EXPOSURE_TIME = "ExposureTime";
+ static final String TAG_APERTURE = "FNumber";
+}
diff --git a/gallerycommon/src/com/android/gallery3d/common/FileCache.java b/gallerycommon/src/com/android/gallery3d/common/FileCache.java
index a69487f4c72f3991623d957604560dc9c2cf4e76..d7deda6fae65ff7bbf50f00036c22ee9b511b44e 100644
--- a/gallerycommon/src/com/android/gallery3d/common/FileCache.java
+++ b/gallerycommon/src/com/android/gallery3d/common/FileCache.java
@@ -92,6 +92,7 @@ public class FileCache implements Closeable {
mDbHelper = new DatabaseHelper(context, dbName);
}
+ @Override
public void close() {
mDbHelper.close();
}
diff --git a/gallerycommon/src/com/android/gallery3d/common/Fingerprint.java b/gallerycommon/src/com/android/gallery3d/common/Fingerprint.java
index 39fcf9e096e5bcccff1279142db9ca24b400d991..2847e57cec757e33e2b4387bd046f313eb7eeb87 100644
--- a/gallerycommon/src/com/android/gallery3d/common/Fingerprint.java
+++ b/gallerycommon/src/com/android/gallery3d/common/Fingerprint.java
@@ -16,10 +16,6 @@
package com.android.gallery3d.common;
-import android.content.ContentResolver;
-import android.net.Uri;
-
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
diff --git a/gallerycommon/src/com/android/gallery3d/common/HttpClientFactory.java b/gallerycommon/src/com/android/gallery3d/common/HttpClientFactory.java
index cb95e33290390992f413ea8eff4a840a86cc404d..18b7a88750b6c2cb5320a6d143d297f0f3ccca4b 100644
--- a/gallerycommon/src/com/android/gallery3d/common/HttpClientFactory.java
+++ b/gallerycommon/src/com/android/gallery3d/common/HttpClientFactory.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
-import android.util.Log;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
@@ -122,7 +121,7 @@ public final class HttpClientFactory {
Build.DEVICE,
Build.MODEL,
Build.ID,
- Build.VERSION.SDK,
+ Build.VERSION.SDK_INT,
Build.VERSION.RELEASE,
Build.VERSION.INCREMENTAL);
}
diff --git a/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java b/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3298e6726a6c321b8452d0a790f55a1ba0f9cbc
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2009 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.common;
+
+
+// Copied from android.util.LongSparseArray for unbundling
+
+/**
+ * SparseArray mapping longs to Objects. Unlike a normal array of Objects,
+ * there can be gaps in the indices. It is intended to be more efficient
+ * than using a HashMap to map Longs to Objects.
+ */
+public class LongSparseArray implements Cloneable {
+ private static final Object DELETED = new Object();
+ private boolean mGarbage = false;
+
+ private long[] mKeys;
+ private Object[] mValues;
+ private int mSize;
+
+ /**
+ * Creates a new LongSparseArray containing no mappings.
+ */
+ public LongSparseArray() {
+ this(10);
+ }
+
+ /**
+ * Creates a new LongSparseArray containing no mappings that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings.
+ */
+ public LongSparseArray(int initialCapacity) {
+ initialCapacity = idealLongArraySize(initialCapacity);
+
+ mKeys = new long[initialCapacity];
+ mValues = new Object[initialCapacity];
+ mSize = 0;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public LongSparseArray clone() {
+ LongSparseArray clone = null;
+ try {
+ clone = (LongSparseArray) super.clone();
+ clone.mKeys = mKeys.clone();
+ clone.mValues = mValues.clone();
+ } catch (CloneNotSupportedException cnse) {
+ /* ignore */
+ }
+ return clone;
+ }
+
+ /**
+ * Gets the Object mapped from the specified key, or null
+ * if no such mapping has been made.
+ */
+ public E get(long key) {
+ return get(key, null);
+ }
+
+ /**
+ * Gets the Object mapped from the specified key, or the specified Object
+ * if no such mapping has been made.
+ */
+ @SuppressWarnings("unchecked")
+ public E get(long key, E valueIfKeyNotFound) {
+ int i = binarySearch(mKeys, 0, mSize, key);
+
+ if (i < 0 || mValues[i] == DELETED) {
+ return valueIfKeyNotFound;
+ } else {
+ return (E) mValues[i];
+ }
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public void delete(long key) {
+ int i = binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ if (mValues[i] != DELETED) {
+ mValues[i] = DELETED;
+ mGarbage = true;
+ }
+ }
+ }
+
+ /**
+ * Alias for {@link #delete(long)}.
+ */
+ public void remove(long key) {
+ delete(key);
+ }
+
+ /**
+ * Removes the mapping at the specified index.
+ */
+ public void removeAt(int index) {
+ if (mValues[index] != DELETED) {
+ mValues[index] = DELETED;
+ mGarbage = true;
+ }
+ }
+
+ private void gc() {
+ // Log.e("SparseArray", "gc start with " + mSize);
+
+ int n = mSize;
+ int o = 0;
+ long[] keys = mKeys;
+ Object[] values = mValues;
+
+ for (int i = 0; i < n; i++) {
+ Object val = values[i];
+
+ if (val != DELETED) {
+ if (i != o) {
+ keys[o] = keys[i];
+ values[o] = val;
+ values[i] = null;
+ }
+
+ o++;
+ }
+ }
+
+ mGarbage = false;
+ mSize = o;
+
+ // Log.e("SparseArray", "gc end with " + mSize);
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(long key, E value) {
+ int i = binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ mValues[i] = value;
+ } else {
+ i = ~i;
+
+ if (i < mSize && mValues[i] == DELETED) {
+ mKeys[i] = key;
+ mValues[i] = value;
+ return;
+ }
+
+ if (mGarbage && mSize >= mKeys.length) {
+ gc();
+
+ // Search again because indices may have changed.
+ i = ~binarySearch(mKeys, 0, mSize, key);
+ }
+
+ if (mSize >= mKeys.length) {
+ int n = idealLongArraySize(mSize + 1);
+
+ long[] nkeys = new long[n];
+ Object[] nvalues = new Object[n];
+
+ // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+
+ if (mSize - i != 0) {
+ // Log.e("SparseArray", "move " + (mSize - i));
+ System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+ System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+ }
+
+ mKeys[i] = key;
+ mValues[i] = value;
+ mSize++;
+ }
+ }
+
+ /**
+ * Returns the number of key-value mappings that this LongSparseArray
+ * currently stores.
+ */
+ public int size() {
+ if (mGarbage) {
+ gc();
+ }
+
+ return mSize;
+ }
+
+ /**
+ * Given an index in the range 0...size()-1, returns
+ * the key from the indexth key-value mapping that this
+ * LongSparseArray stores.
+ */
+ public long keyAt(int index) {
+ if (mGarbage) {
+ gc();
+ }
+
+ return mKeys[index];
+ }
+
+ /**
+ * Given an index in the range 0...size()-1, returns
+ * the value from the indexth key-value mapping that this
+ * LongSparseArray stores.
+ */
+ @SuppressWarnings("unchecked")
+ public E valueAt(int index) {
+ if (mGarbage) {
+ gc();
+ }
+
+ return (E) mValues[index];
+ }
+
+ /**
+ * Given an index in the range 0...size()-1, sets a new
+ * value for the indexth key-value mapping that this
+ * LongSparseArray stores.
+ */
+ public void setValueAt(int index, E value) {
+ if (mGarbage) {
+ gc();
+ }
+
+ mValues[index] = value;
+ }
+
+ /**
+ * Returns the index for which {@link #keyAt} would return the
+ * specified key, or a negative number if the specified
+ * key is not mapped.
+ */
+ public int indexOfKey(long key) {
+ if (mGarbage) {
+ gc();
+ }
+
+ return binarySearch(mKeys, 0, mSize, key);
+ }
+
+ /**
+ * Returns an index for which {@link #valueAt} would return the
+ * specified key, or a negative number if no keys map to the
+ * specified value.
+ * Beware that this is a linear search, unlike lookups by key,
+ * and that multiple keys can map to the same value and this will
+ * find only one of them.
+ */
+ public int indexOfValue(E value) {
+ if (mGarbage) {
+ gc();
+ }
+
+ for (int i = 0; i < mSize; i++)
+ if (mValues[i] == value)
+ return i;
+
+ return -1;
+ }
+
+ /**
+ * Removes all key-value mappings from this LongSparseArray.
+ */
+ public void clear() {
+ int n = mSize;
+ Object[] values = mValues;
+
+ for (int i = 0; i < n; i++) {
+ values[i] = null;
+ }
+
+ mSize = 0;
+ mGarbage = false;
+ }
+
+ /**
+ * Puts a key/value pair into the array, optimizing for the case where
+ * the key is greater than all existing keys in the array.
+ */
+ public void append(long key, E value) {
+ if (mSize != 0 && key <= mKeys[mSize - 1]) {
+ put(key, value);
+ return;
+ }
+
+ if (mGarbage && mSize >= mKeys.length) {
+ gc();
+ }
+
+ int pos = mSize;
+ if (pos >= mKeys.length) {
+ int n = idealLongArraySize(pos + 1);
+
+ long[] nkeys = new long[n];
+ Object[] nvalues = new Object[n];
+
+ // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+
+ mKeys[pos] = key;
+ mValues[pos] = value;
+ mSize = pos + 1;
+ }
+
+ private static int binarySearch(long[] a, int start, int len, long key) {
+ int high = start + len, low = start - 1, guess;
+
+ while (high - low > 1) {
+ guess = (high + low) / 2;
+
+ if (a[guess] < key)
+ low = guess;
+ else
+ high = guess;
+ }
+
+ if (high == start + len)
+ return ~(start + len);
+ else if (a[high] == key)
+ return high;
+ else
+ return ~high;
+ }
+
+ private static int idealByteArraySize(int need) {
+ for (int i = 4; i < 32; i++)
+ if (need <= (1 << i) - 12)
+ return (1 << i) - 12;
+
+ return need;
+ }
+
+ public static int idealLongArraySize(int need) {
+ return idealByteArraySize(need * 8) / 8;
+ }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/common/OverScroller.java b/gallerycommon/src/com/android/gallery3d/common/OverScroller.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ab7953d2984a02b312e480add914c316ee0e9f5
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/OverScroller.java
@@ -0,0 +1,958 @@
+/*
+ * 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.common;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * This class encapsulates scrolling with the ability to overshoot the bounds
+ * of a scrolling operation. This class is a drop-in replacement for
+ * {@link android.widget.Scroller} in most cases.
+ */
+public class OverScroller {
+ private int mMode;
+
+ private final SplineOverScroller mScrollerX;
+ private final SplineOverScroller mScrollerY;
+
+ private Interpolator mInterpolator;
+
+ private final boolean mFlywheel;
+
+ private static final int DEFAULT_DURATION = 250;
+ private static final int SCROLL_MODE = 0;
+ private static final int FLING_MODE = 1;
+
+ /**
+ * Creates an OverScroller with a viscous fluid scroll interpolator and flywheel.
+ * @param context
+ */
+ public OverScroller(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Creates an OverScroller with flywheel enabled.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ */
+ public OverScroller(Context context, Interpolator interpolator) {
+ this(context, interpolator, true);
+ }
+
+ /**
+ * Creates an OverScroller.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
+ * @hide
+ */
+ public OverScroller(Context context, Interpolator interpolator, boolean flywheel) {
+ mInterpolator = interpolator;
+ mFlywheel = flywheel;
+ mScrollerX = new SplineOverScroller();
+ mScrollerY = new SplineOverScroller();
+
+ SplineOverScroller.initFromContext(context);
+ }
+
+ /**
+ * Creates an OverScroller with flywheel enabled.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+ * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+ * means no bounce. This behavior is no longer supported and this coefficient has no effect.
+ * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
+ * behavior is no longer supported and this coefficient has no effect.
+ * !deprecated Use {!link #OverScroller(Context, Interpolator, boolean)} instead.
+ */
+ public OverScroller(Context context, Interpolator interpolator,
+ float bounceCoefficientX, float bounceCoefficientY) {
+ this(context, interpolator, true);
+ }
+
+ /**
+ * Creates an OverScroller.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+ * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+ * means no bounce. This behavior is no longer supported and this coefficient has no effect.
+ * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
+ * behavior is no longer supported and this coefficient has no effect.
+ * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
+ * !deprecated Use {!link OverScroller(Context, Interpolator, boolean)} instead.
+ */
+ public OverScroller(Context context, Interpolator interpolator,
+ float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
+ this(context, interpolator, flywheel);
+ }
+
+ void setInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * The amount of friction applied to flings. The default value
+ * is {@link ViewConfiguration#getScrollFriction}.
+ *
+ * @param friction A scalar dimension-less value representing the coefficient of
+ * friction.
+ */
+ public final void setFriction(float friction) {
+ mScrollerX.setFriction(friction);
+ mScrollerY.setFriction(friction);
+ }
+
+ /**
+ *
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mScrollerX.mFinished && mScrollerY.mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value. Contrary to
+ * {@link #abortAnimation()}, forcing the animation to finished
+ * does NOT cause the scroller to move to the final x and y
+ * position.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mScrollerX.mFinished = mScrollerY.mFinished = finished;
+ }
+
+ /**
+ * Returns the current X offset in the scroll.
+ *
+ * @return The new X offset as an absolute distance from the origin.
+ */
+ public final int getCurrX() {
+ return mScrollerX.mCurrentPosition;
+ }
+
+ /**
+ * Returns the current Y offset in the scroll.
+ *
+ * @return The new Y offset as an absolute distance from the origin.
+ */
+ public final int getCurrY() {
+ return mScrollerY.mCurrentPosition;
+ }
+
+ /**
+ * Returns the absolute value of the current velocity.
+ *
+ * @return The original velocity less the deceleration, norm of the X and Y velocity vector.
+ */
+ public float getCurrVelocity() {
+ float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
+ squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
+ return FloatMath.sqrt(squaredNorm);
+ }
+
+ /**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ */
+ public final int getStartX() {
+ return mScrollerX.mStart;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ */
+ public final int getStartY() {
+ return mScrollerY.mStart;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final X offset as an absolute distance from the origin.
+ */
+ public final int getFinalX() {
+ return mScrollerX.mFinal;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final Y offset as an absolute distance from the origin.
+ */
+ public final int getFinalY() {
+ return mScrollerY.mFinal;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScrollers don't necessarily have a fixed duration.
+ * This function will lie to the best of its ability.
+ */
+ @Deprecated
+ public final int getDuration() {
+ return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinalX(int)
+ * @see #setFinalY(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScrollers don't necessarily have a fixed duration.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ @Deprecated
+ public void extendDuration(int extend) {
+ mScrollerX.extendDuration(extend);
+ mScrollerY.extendDuration(extend);
+ }
+
+ /**
+ * Sets the final position (X) for this scroller.
+ *
+ * @param newX The new X offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalY(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScroller's final position may change during an animation.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ @Deprecated
+ public void setFinalX(int newX) {
+ mScrollerX.setFinalPosition(newX);
+ }
+
+ /**
+ * Sets the final position (Y) for this scroller.
+ *
+ * @param newY The new Y offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalX(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScroller's final position may change during an animation.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ @Deprecated
+ public void setFinalY(int newY) {
+ mScrollerY.setFinalPosition(newY);
+ }
+
+ /**
+ * Call this when you want to know the new location. If it returns true, the
+ * animation is not yet finished.
+ */
+ public boolean computeScrollOffset() {
+ if (isFinished()) {
+ return false;
+ }
+
+ switch (mMode) {
+ case SCROLL_MODE:
+ long time = AnimationUtils.currentAnimationTimeMillis();
+ // Any scroller can be used for time, since they were started
+ // together in scroll mode. We use X here.
+ final long elapsedTime = time - mScrollerX.mStartTime;
+
+ final int duration = mScrollerX.mDuration;
+ if (elapsedTime < duration) {
+ float q = (float) (elapsedTime) / duration;
+
+ if (mInterpolator == null) {
+ q = Scroller.viscousFluid(q);
+ } else {
+ q = mInterpolator.getInterpolation(q);
+ }
+
+ mScrollerX.updateScroll(q);
+ mScrollerY.updateScroll(q);
+ } else {
+ abortAnimation();
+ }
+ break;
+
+ case FLING_MODE:
+ if (!mScrollerX.mFinished) {
+ if (!mScrollerX.update()) {
+ if (!mScrollerX.continueWhenFinished()) {
+ mScrollerX.finish();
+ }
+ }
+ }
+
+ if (!mScrollerY.mFinished) {
+ if (!mScrollerY.update()) {
+ if (!mScrollerY.continueWhenFinished()) {
+ mScrollerY.finish();
+ }
+ }
+ }
+
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ * The scroll will use the default value of 250 milliseconds for the
+ * duration.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy) {
+ startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ * @param duration Duration of the scroll in milliseconds.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+ mMode = SCROLL_MODE;
+ mScrollerX.startScroll(startX, dx, duration);
+ mScrollerY.startScroll(startY, dy, duration);
+ }
+
+ /**
+ * Call this when you want to 'spring back' into a valid coordinate range.
+ *
+ * @param startX Starting X coordinate
+ * @param startY Starting Y coordinate
+ * @param minX Minimum valid X value
+ * @param maxX Maximum valid X value
+ * @param minY Minimum valid Y value
+ * @param maxY Minimum valid Y value
+ * @return true if a springback was initiated, false if startX and startY were
+ * already within the valid range.
+ */
+ public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) {
+ mMode = FLING_MODE;
+
+ // Make sure both methods are called.
+ final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
+ final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
+ return spingbackX || spingbackY;
+ }
+
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY) {
+ fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
+ }
+
+ /**
+ * Start scrolling based on a fling gesture. The distance traveled will
+ * depend on the initial velocity of the fling.
+ *
+ * @param startX Starting point of the scroll (X)
+ * @param startY Starting point of the scroll (Y)
+ * @param velocityX Initial velocity of the fling (X) measured in pixels per
+ * second.
+ * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+ * second
+ * @param minX Minimum X value. The scroller will not scroll past this point
+ * unless overX > 0. If overfling is allowed, it will use minX as
+ * a springback boundary.
+ * @param maxX Maximum X value. The scroller will not scroll past this point
+ * unless overX > 0. If overfling is allowed, it will use maxX as
+ * a springback boundary.
+ * @param minY Minimum Y value. The scroller will not scroll past this point
+ * unless overY > 0. If overfling is allowed, it will use minY as
+ * a springback boundary.
+ * @param maxY Maximum Y value. The scroller will not scroll past this point
+ * unless overY > 0. If overfling is allowed, it will use maxY as
+ * a springback boundary.
+ * @param overX Overfling range. If > 0, horizontal overfling in either
+ * direction will be possible.
+ * @param overY Overfling range. If > 0, vertical overfling in either
+ * direction will be possible.
+ */
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY, int overX, int overY) {
+ // Continue a scroll or fling in progress
+ if (mFlywheel && !isFinished()) {
+ float oldVelocityX = mScrollerX.mCurrVelocity;
+ float oldVelocityY = mScrollerY.mCurrVelocity;
+ if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+ Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+ velocityX += oldVelocityX;
+ velocityY += oldVelocityY;
+ }
+ }
+
+ mMode = FLING_MODE;
+ mScrollerX.fling(startX, velocityX, minX, maxX, overX);
+ mScrollerY.fling(startY, velocityY, minY, maxY, overY);
+ }
+
+ /**
+ * Notify the scroller that we've reached a horizontal boundary.
+ * Normally the information to handle this will already be known
+ * when the animation is started, such as in a call to one of the
+ * fling functions. However there are cases where this cannot be known
+ * in advance. This function will transition the current motion and
+ * animate from startX to finalX as appropriate.
+ *
+ * @param startX Starting/current X position
+ * @param finalX Desired final X position
+ * @param overX Magnitude of overscroll allowed. This should be the maximum
+ * desired distance from finalX. Absolute value - must be positive.
+ */
+ public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
+ mScrollerX.notifyEdgeReached(startX, finalX, overX);
+ }
+
+ /**
+ * Notify the scroller that we've reached a vertical boundary.
+ * Normally the information to handle this will already be known
+ * when the animation is started, such as in a call to one of the
+ * fling functions. However there are cases where this cannot be known
+ * in advance. This function will animate a parabolic motion from
+ * startY to finalY.
+ *
+ * @param startY Starting/current Y position
+ * @param finalY Desired final Y position
+ * @param overY Magnitude of overscroll allowed. This should be the maximum
+ * desired distance from finalY. Absolute value - must be positive.
+ */
+ public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
+ mScrollerY.notifyEdgeReached(startY, finalY, overY);
+ }
+
+ /**
+ * Returns whether the current Scroller is currently returning to a valid position.
+ * Valid bounds were provided by the
+ * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
+ *
+ * One should check this value before calling
+ * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
+ * to restore a valid position will then be stopped. The caller has to take into account
+ * the fact that the started scroll will start from an overscrolled position.
+ *
+ * @return true when the current position is overscrolled and in the process of
+ * interpolating back to a valid value.
+ */
+ public boolean isOverScrolled() {
+ return ((!mScrollerX.mFinished &&
+ mScrollerX.mState != SplineOverScroller.SPLINE) ||
+ (!mScrollerY.mFinished &&
+ mScrollerY.mState != SplineOverScroller.SPLINE));
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating causes the scroller to move to the final x and y
+ * positions.
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mScrollerX.finish();
+ mScrollerY.finish();
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ *
+ * @hide
+ */
+ public int timePassed() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
+ return (int) (time - startTime);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isScrollingInDirection(float xvel, float yvel) {
+ final int dx = mScrollerX.mFinal - mScrollerX.mStart;
+ final int dy = mScrollerY.mFinal - mScrollerY.mStart;
+ return !isFinished() && Math.signum(xvel) == Math.signum(dx) &&
+ Math.signum(yvel) == Math.signum(dy);
+ }
+
+ static class SplineOverScroller {
+ // Initial position
+ private int mStart;
+
+ // Current position
+ private int mCurrentPosition;
+
+ // Final position
+ private int mFinal;
+
+ // Initial velocity
+ private int mVelocity;
+
+ // Current velocity
+ private float mCurrVelocity;
+
+ // Constant current deceleration
+ private float mDeceleration;
+
+ // Animation starting time, in system milliseconds
+ private long mStartTime;
+
+ // Animation duration, in milliseconds
+ private int mDuration;
+
+ // Duration to complete spline component of animation
+ private int mSplineDuration;
+
+ // Distance to travel along spline animation
+ private int mSplineDistance;
+
+ // Whether the animation is currently in progress
+ private boolean mFinished;
+
+ // The allowed overshot distance before boundary is reached.
+ private int mOver;
+
+ // Fling friction
+ private float mFlingFriction = ViewConfiguration.getScrollFriction();
+
+ // Current state of the animation.
+ private int mState = SPLINE;
+
+ // Constant gravity value, used in the deceleration phase.
+ private static final float GRAVITY = 2000.0f;
+
+ // A device specific coefficient adjusted to physical values.
+ private static float PHYSICAL_COEF;
+
+ private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
+ private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
+ private static final float START_TENSION = 0.5f;
+ private static final float END_TENSION = 1.0f;
+ private static final float P1 = START_TENSION * INFLEXION;
+ private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
+
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+ private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
+
+ private static final int SPLINE = 0;
+ private static final int CUBIC = 1;
+ private static final int BALLISTIC = 2;
+
+ static {
+ float x_min = 0.0f;
+ float y_min = 0.0f;
+ for (int i = 0; i < NB_SAMPLES; i++) {
+ final float alpha = (float) i / NB_SAMPLES;
+
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
+ if (Math.abs(tx - alpha) < 1E-5) break;
+ if (tx > alpha) x_max = x;
+ else x_min = x;
+ }
+ SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
+
+ float y_max = 1.0f;
+ float y, dy;
+ while (true) {
+ y = y_min + (y_max - y_min) / 2.0f;
+ coef = 3.0f * y * (1.0f - y);
+ dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
+ if (Math.abs(dy - alpha) < 1E-5) break;
+ if (dy > alpha) y_max = y;
+ else y_min = y;
+ }
+ SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
+ }
+ SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+ }
+
+ static void initFromContext(Context context) {
+ final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+ PHYSICAL_COEF = SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * ppi
+ * 0.84f; // look and feel tuning
+ }
+
+ void setFriction(float friction) {
+ mFlingFriction = friction;
+ }
+
+ SplineOverScroller() {
+ mFinished = true;
+ }
+
+ void updateScroll(float q) {
+ mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
+ }
+
+ /*
+ * Get a signed deceleration that will reduce the velocity.
+ */
+ static private float getDeceleration(int velocity) {
+ return velocity > 0 ? -GRAVITY : GRAVITY;
+ }
+
+ /*
+ * Modifies mDuration to the duration it takes to get from start to newFinal using the
+ * spline interpolation. The previous duration was needed to get to oldFinal.
+ */
+ private void adjustDuration(int start, int oldFinal, int newFinal) {
+ final int oldDistance = oldFinal - start;
+ final int newDistance = newFinal - start;
+ final float x = Math.abs((float) newDistance / oldDistance);
+ final int index = (int) (NB_SAMPLES * x);
+ if (index < NB_SAMPLES) {
+ final float x_inf = (float) index / NB_SAMPLES;
+ final float x_sup = (float) (index + 1) / NB_SAMPLES;
+ final float t_inf = SPLINE_TIME[index];
+ final float t_sup = SPLINE_TIME[index + 1];
+ final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
+ mDuration *= timeCoef;
+ }
+ }
+
+ void startScroll(int start, int distance, int duration) {
+ mFinished = false;
+
+ mStart = start;
+ mFinal = start + distance;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = duration;
+
+ // Unused
+ mDeceleration = 0.0f;
+ mVelocity = 0;
+ }
+
+ void finish() {
+ mCurrentPosition = mFinal;
+ // Not reset since WebView relies on this value for fast fling.
+ // TODO: restore when WebView uses the fast fling implemented in this class.
+ // mCurrVelocity = 0.0f;
+ mFinished = true;
+ }
+
+ void setFinalPosition(int position) {
+ mFinal = position;
+ mFinished = false;
+ }
+
+ void extendDuration(int extend) {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final int elapsedTime = (int) (time - mStartTime);
+ mDuration = elapsedTime + extend;
+ mFinished = false;
+ }
+
+ boolean springback(int start, int min, int max) {
+ mFinished = true;
+
+ mStart = mFinal = start;
+ mVelocity = 0;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = 0;
+
+ if (start < min) {
+ startSpringback(start, min, 0);
+ } else if (start > max) {
+ startSpringback(start, max, 0);
+ }
+
+ return !mFinished;
+ }
+
+ private void startSpringback(int start, int end, int velocity) {
+ // mStartTime has been set
+ mFinished = false;
+ mState = CUBIC;
+ mStart = start;
+ mFinal = end;
+ final int delta = start - end;
+ mDeceleration = getDeceleration(delta);
+ // TODO take velocity into account
+ mVelocity = -delta; // only sign is used
+ mOver = Math.abs(delta);
+ mDuration = (int) (1000.0 * Math.sqrt(-2.0 * delta / mDeceleration));
+ }
+
+ void fling(int start, int velocity, int min, int max, int over) {
+ mOver = over;
+ mFinished = false;
+ mCurrVelocity = mVelocity = velocity;
+ mDuration = mSplineDuration = 0;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mCurrentPosition = mStart = start;
+
+ if (start > max || start < min) {
+ startAfterEdge(start, min, max, velocity);
+ return;
+ }
+
+ mState = SPLINE;
+ double totalDistance = 0.0;
+
+ if (velocity != 0) {
+ mDuration = mSplineDuration = getSplineFlingDuration(velocity);
+ totalDistance = getSplineFlingDistance(velocity);
+ }
+
+ mSplineDistance = (int) (totalDistance * Math.signum(velocity));
+ mFinal = start + mSplineDistance;
+
+ // Clamp to a valid final position
+ if (mFinal < min) {
+ adjustDuration(mStart, mFinal, min);
+ mFinal = min;
+ }
+
+ if (mFinal > max) {
+ adjustDuration(mStart, mFinal, max);
+ mFinal = max;
+ }
+ }
+
+ private double getSplineDeceleration(int velocity) {
+ return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * PHYSICAL_COEF));
+ }
+
+ private double getSplineFlingDistance(int velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return mFlingFriction * PHYSICAL_COEF * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+ }
+
+ /* Returns the duration, expressed in milliseconds */
+ private int getSplineFlingDuration(int velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return (int) (1000.0 * Math.exp(l / decelMinusOne));
+ }
+
+ private void fitOnBounceCurve(int start, int end, int velocity) {
+ // Simulate a bounce that started from edge
+ final float durationToApex = - velocity / mDeceleration;
+ final float distanceToApex = velocity * velocity / 2.0f / Math.abs(mDeceleration);
+ final float distanceToEdge = Math.abs(end - start);
+ final float totalDuration = (float) Math.sqrt(
+ 2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration));
+ mStartTime -= (int) (1000.0f * (totalDuration - durationToApex));
+ mStart = end;
+ mVelocity = (int) (- mDeceleration * totalDuration);
+ }
+
+ private void startBounceAfterEdge(int start, int end, int velocity) {
+ mDeceleration = getDeceleration(velocity == 0 ? start - end : velocity);
+ fitOnBounceCurve(start, end, velocity);
+ onEdgeReached();
+ }
+
+ private void startAfterEdge(int start, int min, int max, int velocity) {
+ if (start > min && start < max) {
+ Log.e("OverScroller", "startAfterEdge called from a valid position");
+ mFinished = true;
+ return;
+ }
+ final boolean positive = start > max;
+ final int edge = positive ? max : min;
+ final int overDistance = start - edge;
+ boolean keepIncreasing = overDistance * velocity >= 0;
+ if (keepIncreasing) {
+ // Will result in a bounce or a to_boundary depending on velocity.
+ startBounceAfterEdge(start, edge, velocity);
+ } else {
+ final double totalDistance = getSplineFlingDistance(velocity);
+ if (totalDistance > Math.abs(overDistance)) {
+ fling(start, velocity, positive ? min : start, positive ? start : max, mOver);
+ } else {
+ startSpringback(start, edge, velocity);
+ }
+ }
+ }
+
+ void notifyEdgeReached(int start, int end, int over) {
+ // mState is used to detect successive notifications
+ if (mState == SPLINE) {
+ mOver = over;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ // We were in fling/scroll mode before: current velocity is such that distance to
+ // edge is increasing. This ensures that startAfterEdge will not start a new fling.
+ startAfterEdge(start, end, end, (int) mCurrVelocity);
+ }
+ }
+
+ private void onEdgeReached() {
+ // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
+ float distance = mVelocity * mVelocity / (2.0f * Math.abs(mDeceleration));
+ final float sign = Math.signum(mVelocity);
+
+ if (distance > mOver) {
+ // Default deceleration is not sufficient to slow us down before boundary
+ mDeceleration = - sign * mVelocity * mVelocity / (2.0f * mOver);
+ distance = mOver;
+ }
+
+ mOver = (int) distance;
+ mState = BALLISTIC;
+ mFinal = mStart + (int) (mVelocity > 0 ? distance : -distance);
+ mDuration = - (int) (1000.0f * mVelocity / mDeceleration);
+ }
+
+ boolean continueWhenFinished() {
+ switch (mState) {
+ case SPLINE:
+ // Duration from start to null velocity
+ if (mDuration < mSplineDuration) {
+ // If the animation was clamped, we reached the edge
+ mStart = mFinal;
+ // TODO Better compute speed when edge was reached
+ mVelocity = (int) mCurrVelocity;
+ mDeceleration = getDeceleration(mVelocity);
+ mStartTime += mDuration;
+ onEdgeReached();
+ } else {
+ // Normal stop, no need to continue
+ return false;
+ }
+ break;
+ case BALLISTIC:
+ mStartTime += mDuration;
+ startSpringback(mFinal, mStart, 0);
+ break;
+ case CUBIC:
+ return false;
+ }
+
+ update();
+ return true;
+ }
+
+ /*
+ * Update the current position and velocity for current time. Returns
+ * true if update has been done and false if animation duration has been
+ * reached.
+ */
+ boolean update() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final long currentTime = time - mStartTime;
+
+ if (currentTime > mDuration) {
+ return false;
+ }
+
+ double distance = 0.0;
+ switch (mState) {
+ case SPLINE: {
+ final float t = (float) currentTime / mSplineDuration;
+ final int index = (int) (NB_SAMPLES * t);
+ float distanceCoef = 1.f;
+ float velocityCoef = 0.f;
+ if (index < NB_SAMPLES) {
+ final float t_inf = (float) index / NB_SAMPLES;
+ final float t_sup = (float) (index + 1) / NB_SAMPLES;
+ final float d_inf = SPLINE_POSITION[index];
+ final float d_sup = SPLINE_POSITION[index + 1];
+ velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
+ distanceCoef = d_inf + (t - t_inf) * velocityCoef;
+ }
+
+ distance = distanceCoef * mSplineDistance;
+ mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f;
+ break;
+ }
+
+ case BALLISTIC: {
+ final float t = currentTime / 1000.0f;
+ mCurrVelocity = mVelocity + mDeceleration * t;
+ distance = mVelocity * t + mDeceleration * t * t / 2.0f;
+ break;
+ }
+
+ case CUBIC: {
+ final float t = (float) (currentTime) / mDuration;
+ final float t2 = t * t;
+ final float sign = Math.signum(mVelocity);
+ distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2);
+ mCurrVelocity = sign * mOver * 6.0f * (- t + t2);
+ break;
+ }
+ }
+
+ mCurrentPosition = mStart + (int) Math.round(distance);
+
+ return true;
+ }
+ }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/common/Scroller.java b/gallerycommon/src/com/android/gallery3d/common/Scroller.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cefd6fb0a01ec550a3a109e1136af871db03932
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/common/Scroller.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2006 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.common;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.os.Build;
+import android.util.FloatMath;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+
+/**
+ * This class encapsulates scrolling. The duration of the scroll
+ * can be passed in the constructor and specifies the maximum time that
+ * the scrolling animation should take. Past this time, the scrolling is
+ * automatically moved to its final stage and computeScrollOffset()
+ * will always return false to indicate that scrolling is over.
+ */
+public class Scroller {
+ private int mMode;
+
+ private int mStartX;
+ private int mStartY;
+ private int mFinalX;
+ private int mFinalY;
+
+ private int mMinX;
+ private int mMaxX;
+ private int mMinY;
+ private int mMaxY;
+
+ private int mCurrX;
+ private int mCurrY;
+ private long mStartTime;
+ private int mDuration;
+ private float mDurationReciprocal;
+ private float mDeltaX;
+ private float mDeltaY;
+ private boolean mFinished;
+ private Interpolator mInterpolator;
+ private boolean mFlywheel;
+
+ private float mVelocity;
+
+ private static final int DEFAULT_DURATION = 250;
+ private static final int SCROLL_MODE = 0;
+ private static final int FLING_MODE = 1;
+
+ private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
+ private static float ALPHA = 800; // pixels / seconds
+ private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
+ private static float END_TENSION = 1.0f - START_TENSION;
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE = new float[NB_SAMPLES + 1];
+
+ private float mDeceleration;
+ private final float mPpi;
+
+ static {
+ float x_min = 0.0f;
+ for (int i = 0; i <= NB_SAMPLES; i++) {
+ final float t = (float) i / NB_SAMPLES;
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
+ if (Math.abs(tx - t) < 1E-5) break;
+ if (tx > t) x_max = x;
+ else x_min = x;
+ }
+ final float d = coef + x * x * x;
+ SPLINE[i] = d;
+ }
+ SPLINE[NB_SAMPLES] = 1.0f;
+
+ // This controls the viscous fluid effect (how much of it)
+ sViscousFluidScale = 8.0f;
+ // must be set to 1.0 (used in viscousFluid())
+ sViscousFluidNormalize = 1.0f;
+ sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+ }
+
+ private static float sViscousFluidScale;
+ private static float sViscousFluidNormalize;
+
+ /**
+ * Create a Scroller with the default duration and interpolator.
+ */
+ public Scroller(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
+ * be in effect for apps targeting Honeycomb or newer.
+ */
+ public Scroller(Context context, Interpolator interpolator) {
+ this(context, interpolator,
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. Specify whether or
+ * not to support progressive "flywheel" behavior in flinging.
+ */
+ public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
+ mFinished = true;
+ mInterpolator = interpolator;
+ mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
+ mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
+ mFlywheel = flywheel;
+ }
+
+ /**
+ * The amount of friction applied to flings. The default value
+ * is {@link ViewConfiguration#getScrollFriction}.
+ *
+ * @param friction A scalar dimension-less value representing the coefficient of
+ * friction.
+ */
+ public final void setFriction(float friction) {
+ mDeceleration = computeDeceleration(friction);
+ }
+
+ private float computeDeceleration(float friction) {
+ return SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * mPpi // pixels per inch
+ * friction;
+ }
+
+ /**
+ *
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mFinished = finished;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ */
+ public final int getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * Returns the current X offset in the scroll.
+ *
+ * @return The new X offset as an absolute distance from the origin.
+ */
+ public final int getCurrX() {
+ return mCurrX;
+ }
+
+ /**
+ * Returns the current Y offset in the scroll.
+ *
+ * @return The new Y offset as an absolute distance from the origin.
+ */
+ public final int getCurrY() {
+ return mCurrY;
+ }
+
+ /**
+ * Returns the current velocity.
+ *
+ * @return The original velocity less the deceleration. Result may be
+ * negative.
+ */
+ public float getCurrVelocity() {
+ return mVelocity - mDeceleration * timePassed() / 2000.0f;
+ }
+
+ /**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ */
+ public final int getStartX() {
+ return mStartX;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ */
+ public final int getStartY() {
+ return mStartY;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final X offset as an absolute distance from the origin.
+ */
+ public final int getFinalX() {
+ return mFinalX;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final Y offset as an absolute distance from the origin.
+ */
+ public final int getFinalY() {
+ return mFinalY;
+ }
+
+ /**
+ * Call this when you want to know the new location. If it returns true,
+ * the animation is not yet finished. loc will be altered to provide the
+ * new location.
+ */
+ public boolean computeScrollOffset() {
+ if (mFinished) {
+ return false;
+ }
+
+ int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+
+ if (timePassed < mDuration) {
+ switch (mMode) {
+ case SCROLL_MODE:
+ float x = timePassed * mDurationReciprocal;
+
+ if (mInterpolator == null)
+ x = viscousFluid(x);
+ else
+ x = mInterpolator.getInterpolation(x);
+
+ mCurrX = mStartX + Math.round(x * mDeltaX);
+ mCurrY = mStartY + Math.round(x * mDeltaY);
+ break;
+ case FLING_MODE:
+ final float t = (float) timePassed / mDuration;
+ final int index = (int) (NB_SAMPLES * t);
+ final float t_inf = (float) index / NB_SAMPLES;
+ final float t_sup = (float) (index + 1) / NB_SAMPLES;
+ final float d_inf = SPLINE[index];
+ final float d_sup = SPLINE[index + 1];
+ final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf);
+
+ mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
+ // Pin to mMinX <= mCurrX <= mMaxX
+ mCurrX = Math.min(mCurrX, mMaxX);
+ mCurrX = Math.max(mCurrX, mMinX);
+
+ mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
+ // Pin to mMinY <= mCurrY <= mMaxY
+ mCurrY = Math.min(mCurrY, mMaxY);
+ mCurrY = Math.max(mCurrY, mMinY);
+
+ if (mCurrX == mFinalX && mCurrY == mFinalY) {
+ mFinished = true;
+ }
+
+ break;
+ }
+ }
+ else {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+ return true;
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ * The scroll will use the default value of 250 milliseconds for the
+ * duration.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy) {
+ startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ * @param duration Duration of the scroll in milliseconds.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+ mMode = SCROLL_MODE;
+ mFinished = false;
+ mDuration = duration;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+ mFinalX = startX + dx;
+ mFinalY = startY + dy;
+ mDeltaX = dx;
+ mDeltaY = dy;
+ mDurationReciprocal = 1.0f / mDuration;
+ }
+
+ /**
+ * Start scrolling based on a fling gesture. The distance travelled will
+ * depend on the initial velocity of the fling.
+ *
+ * @param startX Starting point of the scroll (X)
+ * @param startY Starting point of the scroll (Y)
+ * @param velocityX Initial velocity of the fling (X) measured in pixels per
+ * second.
+ * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+ * second
+ * @param minX Minimum X value. The scroller will not scroll past this
+ * point.
+ * @param maxX Maximum X value. The scroller will not scroll past this
+ * point.
+ * @param minY Minimum Y value. The scroller will not scroll past this
+ * point.
+ * @param maxY Maximum Y value. The scroller will not scroll past this
+ * point.
+ */
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY) {
+ // Continue a scroll or fling in progress
+ if (mFlywheel && !mFinished) {
+ float oldVel = getCurrVelocity();
+
+ float dx = mFinalX - mStartX;
+ float dy = mFinalY - mStartY;
+ float hyp = FloatMath.sqrt(dx * dx + dy * dy);
+
+ float ndx = dx / hyp;
+ float ndy = dy / hyp;
+
+ float oldVelocityX = ndx * oldVel;
+ float oldVelocityY = ndy * oldVel;
+ if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+ Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+ velocityX += oldVelocityX;
+ velocityY += oldVelocityY;
+ }
+ }
+
+ mMode = FLING_MODE;
+ mFinished = false;
+
+ float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
+
+ mVelocity = velocity;
+ final double l = Math.log(START_TENSION * velocity / ALPHA);
+ mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+
+ float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
+ float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
+
+ int totalDistance =
+ (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
+
+ mMinX = minX;
+ mMaxX = maxX;
+ mMinY = minY;
+ mMaxY = maxY;
+
+ mFinalX = startX + Math.round(totalDistance * coeffX);
+ // Pin to mMinX <= mFinalX <= mMaxX
+ mFinalX = Math.min(mFinalX, mMaxX);
+ mFinalX = Math.max(mFinalX, mMinX);
+
+ mFinalY = startY + Math.round(totalDistance * coeffY);
+ // Pin to mMinY <= mFinalY <= mMaxY
+ mFinalY = Math.min(mFinalY, mMaxY);
+ mFinalY = Math.max(mFinalY, mMinY);
+ }
+
+ static float viscousFluid(float x)
+ {
+ x *= sViscousFluidScale;
+ if (x < 1.0f) {
+ x -= (1.0f - (float)Math.exp(-x));
+ } else {
+ float start = 0.36787944117f; // 1/e == exp(-1)
+ x = 1.0f - (float)Math.exp(1.0f - x);
+ x = start + x * (1.0f - start);
+ }
+ x *= sViscousFluidNormalize;
+ return x;
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating cause the scroller to move to the final x and y
+ * position
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinalX(int)
+ * @see #setFinalY(int)
+ */
+ public void extendDuration(int extend) {
+ int passed = timePassed();
+ mDuration = passed + extend;
+ mDurationReciprocal = 1.0f / mDuration;
+ mFinished = false;
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ */
+ public int timePassed() {
+ return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+ }
+
+ /**
+ * Sets the final position (X) for this scroller.
+ *
+ * @param newX The new X offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalY(int)
+ */
+ public void setFinalX(int newX) {
+ mFinalX = newX;
+ mDeltaX = mFinalX - mStartX;
+ mFinished = false;
+ }
+
+ /**
+ * Sets the final position (Y) for this scroller.
+ *
+ * @param newY The new Y offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalX(int)
+ */
+ public void setFinalY(int newY) {
+ mFinalY = newY;
+ mDeltaY = mFinalY - mStartY;
+ mFinished = false;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isScrollingInDirection(float xvel, float yvel) {
+ return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
+ Math.signum(yvel) == Math.signum(mFinalY - mStartY);
+ }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/common/Utils.java b/gallerycommon/src/com/android/gallery3d/common/Utils.java
index 391b22535892ebc6dea8a19fdf0a0fee23c2b18b..3a68745c4ff991a7069283fc1c11c2cdf7061766 100644
--- a/gallerycommon/src/com/android/gallery3d/common/Utils.java
+++ b/gallerycommon/src/com/android/gallery3d/common/Utils.java
@@ -76,7 +76,7 @@ public class Utils {
// Throws IllegalArgumentException if the input is <= 0 or
// the answer overflows.
public static int nextPowerOf2(int n) {
- if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException();
+ if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n);
n -= 1;
n |= n >> 16;
n |= n >> 8;
@@ -310,7 +310,7 @@ public class Utils {
Build.DEVICE,
Build.MODEL,
Build.ID,
- Build.VERSION.SDK,
+ Build.VERSION.SDK_INT,
Build.VERSION.RELEASE,
Build.VERSION.INCREMENTAL);
}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/CountedDataInputStream.java b/gallerycommon/src/com/android/gallery3d/exif/CountedDataInputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..dfd4a1a1034bfa19d4073b2f204c974b6a16c30f
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/CountedDataInputStream.java
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+class CountedDataInputStream extends FilterInputStream {
+
+ private int mCount = 0;
+
+ // allocate a byte buffer for a long value;
+ private final byte mByteArray[] = new byte[8];
+ private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray);
+
+ protected CountedDataInputStream(InputStream in) {
+ super(in);
+ }
+
+ public int getReadByteCount() {
+ return mCount;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ int r = in.read(b);
+ mCount += (r >= 0) ? r : 0;
+ return r;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int r = in.read(b, off, len);
+ mCount += (r >= 0) ? r : 0;
+ return r;
+ }
+
+ @Override
+ public int read() throws IOException {
+ int r = in.read();
+ mCount += (r >= 0) ? 1 : 0;
+ return r;
+ }
+
+ @Override
+ public long skip(long length) throws IOException {
+ long skip = in.skip(length);
+ mCount += skip;
+ return skip;
+ }
+
+ public void skipOrThrow(long length) throws IOException {
+ if (skip(length) != length) throw new EOFException();
+ }
+
+ public void skipTo(long target) throws IOException {
+ long cur = mCount;
+ long diff = target - cur;
+ assert(diff >= 0);
+ skipOrThrow(diff);
+ }
+
+ public void readOrThrow(byte[] b, int off, int len) throws IOException {
+ int r = read(b, off, len);
+ if (r != len) throw new EOFException();
+ }
+
+ public void readOrThrow(byte[] b) throws IOException {
+ readOrThrow(b, 0, b.length);
+ }
+
+ public void setByteOrder(ByteOrder order) {
+ mByteBuffer.order(order);
+ }
+
+ public ByteOrder getByteOrder() {
+ return mByteBuffer.order();
+ }
+
+ public short readShort() throws IOException {
+ readOrThrow(mByteArray, 0 ,2);
+ mByteBuffer.rewind();
+ return mByteBuffer.getShort();
+ }
+
+ public int readUnsignedShort() throws IOException {
+ return readShort() & 0xffff;
+ }
+
+ public int readInt() throws IOException {
+ readOrThrow(mByteArray, 0 , 4);
+ mByteBuffer.rewind();
+ return mByteBuffer.getInt();
+ }
+
+ public long readUnsignedInt() throws IOException {
+ return readInt() & 0xffffffffL;
+ }
+
+ public long readLong() throws IOException {
+ readOrThrow(mByteArray, 0 , 8);
+ mByteBuffer.rewind();
+ return mByteBuffer.getLong();
+ }
+
+ public String readString(int n) throws IOException {
+ byte buf[] = new byte[n];
+ readOrThrow(buf);
+ return new String(buf, "UTF8");
+ }
+
+ public String readString(int n, Charset charset) throws IOException {
+ byte buf[] = new byte[n];
+ readOrThrow(buf);
+ return new String(buf, charset);
+ }
+}
\ No newline at end of file
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
new file mode 100644
index 0000000000000000000000000000000000000000..39eb57455c00ff136802f8dcdaf5e99c893496e5
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * This class stores the EXIF header in IFDs according to the JPEG specification.
+ * It is the result produced by {@link ExifReader}.
+ * @see ExifReader
+ * @see IfdData
+ */
+public class ExifData {
+ private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
+ private byte[] mThumbnail;
+ private ArrayList mStripBytes = new ArrayList();
+ private final ByteOrder mByteOrder;
+
+ public ExifData(ByteOrder order) {
+ mByteOrder = order;
+ }
+
+ IfdData getIfdData(int ifdId) {
+ return mIfdDatas[ifdId];
+ }
+
+ /**
+ * Adds IFD data. If IFD data of the same type already exists,
+ * it will be replaced by the new data.
+ */
+ void addIfdData(IfdData data) {
+ mIfdDatas[data.getId()] = data;
+ }
+
+ /**
+ * Gets the compressed thumbnail. Returns null if there is no compressed thumbnail.
+ *
+ * @see #hasCompressedThumbnail()
+ */
+ public byte[] getCompressedThumbnail() {
+ return mThumbnail;
+ }
+
+ /**
+ * Sets the compressed thumbnail.
+ */
+ public void setCompressedThumbnail(byte[] thumbnail) {
+ mThumbnail = thumbnail;
+ }
+
+ /**
+ * Returns true it this header contains a compressed thumbnail.
+ */
+ public boolean hasCompressedThumbnail() {
+ return mThumbnail != null;
+ }
+
+ /**
+ * Adds an uncompressed strip.
+ */
+ public void setStripBytes(int index, byte[] strip) {
+ if (index < mStripBytes.size()) {
+ mStripBytes.set(index, strip);
+ } else {
+ for (int i = mStripBytes.size(); i < index; i++) {
+ mStripBytes.add(null);
+ }
+ mStripBytes.add(strip);
+ }
+ }
+
+ /**
+ * Gets the strip count.
+ */
+ public int getStripCount() {
+ return mStripBytes.size();
+ }
+
+ /**
+ * Gets the strip at the specified index.
+ * @exceptions #IndexOutOfBoundException
+ */
+ public byte[] getStrip(int index) {
+ return mStripBytes.get(index);
+ }
+
+ /**
+ * Gets the byte order.
+ */
+ public ByteOrder getByteOrder() {
+ return mByteOrder;
+ }
+
+ /**
+ * Returns true if this header contains uncompressed strip of thumbnail.
+ */
+ public boolean hasUncompressedStrip() {
+ return mStripBytes.size() != 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ExifData) {
+ ExifData data = (ExifData) obj;
+ if (data.mByteOrder != mByteOrder
+ || !Arrays.equals(data.mThumbnail, mThumbnail)
+ || data.mStripBytes.size() != mStripBytes.size()) return false;
+
+ for (int i = 0; i < mStripBytes.size(); i++) {
+ if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) return false;
+ }
+
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ if (!Util.equals(data.getIfdData(i), getIfdData(i))) return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds {@link ExifTag#TAG_GPS_LATITUDE}, {@link ExifTag#TAG_GPS_LONGITUDE},
+ * {@link ExifTag#TAG_GPS_LATITUDE_REF} and {@link ExifTag#TAG_GPS_LONGITUDE_REF} with the
+ * given latitude and longitude.
+ */
+ public void addGpsTags(double latitude, double longitude) {
+ IfdData gpsIfd = getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd == null) {
+ gpsIfd = new IfdData(IfdId.TYPE_IFD_GPS);
+ addIfdData(gpsIfd);
+ }
+ ExifTag latTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE, ExifTag.TYPE_RATIONAL,
+ 3, IfdId.TYPE_IFD_GPS);
+ ExifTag longTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE, ExifTag.TYPE_RATIONAL,
+ 3, IfdId.TYPE_IFD_GPS);
+ ExifTag latRefTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE_REF,
+ ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS);
+ ExifTag longRefTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE_REF,
+ ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS);
+ latTag.setValue(toExifLatLong(latitude));
+ longTag.setValue(toExifLatLong(longitude));
+ latRefTag.setValue(latitude >= 0
+ ? ExifTag.GpsLatitudeRef.NORTH
+ : ExifTag.GpsLatitudeRef.SOUTH);
+ longRefTag.setValue(longitude >= 0
+ ? ExifTag.GpsLongitudeRef.EAST
+ : ExifTag.GpsLongitudeRef.WEST);
+ gpsIfd.setTag(latTag);
+ gpsIfd.setTag(longTag);
+ gpsIfd.setTag(latRefTag);
+ gpsIfd.setTag(longRefTag);
+ }
+
+ private static Rational[] toExifLatLong(double value) {
+ // convert to the format dd/1 mm/1 ssss/100
+ value = Math.abs(value);
+ int degrees = (int) value;
+ value = (value - degrees) * 60;
+ int minutes = (int) value;
+ value = (value - minutes) * 6000;
+ int seconds = (int) value;
+ return new Rational[] {
+ new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)};
+ }
+
+ private IfdData getOrCreateIfdData(int ifdId) {
+ IfdData ifdData = mIfdDatas[ifdId];
+ if (ifdData == null) {
+ ifdData = new IfdData(ifdId);
+ mIfdDatas[ifdId] = ifdData;
+ }
+ return ifdData;
+ }
+
+ /**
+ * Gets the tag with the given tag ID. Returns null if the tag does not exist. For tags
+ * related to interoperability or thumbnail, call {@link #getInteroperabilityTag(short)} and
+ * {@link #getThumbnailTag(short)} respectively.
+ */
+ public ExifTag getTag(short tagId) {
+ int ifdId = ExifTag.getIfdIdFromTagId(tagId);
+ IfdData ifdData = mIfdDatas[ifdId];
+ return (ifdData == null) ? null : ifdData.getTag(tagId);
+ }
+
+ /**
+ * Gets the thumbnail-related tag with the given tag ID.
+ */
+ public ExifTag getThumbnailTag(short tagId) {
+ IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_1];
+ return (ifdData == null) ? null : ifdData.getTag(tagId);
+ }
+
+ /**
+ * Gets the interoperability-related tag with the given tag ID.
+ */
+ public ExifTag getInteroperabilityTag(short tagId) {
+ IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_INTEROPERABILITY];
+ return (ifdData == null) ? null : ifdData.getTag(tagId);
+ }
+
+ /**
+ * Adds a tag with the given tag ID. The original tag will be replaced by the new tag. For tags
+ * related to interoperability or thumbnail, call {@link #addInteroperabilityTag(short)} or
+ * {@link #addThumbnailTag(short)} respectively.
+ * @exception IllegalArgumentException if the tag ID is invalid.
+ */
+ public ExifTag addTag(short tagId) {
+ int ifdId = ExifTag.getIfdIdFromTagId(tagId);
+ IfdData ifdData = getOrCreateIfdData(ifdId);
+ ExifTag tag = ExifTag.buildTag(tagId);
+ ifdData.setTag(tag);
+ return tag;
+ }
+
+ /**
+ * Adds a thumbnail-related tag with the given tag ID. The original tag will be replaced
+ * by the new tag.
+ * @exception IllegalArgumentException if the tag ID is invalid.
+ */
+ public ExifTag addThumbnailTag(short tagId) {
+ IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_1);
+ ExifTag tag = ExifTag.buildThumbnailTag(tagId);
+ ifdData.setTag(tag);
+ return tag;
+ }
+
+ /**
+ * Adds an interoperability-related tag with the given tag ID. The original tag will be
+ * replaced by the new tag.
+ * @exception IllegalArgumentException if the tag ID is invalid.
+ */
+ public ExifTag addInteroperabilityTag(short tagId) {
+ IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ ExifTag tag = ExifTag.buildInteroperabilityTag(tagId);
+ ifdData.setTag(tag);
+ return tag;
+ }
+
+ public void removeThumbnailData() {
+ mThumbnail = null;
+ mStripBytes.clear();
+ mIfdDatas[IfdId.TYPE_IFD_1] = null;
+ }
+}
\ No newline at end of file
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifInvalidFormatException.java b/gallerycommon/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf923ec269d8fe5d678797a13f99ff9db8abd852
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+public class ExifInvalidFormatException extends Exception {
+ public ExifInvalidFormatException(String meg) {
+ super(meg);
+ }
+}
\ No newline at end of file
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..b8db8e34cab848020b5504d10687c49b9488f46d
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -0,0 +1,377 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class ExifOutputStream extends FilterOutputStream {
+ private static final String TAG = "ExifOutputStream";
+
+ private static final int STATE_SOI = 0;
+ private static final int STATE_FRAME_HEADER = 1;
+ private static final int STATE_JPEG_DATA = 2;
+
+ private static final int EXIF_HEADER = 0x45786966;
+ private static final short TIFF_HEADER = 0x002A;
+ private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+ private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+ private static final short TAG_SIZE = 12;
+ private static final short TIFF_HEADER_SIZE = 8;
+
+ private ExifData mExifData;
+ private int mState = STATE_SOI;
+ private int mByteToSkip;
+ private int mByteToCopy;
+ private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+
+ public ExifOutputStream(OutputStream ou) {
+ super(ou);
+ }
+
+ public void setExifData(ExifData exifData) {
+ mExifData = exifData;
+ }
+
+ public ExifData getExifData() {
+ return mExifData;
+ }
+
+ private int requestByteToBuffer(int requestByteCount, byte[] buffer
+ , int offset, int length) {
+ int byteNeeded = requestByteCount - mBuffer.position();
+ int byteToRead = length > byteNeeded ? byteNeeded : length;
+ mBuffer.put(buffer, offset, byteToRead);
+ return byteToRead;
+ }
+
+ @Override
+ public void write(byte[] buffer, int offset, int length) throws IOException {
+ while((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
+ && length > 0) {
+ if (mByteToSkip > 0) {
+ int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
+ length -= byteToProcess;
+ mByteToSkip -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (mByteToCopy > 0) {
+ int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
+ out.write(buffer, offset, byteToProcess);
+ length -= byteToProcess;
+ mByteToCopy -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (length == 0) return;
+ switch (mState) {
+ case STATE_SOI:
+ int byteRead = requestByteToBuffer(2, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ if (mBuffer.position() < 2) return;
+ mBuffer.rewind();
+ assert(mBuffer.getShort() == JpegHeader.SOI);
+ out.write(mBuffer.array(), 0 ,2);
+ mState = STATE_FRAME_HEADER;
+ mBuffer.rewind();
+ writeExifData();
+ break;
+ case STATE_FRAME_HEADER:
+ // We ignore the APP1 segment and copy all other segments until SOF tag.
+ byteRead = requestByteToBuffer(4, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ // Check if this image data doesn't contain SOF.
+ if (mBuffer.position() == 2) {
+ short tag = mBuffer.getShort();
+ if (tag == JpegHeader.EOI) {
+ out.write(mBuffer.array(), 0, 2);
+ mBuffer.rewind();
+ }
+ }
+ if (mBuffer.position() < 4) return;
+ mBuffer.rewind();
+ short marker = mBuffer.getShort();
+ if (marker == JpegHeader.APP1) {
+ mByteToSkip = (mBuffer.getShort() & 0xff) - 2;
+ mState = STATE_JPEG_DATA;
+ } else if (!JpegHeader.isSofMarker(marker)) {
+ out.write(mBuffer.array(), 0, 4);
+ mByteToCopy = (mBuffer.getShort() & 0xff) - 2;
+ } else {
+ out.write(mBuffer.array(), 0, 4);
+ mState = STATE_JPEG_DATA;
+ }
+ mBuffer.rewind();
+ }
+ }
+ if (length > 0) {
+ out.write(buffer, offset, length);
+ }
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ byte[] buf = new byte[] {(byte) (0xff & oneByte)};
+ write(buf);
+ }
+
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ write(buffer, 0, buffer.length);
+ }
+
+ private void writeExifData() throws IOException {
+ createRequiredIfdAndTag();
+ int exifSize = calculateAllOffset();
+ OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
+ dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ dataOutputStream.writeShort(JpegHeader.APP1);
+ dataOutputStream.writeShort((short) (exifSize + 8));
+ dataOutputStream.writeInt(EXIF_HEADER);
+ dataOutputStream.writeShort((short) 0x0000);
+ if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+ dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
+ } else {
+ dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
+ }
+ dataOutputStream.setByteOrder(mExifData.getByteOrder());
+ dataOutputStream.writeShort(TIFF_HEADER);
+ dataOutputStream.writeInt(8);
+ writeAllTags(dataOutputStream);
+ writeThumbnail(dataOutputStream);
+ }
+
+ private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
+ if (mExifData.hasCompressedThumbnail()) {
+ dataOutputStream.write(mExifData.getCompressedThumbnail());
+ } else if (mExifData.hasUncompressedStrip()) {
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ dataOutputStream.write(mExifData.getStrip(i));
+ }
+ }
+ }
+
+ private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
+ IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interoperabilityIfd != null) {
+ writeIfd(interoperabilityIfd, dataOutputStream);
+ }
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ writeIfd(gpsIfd, dataOutputStream);
+ }
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+ if (ifd1 != null) {
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
+ }
+ }
+
+ private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ ExifTag[] tags = ifd.getAllTags();
+ dataOutputStream.writeShort((short) tags.length);
+ for (ExifTag tag: tags) {
+ dataOutputStream.writeShort(tag.getTagId());
+ dataOutputStream.writeShort(tag.getDataType());
+ dataOutputStream.writeInt(tag.getComponentCount());
+ if (tag.getDataSize() > 4) {
+ dataOutputStream.writeInt(tag.getOffset());
+ } else {
+ writeTagValue(tag, dataOutputStream);
+ for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
+ dataOutputStream.write(0);
+ }
+ }
+ }
+ dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
+ for (ExifTag tag: tags) {
+ if (tag.getDataSize() > 4) {
+ writeTagValue(tag, dataOutputStream);
+ }
+ }
+ }
+
+ private void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_ASCII:
+ dataOutputStream.write(tag.getString().getBytes());
+ int remain = tag.getComponentCount() - tag.getString().length();
+ for (int i = 0; i < remain; i++) {
+ dataOutputStream.write(0);
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeInt(tag.getLong(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeRational(tag.getRational(i));
+ }
+ break;
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ byte[] buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ dataOutputStream.write(buf);
+ break;
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeInt((int) tag.getUnsignedLong(i));
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeShort((short) tag.getUnsignedShort(i));
+ }
+ break;
+ }
+ }
+
+ private int calculateOffsetOfIfd(IfdData ifd, int offset) {
+ offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
+ ExifTag[] tags = ifd.getAllTags();
+ for(ExifTag tag: tags) {
+ if (tag.getDataSize() > 4) {
+ tag.setOffset(offset);
+ offset += tag.getDataSize();
+ }
+ }
+ return offset;
+ }
+
+ private void createRequiredIfdAndTag() {
+ // IFD0 is required for all file
+ IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+ if (ifd0 == null) {
+ ifd0 = new IfdData(IfdId.TYPE_IFD_0);
+ mExifData.addIfdData(ifd0);
+ }
+ ExifTag exifOffsetTag = new ExifTag(ExifTag.TAG_EXIF_IFD,
+ ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
+ ifd0.setTag(exifOffsetTag);
+
+ // Exif IFD is required for all file.
+ IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+ if (exifIfd == null) {
+ exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
+ mExifData.addIfdData(exifIfd);
+ }
+
+ // GPS IFD
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ ExifTag gpsOffsetTag = new ExifTag(ExifTag.TAG_GPS_IFD,
+ ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
+ ifd0.setTag(gpsOffsetTag);
+ }
+
+ // Interoperability IFD
+ IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interIfd != null) {
+ ExifTag interOffsetTag = new ExifTag(ExifTag.TAG_INTEROPERABILITY_IFD,
+ ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_EXIF);
+ exifIfd.setTag(interOffsetTag);
+ }
+
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+
+ // thumbnail
+ if (mExifData.hasCompressedThumbnail()) {
+ if (ifd1 == null) {
+ ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+ mExifData.addIfdData(ifd1);
+ }
+ ExifTag offsetTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT,
+ ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
+ ifd1.setTag(offsetTag);
+ ExifTag lengthTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+ ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
+ lengthTag.setValue(mExifData.getCompressedThumbnail().length);
+ ifd1.setTag(lengthTag);
+ } else if (mExifData.hasUncompressedStrip()){
+ if (ifd1 == null) {
+ ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+ mExifData.addIfdData(ifd1);
+ }
+ int stripCount = mExifData.getStripCount();
+ ExifTag offsetTag = new ExifTag(ExifTag.TAG_STRIP_OFFSETS,
+ ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
+ ExifTag lengthTag = new ExifTag(ExifTag.TAG_STRIP_BYTE_COUNTS,
+ ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
+ long[] lengths = new long[stripCount];
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ lengths[i] = mExifData.getStrip(i).length;
+ }
+ lengthTag.setValue(lengths);
+ ifd1.setTag(offsetTag);
+ ifd1.setTag(lengthTag);
+ }
+ }
+
+ private int calculateAllOffset() {
+ int offset = TIFF_HEADER_SIZE;
+ IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+ offset = calculateOffsetOfIfd(ifd0, offset);
+ ifd0.getTag(ExifTag.TAG_EXIF_IFD).setValue(offset);
+
+ IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+ offset = calculateOffsetOfIfd(exifIfd, offset);
+
+ IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interIfd != null) {
+ exifIfd.getTag(ExifTag.TAG_INTEROPERABILITY_IFD).setValue(offset);
+ offset = calculateOffsetOfIfd(interIfd, offset);
+ }
+
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ ifd0.getTag(ExifTag.TAG_GPS_IFD).setValue(offset);
+ offset = calculateOffsetOfIfd(gpsIfd, offset);
+ }
+
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+ if (ifd1 != null) {
+ ifd0.setOffsetToNextIfd(offset);
+ offset = calculateOffsetOfIfd(ifd1, offset);
+ }
+
+ // thumbnail
+ if (mExifData.hasCompressedThumbnail()) {
+ ifd1.getTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT).setValue(offset);
+ offset += mExifData.getCompressedThumbnail().length;
+ } else if (mExifData.hasUncompressedStrip()){
+ int stripCount = mExifData.getStripCount();
+ long[] offsets = new long[stripCount];
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ offsets[i] = offset;
+ offset += mExifData.getStrip(i).length;
+ }
+ ifd1.getTag(ExifTag.TAG_STRIP_OFFSETS).setValue(offsets);
+ }
+ return offset;
+ }
+}
\ No newline at end of file
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1e52c5b35ab76050031b733e6be71de325cf646
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
@@ -0,0 +1,752 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/**
+ * This class provides a low-level EXIF parsing API. Given a JPEG format InputStream, the caller
+ * can request which IFD's to read via {@link #parse(InputStream, int)} with given options.
+ *
+ * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the parser.
+ *
+ * void parse() {
+ * ExifParser parser = ExifParser.parse(mImageInputStream,
+ * ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
+ * int event = parser.next();
+ * while (event != ExifParser.EVENT_END) {
+ * switch (event) {
+ * case ExifParser.EVENT_START_OF_IFD:
+ * break;
+ * case ExifParser.EVENT_NEW_TAG:
+ * ExifTag tag = parser.getTag();
+ * if (!tag.hasValue()) {
+ * parser.registerForTagValue(tag);
+ * } else {
+ * processTag(tag);
+ * }
+ * break;
+ * case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ * tag = parser.getTag();
+ * if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ * processTag(tag);
+ * }
+ * break;
+ * }
+ * event = parser.next();
+ * }
+ * }
+ *
+ * void processTag(ExifTag tag) {
+ * // process the tag as you like.
+ * }
+ *
+ */
+public class ExifParser {
+ /**
+ * When the parser reaches a new IFD area. Call
+ * {@link #getCurrentIfd()} to know which IFD we are in.
+ */
+ public static final int EVENT_START_OF_IFD = 0;
+ /**
+ * When the parser reaches a new tag. Call {@link #getTag()}to get the
+ * corresponding tag.
+ */
+ public static final int EVENT_NEW_TAG = 1;
+ /**
+ * When the parser reaches the value area of tag that is registered by
+ * {@link #registerForTagValue(ExifTag)} previously. Call
+ * {@link #getTag()} to get the corresponding tag.
+ */
+ public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
+
+ /**
+ * When the parser reaches the compressed image area.
+ */
+ public static final int EVENT_COMPRESSED_IMAGE = 3;
+ /**
+ * When the parser reaches the uncompressed image strip.
+ * Call {@link #getStripIndex()} to get the index of the strip.
+ * @see #getStripIndex()
+ * @see #getStripCount()
+ */
+ public static final int EVENT_UNCOMPRESSED_STRIP = 4;
+ /**
+ * When there is nothing more to parse.
+ */
+ public static final int EVENT_END = 5;
+
+ /**
+ * Option bit to request to parse IFD0.
+ */
+ public static final int OPTION_IFD_0 = 1 << 0;
+ /**
+ * Option bit to request to parse IFD1.
+ */
+ public static final int OPTION_IFD_1 = 1 << 1;
+ /**
+ * Option bit to request to parse Exif-IFD.
+ */
+ public static final int OPTION_IFD_EXIF = 1 << 2;
+ /**
+ * Option bit to request to parse GPS-IFD.
+ */
+ public static final int OPTION_IFD_GPS = 1 << 3;
+ /**
+ * Option bit to request to parse Interoperability-IFD.
+ */
+ public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4;
+ /**
+ * Option bit to request to parse thumbnail.
+ */
+ public static final int OPTION_THUMBNAIL = 1 << 5;
+
+ private static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
+ private static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
+
+ // TIFF header
+ private static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
+ private static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
+ private static final short TIFF_HEADER_TAIL = 0x002A;
+
+ private static final int TAG_SIZE = 12;
+ private static final int OFFSET_SIZE = 2;
+
+ private final CountedDataInputStream mTiffStream;
+ private final int mOptions;
+ private int mIfdStartOffset = 0;
+ private int mNumOfTagInIfd = 0;
+ private int mIfdType;
+ private ExifTag mTag;
+ private ImageEvent mImageEvent;
+ private int mStripCount;
+ private ExifTag mStripSizeTag;
+ private ExifTag mJpegSizeTag;
+ private boolean mNeedToParseOffsetsInCurrentIfd;
+ private boolean mContainExifData = false;
+
+ private final TreeMap mCorrespondingEvent = new TreeMap();
+
+ private boolean isIfdRequested(int ifdType) {
+ switch (ifdType) {
+ case IfdId.TYPE_IFD_0:
+ return (mOptions & OPTION_IFD_0) != 0;
+ case IfdId.TYPE_IFD_1:
+ return (mOptions & OPTION_IFD_1) != 0;
+ case IfdId.TYPE_IFD_EXIF:
+ return (mOptions & OPTION_IFD_EXIF) != 0;
+ case IfdId.TYPE_IFD_GPS:
+ return (mOptions & OPTION_IFD_GPS) != 0;
+ case IfdId.TYPE_IFD_INTEROPERABILITY:
+ return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0;
+ }
+ return false;
+ }
+
+ private boolean isThumbnailRequested() {
+ return (mOptions & OPTION_THUMBNAIL) != 0;
+ }
+
+ private ExifParser(InputStream inputStream, int options)
+ throws IOException, ExifInvalidFormatException {
+ mContainExifData = seekTiffData(inputStream);
+ mTiffStream = new CountedDataInputStream(inputStream);
+ mOptions = options;
+ if (!mContainExifData) return;
+ if (mTiffStream.getReadByteCount() == 0) {
+ parseTiffHeader();
+ long offset = mTiffStream.readUnsignedInt();
+ registerIfd(IfdId.TYPE_IFD_0, offset);
+ }
+ }
+
+ /**
+ * Parses the the given InputStream with the given options
+ * @exception IOException
+ * @exception ExifInvalidFormatException
+ */
+ public static ExifParser parse(InputStream inputStream, int options)
+ throws IOException, ExifInvalidFormatException {
+ return new ExifParser(inputStream, options);
+ }
+
+ /**
+ * Parses the the given InputStream with default options; that is, every IFD and thumbnaill
+ * will be parsed.
+ * @exception IOException
+ * @exception ExifInvalidFormatException
+ * @see #parse(InputStream, int)
+ */
+ public static ExifParser parse(InputStream inputStream)
+ throws IOException, ExifInvalidFormatException {
+ return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
+ | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
+ | OPTION_THUMBNAIL);
+ }
+
+ /**
+ * Moves the parser forward and returns the next parsing event
+ *
+ * @exception IOException
+ * @exception ExifInvalidFormatException
+ * @see #EVENT_START_OF_IFD
+ * @see #EVENT_NEW_TAG
+ * @see #EVENT_VALUE_OF_REGISTERED_TAG
+ * @see #EVENT_COMPRESSED_IMAGE
+ * @see #EVENT_UNCOMPRESSED_STRIP
+ * @see #EVENT_END
+ */
+ public int next() throws IOException, ExifInvalidFormatException {
+ if (!mContainExifData) {
+ return EVENT_END;
+ }
+ int offset = mTiffStream.getReadByteCount();
+ int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+ if (offset < endOfTags) {
+ mTag = readTag();
+ if (mNeedToParseOffsetsInCurrentIfd) {
+ checkOffsetOrImageTag(mTag);
+ }
+ return EVENT_NEW_TAG;
+ } else if (offset == endOfTags) {
+ long ifdOffset = readUnsignedLong();
+ // There is a link to ifd1 at the end of ifd0
+ if (mIfdType == IfdId.TYPE_IFD_0) {
+ if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
+ if (ifdOffset != 0) {
+ registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+ }
+ }
+ } else {
+ if (ifdOffset != 0) {
+ throw new ExifInvalidFormatException("Invalid link to next IFD");
+ }
+ }
+ }
+ while(mCorrespondingEvent.size() != 0) {
+ Entry entry = mCorrespondingEvent.pollFirstEntry();
+ Object event = entry.getValue();
+ skipTo(entry.getKey());
+ if (event instanceof IfdEvent) {
+ mIfdType = ((IfdEvent) event).ifd;
+ mNumOfTagInIfd = mTiffStream.readUnsignedShort();
+ mIfdStartOffset = entry.getKey();
+ mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
+ if (((IfdEvent) event).isRequested) {
+ return EVENT_START_OF_IFD;
+ } else {
+ skipRemainingTagsInCurrentIfd();
+ }
+ } else if (event instanceof ImageEvent) {
+ mImageEvent = (ImageEvent) event;
+ return mImageEvent.type;
+ } else {
+ ExifTagEvent tagEvent = (ExifTagEvent) event;
+ mTag = tagEvent.tag;
+ if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ readFullTagValue(mTag);
+ checkOffsetOrImageTag(mTag);
+ }
+ if (tagEvent.isRequested) {
+ return EVENT_VALUE_OF_REGISTERED_TAG;
+ }
+ }
+ }
+ return EVENT_END;
+ }
+
+ /**
+ * Skips the tags area of current IFD, if the parser is not in the tag area, nothing will
+ * happen.
+ *
+ * @throws IOException
+ * @throws ExifInvalidFormatException
+ */
+ public void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
+ int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+ int offset = mTiffStream.getReadByteCount();
+ if (offset > endOfTags) return;
+ if (mNeedToParseOffsetsInCurrentIfd) {
+ while (offset < endOfTags) {
+ mTag = readTag();
+ checkOffsetOrImageTag(mTag);
+ offset += TAG_SIZE;
+ }
+ } else {
+ skipTo(endOfTags);
+ }
+ long ifdOffset = readUnsignedLong();
+ // For ifd0, there is a link to ifd1 in the end of all tags
+ if (mIfdType == IfdId.TYPE_IFD_0
+ && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) {
+ if (ifdOffset > 0) {
+ registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+ }
+ }
+ }
+
+ private boolean needToParseOffsetsInCurrentIfd() {
+ switch (mIfdType) {
+ case IfdId.TYPE_IFD_0:
+ return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS)
+ || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
+ case IfdId.TYPE_IFD_1:
+ return isThumbnailRequested();
+ case IfdId.TYPE_IFD_EXIF:
+ // The offset to interoperability IFD is located in Exif IFD
+ return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * If {@link #next()} return {@link #EVENT_NEW_TAG} or {@link #EVENT_VALUE_OF_REGISTERED_TAG},
+ * call this function to get the corresponding tag.
+ *
+ *
+ * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size of the value is
+ * greater than 4 bytes. One should call {@link ExifTag#hasValue()} to check if the tag
+ * contains value.
+ * If there is no value,call {@link #registerForTagValue(ExifTag)} to have the parser emit
+ * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area pointed by the offset.
+ *
+ *
+ * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the tag will have
+ * already been read except for tags of undefined type. For tags of undefined type, call
+ * one of the read methods to get the value.
+ *
+ * @see #registerForTagValue(ExifTag)
+ * @see #read(byte[])
+ * @see #read(byte[], int, int)
+ * @see #readLong()
+ * @see #readRational()
+ * @see #readShort()
+ * @see #readString(int)
+ * @see #readString(int, Charset)
+ */
+ public ExifTag getTag() {
+ return mTag;
+ }
+
+ /**
+ * Gets number of tags in the current IFD area.
+ */
+ public int getTagCountInCurrentIfd() {
+ return mNumOfTagInIfd;
+ }
+
+ /**
+ * Gets the ID of current IFD.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ * @see IfdId#TYPE_IFD_EXIF
+ */
+ public int getCurrentIfd() {
+ return mIfdType;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP},
+ * call this function to get the index of this strip.
+ * @see #getStripCount()
+ */
+ public int getStripIndex() {
+ return mImageEvent.stripIndex;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the number
+ * of strip data.
+ * @see #getStripIndex()
+ */
+ public int getStripCount() {
+ return mStripCount;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the strip size.
+ */
+ public int getStripSize() {
+ if (mStripSizeTag == null) return 0;
+ if (mStripSizeTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+ return mStripSizeTag.getUnsignedShort(mImageEvent.stripIndex);
+ } else {
+ // Cast unsigned int to int since the strip size is always smaller
+ // than the size of APP1 (65536)
+ return (int) mStripSizeTag.getUnsignedLong(mImageEvent.stripIndex);
+ }
+ }
+
+ /**
+ * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get the image data
+ * size.
+ */
+ public int getCompressedImageSize() {
+ if (mJpegSizeTag == null) return 0;
+ // Cast unsigned int to int since the thumbnail is always smaller
+ // than the size of APP1 (65536)
+ return (int) mJpegSizeTag.getUnsignedLong(0);
+ }
+
+ private void skipTo(int offset) throws IOException {
+ mTiffStream.skipTo(offset);
+ while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
+ mCorrespondingEvent.pollFirstEntry();
+ }
+ }
+
+ /**
+ * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD,
+ * the tag may not contain the value if the size of the value is greater than 4 bytes.
+ * When the value is not available here, call this method so that the parser will emit
+ * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area where the value is located.
+
+ * @see #EVENT_VALUE_OF_REGISTERED_TAG
+ */
+ public void registerForTagValue(ExifTag tag) {
+ mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
+ }
+
+ private void registerIfd(int ifdType, long offset) {
+ // Cast unsigned int to int since the offset is always smaller
+ // than the size of APP1 (65536)
+ mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType)));
+ }
+
+ private void registerCompressedImage(long offset) {
+ mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE));
+ }
+
+ private void registerUncompressedStrip(int stripIndex, long offset) {
+ mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP
+ , stripIndex));
+ }
+
+ private ExifTag readTag() throws IOException, ExifInvalidFormatException {
+ short tagId = mTiffStream.readShort();
+ short dataFormat = mTiffStream.readShort();
+ long numOfComp = mTiffStream.readUnsignedInt();
+ if (numOfComp > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException(
+ "Number of component is larger then Integer.MAX_VALUE");
+ }
+ ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType);
+ int dataSize = tag.getDataSize();
+ if (dataSize > 4) {
+ long offset = mTiffStream.readUnsignedInt();
+ if (offset > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException(
+ "offset is larger then Integer.MAX_VALUE");
+ }
+ tag.setOffset((int) offset);
+ } else {
+ readFullTagValue(tag);
+ mTiffStream.skip(4 - dataSize);
+ }
+ return tag;
+ }
+
+ /**
+ * Check the tag, if the tag is one of the offset tag that points to the IFD or image the
+ * caller is interested in, register the IFD or image.
+ */
+ private void checkOffsetOrImageTag(ExifTag tag) {
+ switch (tag.getTagId()) {
+ case ExifTag.TAG_EXIF_IFD:
+ if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
+ || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+ registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedLong(0));
+ }
+ break;
+ case ExifTag.TAG_GPS_IFD:
+ if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
+ registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedLong(0));
+ }
+ break;
+ case ExifTag.TAG_INTEROPERABILITY_IFD:
+ if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+ registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedLong(0));
+ }
+ break;
+ case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT:
+ if (isThumbnailRequested()) {
+ registerCompressedImage(tag.getUnsignedLong(0));
+ }
+ break;
+ case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
+ if (isThumbnailRequested()) {
+ mJpegSizeTag = tag;
+ }
+ break;
+ case ExifTag.TAG_STRIP_OFFSETS:
+ if (isThumbnailRequested()) {
+ if (tag.hasValue()) {
+ for (int i = 0; i < tag.getComponentCount(); i++) {
+ if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+ registerUncompressedStrip(i, tag.getUnsignedShort(i));
+ } else {
+ registerUncompressedStrip(i, tag.getUnsignedLong(i));
+ }
+ }
+ } else {
+ mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
+ }
+ }
+ break;
+ case ExifTag.TAG_STRIP_BYTE_COUNTS:
+ if (isThumbnailRequested()) {
+ if (tag.hasValue()) {
+ mStripSizeTag = tag;
+ }
+ }
+ break;
+ }
+ }
+
+ private void readFullTagValue(ExifTag tag) throws IOException {
+ switch(tag.getDataType()) {
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ case ExifTag.TYPE_UNDEFINED:
+ {
+ byte buf[] = new byte[tag.getComponentCount()];
+ read(buf);
+ tag.setValue(buf);
+ }
+ break;
+ case ExifTag.TYPE_ASCII:
+ tag.setValue(readString(tag.getComponentCount()));
+ break;
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ {
+ long value[] = new long[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedLong();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ {
+ Rational value[] = new Rational[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedRational();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ {
+ int value[] = new int[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedShort();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ {
+ int value[] = new int[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readLong();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ {
+ Rational value[] = new Rational[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readRational();
+ }
+ tag.setValue(value);
+ }
+ break;
+ }
+ }
+
+ private void parseTiffHeader() throws IOException,
+ ExifInvalidFormatException {
+ short byteOrder = mTiffStream.readShort();
+ ByteOrder order;
+ if (LITTLE_ENDIAN_TAG == byteOrder) {
+ mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ } else if (BIG_ENDIAN_TAG == byteOrder) {
+ mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ } else {
+ throw new ExifInvalidFormatException("Invalid TIFF header");
+ }
+
+ if (mTiffStream.readShort() != TIFF_HEADER_TAIL) {
+ throw new ExifInvalidFormatException("Invalid TIFF header");
+ }
+ }
+
+ private boolean seekTiffData(InputStream inputStream) throws IOException,
+ ExifInvalidFormatException {
+ DataInputStream dataStream = new DataInputStream(inputStream);
+
+ // SOI and APP1
+ if (dataStream.readShort() != JpegHeader.SOI) {
+ throw new ExifInvalidFormatException("Invalid JPEG format");
+ }
+
+ short marker = dataStream.readShort();
+ while(marker != JpegHeader.APP1 && marker != JpegHeader.EOI
+ && !JpegHeader.isSofMarker(marker)) {
+ int length = dataStream.readUnsignedShort();
+ if ((length - 2) != dataStream.skip(length - 2)) {
+ throw new EOFException();
+ }
+ marker = dataStream.readShort();
+ }
+
+ if (marker != JpegHeader.APP1) return false; // No APP1 segment
+
+ // APP1 length, it's not used for us
+ dataStream.readShort();
+
+ // Exif header
+ return (dataStream.readInt() == EXIF_HEADER
+ && dataStream.readShort() == EXIF_HEADER_TAIL);
+ }
+
+ /**
+ * Reads bytes from the InputStream.
+ */
+ public int read(byte[] buffer, int offset, int length) throws IOException {
+ return mTiffStream.read(buffer, offset, length);
+ }
+
+ /**
+ * Equivalent to read(buffer, 0, buffer.length).
+ */
+ public int read(byte[] buffer) throws IOException {
+ return mTiffStream.read(buffer);
+ }
+
+ /**
+ * Reads a String from the InputStream with UTF8 charset.
+ * This is used for reading values of type {@link ExifTag#TYPE_ASCII}.
+ */
+ public String readString(int n) throws IOException {
+ if (n > 0) {
+ byte[] buf = new byte[n];
+ mTiffStream.readOrThrow(buf);
+ return new String(buf, 0, n - 1, "UTF8");
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Reads a String from the InputStream with the given charset.
+ * This is used for reading values of type {@link ExifTag#TYPE_ASCII}.
+ */
+ public String readString(int n, Charset charset) throws IOException {
+ byte[] buf = new byte[n];
+ mTiffStream.readOrThrow(buf);
+ return new String(buf, 0, n - 1, charset);
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the InputStream.
+ */
+ public int readUnsignedShort() throws IOException {
+ return mTiffStream.readShort() & 0xffff;
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the InputStream.
+ */
+ public long readUnsignedLong() throws IOException {
+ return readLong() & 0xffffffffL;
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the InputStream.
+ */
+ public Rational readUnsignedRational() throws IOException {
+ long nomi = readUnsignedLong();
+ long denomi = readUnsignedLong();
+ return new Rational(nomi, denomi);
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
+ */
+ public int readLong() throws IOException {
+ return mTiffStream.readInt();
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
+ */
+ public Rational readRational() throws IOException {
+ int nomi = readLong();
+ int denomi = readLong();
+ return new Rational(nomi, denomi);
+ }
+
+ private static class ImageEvent {
+ int stripIndex;
+ int type;
+ ImageEvent(int type) {
+ this.stripIndex = 0;
+ this.type = type;
+ }
+ ImageEvent(int type, int stripIndex) {
+ this.type = type;
+ this.stripIndex = stripIndex;
+ }
+ }
+
+ private static class IfdEvent {
+ int ifd;
+ boolean isRequested;
+ IfdEvent(int ifd, boolean isInterestedIfd) {
+ this.ifd = ifd;
+ this.isRequested = isInterestedIfd;
+ }
+ }
+
+ private static class ExifTagEvent {
+ ExifTag tag;
+ boolean isRequested;
+ ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
+ this.tag = tag;
+ this.isRequested = isRequireByUser;
+ }
+ }
+
+ /**
+ * Gets the byte order of the current InputStream.
+ */
+ public ByteOrder getByteOrder() {
+ return mTiffStream.getByteOrder();
+ }
+}
\ No newline at end of file
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8083b2dd3343d6a088b208e8745d624d549879f
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class reads the EXIF header of a JPEG file and stores it in {@link ExifData}.
+ */
+public class ExifReader {
+ /**
+ * Parses the inputStream and and returns the EXIF data in an {@link ExifData}.
+ * @throws ExifInvalidFormatException
+ * @throws IOException
+ */
+ public ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
+ IOException {
+ ExifParser parser = ExifParser.parse(inputStream);
+ ExifData exifData = new ExifData(parser.getByteOrder());
+
+ int event = parser.next();
+ while (event != ExifParser.EVENT_END) {
+ switch (event) {
+ case ExifParser.EVENT_START_OF_IFD:
+ exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
+ break;
+ case ExifParser.EVENT_NEW_TAG:
+ ExifTag tag = parser.getTag();
+ if (!tag.hasValue()) {
+ parser.registerForTagValue(tag);
+ } else {
+ exifData.getIfdData(tag.getIfd()).setTag(tag);
+ }
+ break;
+ case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ tag = parser.getTag();
+ if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+ byte[] buf = new byte[tag.getComponentCount()];
+ parser.read(buf);
+ tag.setValue(buf);
+ }
+ exifData.getIfdData(tag.getIfd()).setTag(tag);
+ break;
+ case ExifParser.EVENT_COMPRESSED_IMAGE:
+ byte buf[] = new byte[parser.getCompressedImageSize()];
+ parser.read(buf);
+ exifData.setCompressedThumbnail(buf);
+ break;
+ case ExifParser.EVENT_UNCOMPRESSED_STRIP:
+ buf = new byte[parser.getStripSize()];
+ parser.read(buf);
+ exifData.setStripBytes(parser.getStripIndex(), buf);
+ break;
+ }
+ event = parser.next();
+ }
+ return exifData;
+ }
+}
\ No newline at end of file
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
new file mode 100644
index 0000000000000000000000000000000000000000..49cb6edbc7c559aa364de3038af2039440a810df
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
@@ -0,0 +1,1441 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+import android.util.SparseArray;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * This class stores information of an EXIF tag.
+ * @see ExifParser
+ * @see ExifReader
+ * @see IfdData
+ * @see ExifData
+ */
+public class ExifTag {
+ // Tiff Tags
+ public static final short TAG_IMAGE_WIDTH = 0x100;
+ /*
+ * The height of the image.
+ */
+ public static final short TAG_IMAGE_LENGTH = 0x101;
+ public static final short TAG_BITS_PER_SAMPLE = 0x102;
+ public static final short TAG_COMPRESSION = 0x103;
+ public static final short TAG_PHOTOMETRIC_INTERPRETATION = 0x106;
+ public static final short TAG_IMAGE_DESCRIPTION = 0x10E;
+ public static final short TAG_MAKE = 0x10F;
+ public static final short TAG_MODEL = 0x110;
+ public static final short TAG_STRIP_OFFSETS = 0x111;
+ public static final short TAG_ORIENTATION = 0x112;
+ public static final short TAG_SAMPLES_PER_PIXEL = 0x115;
+ public static final short TAG_ROWS_PER_STRIP = 0x116;
+ public static final short TAG_STRIP_BYTE_COUNTS = 0x117;
+ public static final short TAG_X_RESOLUTION = 0x11A;
+ public static final short TAG_Y_RESOLUTION = 0x11B;
+ public static final short TAG_PLANAR_CONFIGURATION = 0x11C;
+ public static final short TAG_RESOLUTION_UNIT = 0x128;
+ public static final short TAG_TRANSFER_FUNCTION = 0x12D;
+ public static final short TAG_SOFTWARE = 0x131;
+ public static final short TAG_DATE_TIME = 0x132;
+ public static final short TAG_ARTIST = 0x13B;
+ public static final short TAG_WHITE_POINT = 0x13E;
+ public static final short TAG_PRIMARY_CHROMATICITIES = 0x13F;
+ public static final short TAG_JPEG_INTERCHANGE_FORMAT = 0x201;
+ public static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 0x202;
+ public static final short TAG_Y_CB_CR_COEFFICIENTS = 0x211;
+ public static final short TAG_Y_CB_CR_SUB_SAMPLING = 0x212;
+ public static final short TAG_Y_CB_CR_POSITIONING = 0x213;
+ public static final short TAG_REFERENCE_BLACK_WHITE = 0x214;
+ public static final short TAG_COPYRIGHT = (short) 0x8298;
+ public static final short TAG_EXIF_IFD = (short) 0x8769;
+ public static final short TAG_GPS_IFD = (short) 0x8825;
+
+ // Exif Tags
+ public static final short TAG_EXPOSURE_TIME = (short) 0x829A;
+ public static final short TAG_F_NUMBER = (short) 0x829D;
+ public static final short TAG_EXPOSURE_PROGRAM = (short) 0x8822;
+ public static final short TAG_SPECTRAL_SENSITIVITY = (short) 0x8824;
+ public static final short TAG_ISO_SPEED_RATINGS = (short) 0x8827;
+ public static final short TAG_OECF = (short) 0x8828;
+ public static final short TAG_EXIF_VERSION = (short) 0x9000;
+ public static final short TAG_DATE_TIME_ORIGINAL = (short) 0x9003;
+ public static final short TAG_DATE_TIME_DIGITIZED = (short) 0x9004;
+ public static final short TAG_COMPONENTS_CONFIGURATION = (short) 0x9101;
+ public static final short TAG_COMPRESSED_BITS_PER_PIXEL = (short) 0x9102;
+ public static final short TAG_SHUTTER_SPEED_VALUE = (short) 0x9201;
+ public static final short TAG_APERTURE_VALUE = (short) 0x9202;
+ public static final short TAG_BRIGHTNESS_VALUE = (short) 0x9203;
+ public static final short TAG_EXPOSURE_BIAS_VALUE = (short) 0x9204;
+ public static final short TAG_MAX_APERTURE_VALUE = (short) 0x9205;
+ public static final short TAG_SUBJECT_DISTANCE = (short) 0x9206;
+ public static final short TAG_METERING_MODE = (short) 0x9207;
+ public static final short TAG_LIGHT_SOURCE = (short) 0x9208;
+ public static final short TAG_FLASH = (short) 0x9209;
+ public static final short TAG_FOCAL_LENGTH = (short) 0x920A;
+ public static final short TAG_SUBJECT_AREA = (short) 0x9214;
+ public static final short TAG_MAKER_NOTE = (short) 0x927C;
+ public static final short TAG_USER_COMMENT = (short) 0x9286;
+ public static final short TAG_SUB_SEC_TIME = (short) 0x9290;
+ public static final short TAG_SUB_SEC_TIME_ORIGINAL = (short) 0x9291;
+ public static final short TAG_SUB_SEC_TIME_DIGITIZED = (short) 0x9292;
+ public static final short TAG_FLASHPIX_VERSION = (short) 0xA000;
+ public static final short TAG_COLOR_SPACE = (short) 0xA001;
+ public static final short TAG_PIXEL_X_DIMENSION = (short) 0xA002;
+ public static final short TAG_PIXEL_Y_DIMENSION = (short) 0xA003;
+ public static final short TAG_RELATED_SOUND_FILE = (short) 0xA004;
+ public static final short TAG_INTEROPERABILITY_IFD = (short) 0xA005;
+ public static final short TAG_FLASH_ENERGY = (short) 0xA20B;
+ public static final short TAG_SPATIAL_FREQUENCY_RESPONSE = (short) 0xA20C;
+ public static final short TAG_FOCAL_PLANE_X_RESOLUTION = (short) 0xA20E;
+ public static final short TAG_FOCAL_PLANE_Y_RESOLUTION = (short) 0xA20F;
+ public static final short TAG_FOCAL_PLANE_RESOLUTION_UNIT = (short) 0xA210;
+ public static final short TAG_SUBJECT_LOCATION = (short) 0xA214;
+ public static final short TAG_EXPOSURE_INDEX = (short) 0xA215;
+ public static final short TAG_SENSING_METHOD = (short) 0xA217;
+ public static final short TAG_FILE_SOURCE = (short) 0xA300;
+ public static final short TAG_SCENE_TYPE = (short) 0xA301;
+ public static final short TAG_CFA_PATTERN = (short) 0xA302;
+ public static final short TAG_CUSTOM_RENDERED = (short) 0xA401;
+ public static final short TAG_EXPOSURE_MODE = (short) 0xA402;
+ public static final short TAG_WHITE_BALANCE = (short) 0xA403;
+ public static final short TAG_DIGITAL_ZOOM_RATIO = (short) 0xA404;
+ public static final short TAG_FOCAL_LENGTH_IN_35_MM_FILE = (short) 0xA405;
+ public static final short TAG_SCENE_CAPTURE_TYPE = (short) 0xA406;
+ public static final short TAG_GAIN_CONTROL = (short) 0xA407;
+ public static final short TAG_CONTRAST = (short) 0xA408;
+ public static final short TAG_SATURATION = (short) 0xA409;
+ public static final short TAG_SHARPNESS = (short) 0xA40A;
+ public static final short TAG_DEVICE_SETTING_DESCRIPTION = (short) 0xA40B;
+ public static final short TAG_SUBJECT_DISTANCE_RANGE = (short) 0xA40C;
+ public static final short TAG_IMAGE_UNIQUE_ID = (short) 0xA420;
+
+ // GPS tags
+ public static final short TAG_GPS_VERSION_ID = 0;
+ public static final short TAG_GPS_LATITUDE_REF = 1;
+ public static final short TAG_GPS_LATITUDE = 2;
+ public static final short TAG_GPS_LONGITUDE_REF = 3;
+ public static final short TAG_GPS_LONGITUDE = 4;
+ public static final short TAG_GPS_ALTITUDE_REF = 5;
+ public static final short TAG_GPS_ALTITUDE = 6;
+ public static final short TAG_GPS_TIME_STAMP = 7;
+ public static final short TAG_GPS_SATTELLITES = 8;
+ public static final short TAG_GPS_STATUS = 9;
+ public static final short TAG_GPS_MEASURE_MODE = 10;
+ public static final short TAG_GPS_DOP = 11;
+ public static final short TAG_GPS_SPEED_REF = 12;
+ public static final short TAG_GPS_SPEED = 13;
+ public static final short TAG_GPS_TRACK_REF = 14;
+ public static final short TAG_GPS_TRACK = 15;
+ public static final short TAG_GPS_IMG_DIRECTION_REF = 16;
+ public static final short TAG_GPS_IMG_DIRECTION = 17;
+ public static final short TAG_GPS_MAP_DATUM = 18;
+ public static final short TAG_GPS_DEST_LATITUDE_REF = 19;
+ public static final short TAG_GPS_DEST_LATITUDE = 20;
+ public static final short TAG_GPS_DEST_LONGITUDE_REF = 21;
+ public static final short TAG_GPS_DEST_LONGITUDE = 22;
+ public static final short TAG_GPS_DEST_BEARING_REF = 23;
+ public static final short TAG_GPS_DEST_BEARING = 24;
+ public static final short TAG_GPS_DEST_DISTANCE_REF = 25;
+ public static final short TAG_GPS_DEST_DISTANCE = 26;
+ public static final short TAG_GPS_PROCESSING_METHOD = 27;
+ public static final short TAG_GPS_AREA_INFORMATION = 28;
+ public static final short TAG_GPS_DATA_STAMP = 29;
+ public static final short TAG_GPS_DIFFERENTIAL = 30;
+
+ // Interoperability tag
+ public static final short TAG_INTEROPERABILITY_INDEX = 1;
+
+ /**
+ * Constants for {@link #TAG_ORIENTATION}
+ */
+ public static interface Orientation {
+ public static final short TOP_LEFT = 1;
+ public static final short TOP_RIGHT = 2;
+ public static final short BOTTOM_LEFT = 3;
+ public static final short BOTTOM_RIGHT = 4;
+ public static final short LEFT_TOP = 5;
+ public static final short RIGHT_TOP = 6;
+ public static final short LEFT_BOTTOM = 7;
+ public static final short RIGHT_BOTTOM = 8;
+ }
+
+ /**
+ * Constants for {@link #TAG_Y_CB_CR_POSITIONING}
+ */
+ public static interface YCbCrPositioning {
+ public static final short CENTERED = 1;
+ public static final short CO_SITED = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_COMPRESSION}
+ */
+ public static interface Compression {
+ public static final short UNCOMPRESSION = 1;
+ public static final short JPEG = 6;
+ }
+
+ /**
+ * Constants for {@link #TAG_RESOLUTION_UNIT}
+ */
+ public static interface ResolutionUnit {
+ public static final short INCHES = 2;
+ public static final short CENTIMETERS = 3;
+ }
+
+ /**
+ * Constants for {@link #TAG_PHOTOMETRIC_INTERPRETATION}
+ */
+ public static interface PhotometricInterpretation {
+ public static final short RGB = 2;
+ public static final short YCBCR = 6;
+ }
+
+ /**
+ * Constants for {@link #TAG_PLANAR_CONFIGURATION}
+ */
+ public static interface PlanarConfiguration {
+ public static final short CHUNKY = 1;
+ public static final short PLANAR = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_EXPOSURE_PROGRAM}
+ */
+ public static interface ExposureProgram {
+ public static final short NOT_DEFINED = 0;
+ public static final short MANUAL = 1;
+ public static final short NORMAL_PROGRAM = 2;
+ public static final short APERTURE_PRIORITY = 3;
+ public static final short SHUTTER_PRIORITY = 4;
+ public static final short CREATIVE_PROGRAM = 5;
+ public static final short ACTION_PROGRAM = 6;
+ public static final short PROTRAIT_MODE = 7;
+ public static final short LANDSCAPE_MODE = 8;
+ }
+
+ /**
+ * Constants for {@link #TAG_METERING_MODE}
+ */
+ public static interface MeteringMode {
+ public static final short UNKNOWN = 0;
+ public static final short AVERAGE = 1;
+ public static final short CENTER_WEIGHTED_AVERAGE = 2;
+ public static final short SPOT = 3;
+ public static final short MULTISPOT = 4;
+ public static final short PATTERN = 5;
+ public static final short PARTAIL = 6;
+ public static final short OTHER = 255;
+ }
+
+ /**
+ * Constants for {@link #TAG_FLASH} As the definition in Jeita EXIF 2.2 standard, we can
+ * treat this constant as bitwise flag.
+ *
+ * e.g.
+ *
+ * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED | MODE_AUTO_MODE
+ */
+ public static interface Flash {
+ // LSB
+ public static final short DID_NOT_FIRED = 0;
+ public static final short FIRED = 1;
+ // 1st~2nd bits
+ public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
+ public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
+ public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
+ // 3rd~4th bits
+ public static final short MODE_UNKNOWN = 0 << 3;
+ public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
+ public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
+ public static final short MODE_AUTO_MODE = 3 << 3;
+ // 5th bit
+ public static final short FUNCTION_PRESENT = 0 << 5;
+ public static final short FUNCTION_NO_FUNCTION = 1 << 5;
+ // 6th bit
+ public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
+ public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
+ }
+
+ /**
+ * Constants for {@link #TAG_COLOR_SPACE}
+ */
+ public static interface ColorSpace {
+ public static final short SRGB = 1;
+ public static final short UNCALIBRATED = (short) 0xFFFF;
+ }
+
+ /**
+ * Constants for {@link #TAG_EXPOSURE_MODE}
+ */
+ public static interface ExposureMode {
+ public static final short AUTO_EXPOSURE = 0;
+ public static final short MANUAL_EXPOSURE = 1;
+ public static final short AUTO_BRACKET = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_WHITE_BALANCE}
+ */
+ public static interface WhiteBalance {
+ public static final short AUTO = 0;
+ public static final short MANUAL = 1;
+ }
+
+ /**
+ * Constants for {@link #TAG_SCENE_CAPTURE_TYPE}
+ */
+ public static interface SceneCapture {
+ public static final short STANDARD = 0;
+ public static final short LANDSCAPE = 1;
+ public static final short PROTRAIT = 2;
+ public static final short NIGHT_SCENE = 3;
+ }
+
+ /**
+ * Constants for {@link #TAG_COMPONENTS_CONFIGURATION}
+ */
+ public static interface ComponentsConfiguration {
+ public static final short NOT_EXIST = 0;
+ public static final short Y = 1;
+ public static final short CB = 2;
+ public static final short CR = 3;
+ public static final short R = 4;
+ public static final short G = 5;
+ public static final short B = 6;
+ }
+
+ /**
+ * Constants for {@link #TAG_LIGHT_SOURCE}
+ */
+ public static interface LightSource {
+ public static final short UNKNOWN = 0;
+ public static final short DAYLIGHT = 1;
+ public static final short FLUORESCENT = 2;
+ public static final short TUNGSTEN = 3;
+ public static final short FLASH = 4;
+ public static final short FINE_WEATHER = 9;
+ public static final short CLOUDY_WEATHER = 10;
+ public static final short SHADE = 11;
+ public static final short DAYLIGHT_FLUORESCENT = 12;
+ public static final short DAY_WHITE_FLUORESCENT = 13;
+ public static final short COOL_WHITE_FLUORESCENT = 14;
+ public static final short WHITE_FLUORESCENT = 15;
+ public static final short STANDARD_LIGHT_A = 17;
+ public static final short STANDARD_LIGHT_B = 18;
+ public static final short STANDARD_LIGHT_C = 19;
+ public static final short D55 = 20;
+ public static final short D65 = 21;
+ public static final short D75 = 22;
+ public static final short D50 = 23;
+ public static final short ISO_STUDIO_TUNGSTEN = 24;
+ public static final short OTHER = 255;
+ }
+
+ /**
+ * Constants for {@link #TAG_SENSING_METHOD}
+ */
+ public static interface SensingMethod {
+ public static final short NOT_DEFINED = 1;
+ public static final short ONE_CHIP_COLOR = 2;
+ public static final short TWO_CHIP_COLOR = 3;
+ public static final short THREE_CHIP_COLOR = 4;
+ public static final short COLOR_SEQUENTIAL_AREA = 5;
+ public static final short TRILINEAR = 7;
+ public static final short COLOR_SEQUENTIAL_LINEAR = 8;
+ }
+
+ /**
+ * Constants for {@link #TAG_FILE_SOURCE}
+ */
+ public static interface FileSource {
+ public static final short DSC = 3;
+ }
+
+ /**
+ * Constants for {@link #TAG_SCENE_TYPE}
+ */
+ public static interface SceneType {
+ public static final short DIRECT_PHOTOGRAPHED = 1;
+ }
+
+ /**
+ * Constants for {@link #TAG_GAIN_CONTROL}
+ */
+ public static interface GainControl {
+ public static final short NONE = 0;
+ public static final short LOW_UP = 1;
+ public static final short HIGH_UP = 2;
+ public static final short LOW_DOWN = 3;
+ public static final short HIGH_DOWN = 4;
+ }
+
+ /**
+ * Constants for {@link #TAG_CONTRAST}
+ */
+ public static interface Contrast {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_SATURATION}
+ */
+ public static interface Saturation {
+ public static final short NORMAL = 0;
+ public static final short LOW = 1;
+ public static final short HIGH = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_SHARPNESS}
+ */
+ public static interface Sharpness {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_SUBJECT_DISTANCE}
+ */
+ public static interface SubjectDistance {
+ public static final short UNKNOWN = 0;
+ public static final short MACRO = 1;
+ public static final short CLOSE_VIEW = 2;
+ public static final short DISTANT_VIEW = 3;
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_LATITUDE_REF}, {@link #TAG_GPS_DEST_LATITUDE_REF}
+ */
+ public static interface GpsLatitudeRef {
+ public static final String NORTH = "N";
+ public static final String SOUTH = "S";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_LONGITUDE_REF}, {@link #TAG_GPS_DEST_LONGITUDE_REF}
+ */
+ public static interface GpsLongitudeRef {
+ public static final String EAST = "E";
+ public static final String WEST = "W";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_ALTITUDE_REF}
+ */
+ public static interface GpsAltitudeRef {
+ public static final short SEA_LEVEL = 0;
+ public static final short SEA_LEVEL_NEGATIVE = 1;
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_STATUS}
+ */
+ public static interface GpsStatus {
+ public static final String IN_PROGRESS = "A";
+ public static final String INTEROPERABILITY = "V";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_MEASURE_MODE}
+ */
+ public static interface GpsMeasureMode {
+ public static final String MODE_2_DIMENSIONAL = "2";
+ public static final String MODE_3_DIMENSIONAL = "3";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_SPEED_REF}, {@link #TAG_GPS_DEST_DISTANCE_REF}
+ */
+ public static interface GpsSpeedRef {
+ public static final String KILOMETERS = "K";
+ public static final String MILES = "M";
+ public static final String KNOTS = "N";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_TRACK_REF}, {@link #TAG_GPS_IMG_DIRECTION_REF},
+ * {@link #TAG_GPS_DEST_BEARING_REF}
+ */
+ public static interface GpsTrackRef {
+ public static final String TRUE_DIRECTION = "T";
+ public static final String MAGNETIC_DIRECTION = "M";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_DIFFERENTIAL}
+ */
+ public static interface GpsDifferential {
+ public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
+ public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
+ }
+
+ /**
+ * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
+ */
+ public static final short TYPE_UNSIGNED_BYTE = 1;
+ /**
+ * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit ASCII code.
+ * The final byte is terminated with NULL.
+ */
+ public static final short TYPE_ASCII = 2;
+ /**
+ * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
+ */
+ public static final short TYPE_UNSIGNED_SHORT = 3;
+ /**
+ * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
+ */
+ public static final short TYPE_UNSIGNED_LONG = 4;
+ /**
+ * The RATIONAL type of EXIF standard. It consists of two LONGs. The first one is the numerator
+ * and the second one expresses the denominator.
+ */
+ public static final short TYPE_UNSIGNED_RATIONAL = 5;
+ /**
+ * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any value
+ * depending on the field definition.
+ */
+ public static final short TYPE_UNDEFINED = 7;
+ /**
+ * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
+ * (2's complement notation).
+ */
+ public static final short TYPE_LONG = 9;
+ /**
+ * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first one is the
+ * numerator and the second one is the denominator.
+ */
+ public static final short TYPE_RATIONAL = 10;
+
+ private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+ static {
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8;
+ TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_LONG] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
+ }
+
+ /**
+ * Gets the element size of the given data type.
+ *
+ * @see #TYPE_ASCII
+ * @see #TYPE_LONG
+ * @see #TYPE_RATIONAL
+ * @see #TYPE_UNDEFINED
+ * @see #TYPE_UNSIGNED_BYTE
+ * @see #TYPE_UNSIGNED_LONG
+ * @see #TYPE_UNSIGNED_RATIONAL
+ * @see #TYPE_UNSIGNED_SHORT
+ */
+ public static int getElementSize(short type) {
+ return TYPE_TO_SIZE_MAP[type];
+ }
+
+ private static volatile SparseArray sTagInfo = null;
+ private static volatile SparseArray sInteroperTagInfo = null;
+ private static final int SIZE_UNDEFINED = 0;
+
+ private static SparseArray getTagInfo() {
+ if (sTagInfo == null) {
+ synchronized(ExifTag.class) {
+ if (sTagInfo == null) {
+ sTagInfo = new SparseArray();
+ initTagInfo();
+ }
+ }
+ }
+ return sTagInfo;
+ }
+
+ private static SparseArray getInteroperTagInfo() {
+ if (sInteroperTagInfo == null) {
+ synchronized(ExifTag.class) {
+ if (sInteroperTagInfo == null) {
+ sInteroperTagInfo = new SparseArray();
+ sInteroperTagInfo.put(TAG_INTEROPERABILITY_INDEX,
+ (IfdId.TYPE_IFD_INTEROPERABILITY << 24)
+ | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ }
+ }
+ }
+ return sInteroperTagInfo;
+ }
+
+ private static void initTagInfo() {
+ /**
+ * We put tag information in a 4-bytes integer. The first byte is the
+ * IFD of the tag, and the second byte is the default data type. The
+ * last two byte are a short value indicating the component count of this
+ * tag.
+ */
+ sTagInfo.put(TAG_MAKE,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_IMAGE_WIDTH,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
+ sTagInfo.put(TAG_IMAGE_LENGTH,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
+ sTagInfo.put(TAG_BITS_PER_SAMPLE,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3);
+ sTagInfo.put(TAG_COMPRESSION,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_PHOTOMETRIC_INTERPRETATION,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_ORIENTATION, (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_SAMPLES_PER_PIXEL,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_PLANAR_CONFIGURATION,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_Y_CB_CR_SUB_SAMPLING,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 2);
+ sTagInfo.put(TAG_Y_CB_CR_POSITIONING,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_X_RESOLUTION,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_Y_RESOLUTION,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_RESOLUTION_UNIT,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_STRIP_OFFSETS,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_ROWS_PER_STRIP,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
+ sTagInfo.put(TAG_STRIP_BYTE_COUNTS,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
+ sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
+ sTagInfo.put(TAG_TRANSFER_FUNCTION,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
+ sTagInfo.put(TAG_WHITE_POINT,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 2);
+ sTagInfo.put(TAG_PRIMARY_CHROMATICITIES,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6);
+ sTagInfo.put(TAG_Y_CB_CR_COEFFICIENTS,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3);
+ sTagInfo.put(TAG_REFERENCE_BLACK_WHITE,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6);
+ sTagInfo.put(TAG_DATE_TIME,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | 20);
+ sTagInfo.put(TAG_IMAGE_DESCRIPTION,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_MAKE,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_MODEL,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_SOFTWARE,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_ARTIST,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_COPYRIGHT,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_EXIF_IFD,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
+ sTagInfo.put(TAG_GPS_IFD,
+ (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
+
+ // EXIF TAG
+ sTagInfo.put(TAG_EXIF_VERSION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4);
+ sTagInfo.put(TAG_FLASHPIX_VERSION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4);
+ sTagInfo.put(TAG_COLOR_SPACE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_COMPONENTS_CONFIGURATION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4);
+ sTagInfo.put(TAG_COMPRESSED_BITS_PER_PIXEL,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_PIXEL_X_DIMENSION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
+ sTagInfo.put(TAG_PIXEL_Y_DIMENSION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
+ sTagInfo.put(TAG_MAKER_NOTE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_USER_COMMENT,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_RELATED_SOUND_FILE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 13);
+ sTagInfo.put(TAG_DATE_TIME_ORIGINAL,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20);
+ sTagInfo.put(TAG_DATE_TIME_DIGITIZED,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20);
+ sTagInfo.put(TAG_SUB_SEC_TIME,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_SUB_SEC_TIME_ORIGINAL,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_SUB_SEC_TIME_DIGITIZED,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_IMAGE_UNIQUE_ID,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 33);
+ sTagInfo.put(TAG_EXPOSURE_TIME,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_F_NUMBER,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_EXPOSURE_PROGRAM,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_SPECTRAL_SENSITIVITY,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_ISO_SPEED_RATINGS,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_OECF,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_SHUTTER_SPEED_VALUE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_APERTURE_VALUE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_BRIGHTNESS_VALUE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_EXPOSURE_BIAS_VALUE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_MAX_APERTURE_VALUE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_SUBJECT_DISTANCE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_METERING_MODE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_LIGHT_SOURCE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_FLASH,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_FOCAL_LENGTH,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_SUBJECT_AREA,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_FLASH_ENERGY,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_SPATIAL_FREQUENCY_RESPONSE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_FOCAL_PLANE_X_RESOLUTION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_FOCAL_PLANE_Y_RESOLUTION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_FOCAL_PLANE_RESOLUTION_UNIT,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_SUBJECT_LOCATION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 2);
+ sTagInfo.put(TAG_EXPOSURE_INDEX,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_SENSING_METHOD,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_FILE_SOURCE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1);
+ sTagInfo.put(TAG_SCENE_TYPE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1);
+ sTagInfo.put(TAG_CFA_PATTERN,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_CUSTOM_RENDERED,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_EXPOSURE_MODE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_WHITE_BALANCE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_DIGITAL_ZOOM_RATIO,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_FOCAL_LENGTH_IN_35_MM_FILE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_SCENE_CAPTURE_TYPE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_GAIN_CONTROL,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_CONTRAST,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_SATURATION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_SHARPNESS,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ sTagInfo.put(TAG_DEVICE_SETTING_DESCRIPTION,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_SUBJECT_DISTANCE_RANGE,
+ (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
+ // GPS tag
+ sTagInfo.put(TAG_GPS_VERSION_ID,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 4);
+ sTagInfo.put(TAG_GPS_LATITUDE_REF,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_LONGITUDE_REF,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_LATITUDE,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3);
+ sTagInfo.put(TAG_GPS_LONGITUDE,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3);
+ sTagInfo.put(TAG_GPS_ALTITUDE_REF,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 1);
+ sTagInfo.put(TAG_GPS_ALTITUDE,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_GPS_TIME_STAMP,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3);
+ sTagInfo.put(TAG_GPS_SATTELLITES,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_GPS_STATUS,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_MEASURE_MODE,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_DOP,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_GPS_SPEED_REF,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_SPEED,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_GPS_TRACK_REF,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_TRACK,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_GPS_IMG_DIRECTION_REF,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_IMG_DIRECTION,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_GPS_MAP_DATUM,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_GPS_DEST_LATITUDE_REF,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_DEST_LATITUDE,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_GPS_DEST_BEARING_REF,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_DEST_BEARING,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_GPS_DEST_DISTANCE_REF,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
+ sTagInfo.put(TAG_GPS_DEST_DISTANCE,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ sTagInfo.put(TAG_GPS_PROCESSING_METHOD,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_GPS_AREA_INFORMATION,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
+ sTagInfo.put(TAG_GPS_DATA_STAMP,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 11);
+ sTagInfo.put(TAG_GPS_DIFFERENTIAL,
+ (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_SHORT << 16 | 11);
+ }
+
+ private final short mTagId;
+ private final short mDataType;
+ private final int mIfd;
+ private final boolean mComponentCountDefined;
+ private int mComponentCount;
+ private Object mValue;
+ private int mOffset;
+
+ static private short getTypeFromInfo(int info) {
+ return (short) ((info >> 16) & 0xff);
+ }
+
+ static private int getComponentCountFromInfo(int info) {
+ return info & 0xffff;
+ }
+
+ static private int getIfdIdFromInfo(int info) {
+ return (info >> 24) & 0xff;
+ }
+
+ static private boolean getComponentCountDefined(short tagId, int ifd) {
+ Integer info = (ifd == IfdId.TYPE_IFD_INTEROPERABILITY) ?
+ getInteroperTagInfo().get(tagId) : getTagInfo().get(tagId);
+ if (info == null) return false;
+ return getComponentCountFromInfo(info) != SIZE_UNDEFINED;
+ }
+
+ static int getIfdIdFromTagId(short tagId) {
+ Integer info = getTagInfo().get(tagId);
+ if (info == null) {
+ throw new IllegalArgumentException("Unknown Tag ID: " + tagId);
+ }
+ return getIfdIdFromInfo(info);
+ }
+
+ /**
+ * Create a tag with given ID. For tags related to interoperability and thumbnail, call
+ * {@link #buildInteroperabilityTag(short)} and {@link #buildThumbnailTag(short)} respectively.
+ * @exception IllegalArgumentException If the ID is invalid.
+ */
+ static public ExifTag buildTag(short tagId) {
+ Integer info = getTagInfo().get(tagId);
+ if (info == null) {
+ throw new IllegalArgumentException("Unknown Tag ID: " + tagId);
+ }
+ return new ExifTag(tagId, getTypeFromInfo(info),
+ getComponentCountFromInfo(info),
+ getIfdIdFromInfo(info));
+ }
+
+ /**
+ * Create a tag related to thumbnail with given ID.
+ * @exception IllegalArgumentException If the ID is invalid.
+ */
+ static public ExifTag buildThumbnailTag(short tagId) {
+ Integer info = getTagInfo().get(tagId);
+ if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_0) {
+ throw new IllegalArgumentException("Unknown Thumnail Tag ID: " + tagId);
+ }
+ return new ExifTag(tagId, getTypeFromInfo(info),
+ getComponentCountFromInfo(info),
+ IfdId.TYPE_IFD_1);
+ }
+
+ /**
+ * Create a tag related to interoperability with given ID.
+ * @exception IllegalArgumentException If the ID is invalid.
+ */
+ static public ExifTag buildInteroperabilityTag(short tagId) {
+ Integer info = getInteroperTagInfo().get(tagId);
+ if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_INTEROPERABILITY) {
+ throw new RuntimeException("Unknown Interoperability Tag ID: " + tagId);
+ }
+ return new ExifTag(tagId, getTypeFromInfo(info),
+ getComponentCountFromInfo(info),
+ IfdId.TYPE_IFD_INTEROPERABILITY);
+ }
+
+ ExifTag(short tagId, short type, int componentCount, int ifd) {
+ mTagId = tagId;
+ mDataType = type;
+ mComponentCount = componentCount;
+ mComponentCountDefined = getComponentCountDefined(tagId, ifd);
+ mIfd = ifd;
+ }
+
+ /**
+ * Returns the ID of the IFD this tag belongs to.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ public int getIfd() {
+ return mIfd;
+ }
+
+ /**
+ * Gets the ID of this tag.
+ */
+ public short getTagId() {
+ return mTagId;
+ }
+
+ /**
+ * Gets the data type of this tag
+ *
+ * @see #TYPE_ASCII
+ * @see #TYPE_LONG
+ * @see #TYPE_RATIONAL
+ * @see #TYPE_UNDEFINED
+ * @see #TYPE_UNSIGNED_BYTE
+ * @see #TYPE_UNSIGNED_LONG
+ * @see #TYPE_UNSIGNED_RATIONAL
+ * @see #TYPE_UNSIGNED_SHORT
+ */
+ public short getDataType() {
+ return mDataType;
+ }
+
+ /**
+ * Gets the total data size in bytes of the value of this tag.
+ */
+ public int getDataSize() {
+ return getComponentCount() * getElementSize(getDataType());
+ }
+
+ /**
+ * Gets the component count of this tag.
+ */
+ public int getComponentCount() {
+ return mComponentCount;
+ }
+
+ /**
+ * Returns true if this ExifTag contains value; otherwise, this tag will contain an offset value
+ * that links to the area where the actual value is located.
+ *
+ * @see #getOffset()
+ */
+ public boolean hasValue() {
+ return mValue != null;
+ }
+
+ /**
+ * Gets the offset of this tag. This is only valid if this data size > 4 and contains an offset
+ * to the location of the actual value.
+ */
+ public int getOffset() {
+ return mOffset;
+ }
+
+ /**
+ * Sets the offset of this tag.
+ */
+ void setOffset(int offset) {
+ mOffset = offset;
+ }
+
+ private void checkComponentCountOrThrow(int count)
+ throws IllegalArgumentException {
+ if (mComponentCountDefined && (mComponentCount != count)) {
+ throw new IllegalArgumentException("Tag " + mTagId + ": Required "
+ + mComponentCount + " components but was given " + count
+ + " component(s)");
+ }
+ }
+
+ private void throwTypeNotMatchedException(String className)
+ throws IllegalArgumentException {
+ throw new IllegalArgumentException("Tag " + mTagId + ": expect type " +
+ convertTypeToString(mDataType) + " but got " + className);
+ }
+
+ private static String convertTypeToString(short type) {
+ switch (type) {
+ case TYPE_UNSIGNED_BYTE:
+ return "UNSIGNED_BYTE";
+ case TYPE_ASCII:
+ return "ASCII";
+ case TYPE_UNSIGNED_SHORT:
+ return "UNSIGNED_SHORT";
+ case TYPE_UNSIGNED_LONG:
+ return "UNSIGNED_LONG";
+ case TYPE_UNSIGNED_RATIONAL:
+ return "UNSIGNED_RATIONAL";
+ case TYPE_UNDEFINED:
+ return "UNDEFINED";
+ case TYPE_LONG:
+ return "LONG";
+ case TYPE_RATIONAL:
+ return "RATIONAL";
+ default:
+ return "";
+ }
+ }
+
+ private static final int UNSIGNED_SHORT_MAX = 65535;
+ private static final long UNSIGNED_LONG_MAX = 4294967295L;
+ private static final long LONG_MAX = Integer.MAX_VALUE;
+ private static final long LONG_MIN = Integer.MIN_VALUE;
+
+ private void checkOverflowForUnsignedShort(int[] value) {
+ for (int v : value) {
+ if (v > UNSIGNED_SHORT_MAX || v < 0) {
+ throw new IllegalArgumentException(
+ "Tag " + mTagId+ ": Value" + v +
+ " is illegal for type UNSIGNED_SHORT");
+ }
+ }
+ }
+
+ private void checkOverflowForUnsignedLong(long[] value) {
+ for (long v: value) {
+ if (v < 0 || v > UNSIGNED_LONG_MAX) {
+ throw new IllegalArgumentException(
+ "Tag " + mTagId+ ": Value" + v +
+ " is illegal for type UNSIGNED_LONG");
+ }
+ }
+ }
+
+ private void checkOverflowForUnsignedLong(int[] value) {
+ for (int v: value) {
+ if (v < 0) {
+ throw new IllegalArgumentException(
+ "Tag " + mTagId+ ": Value" + v +
+ " is illegal for type UNSIGNED_LONG");
+ }
+ }
+ }
+
+ private void checkOverflowForUnsignedRational(Rational[] value) {
+ for (Rational v: value) {
+ if (v.getNominator() < 0 || v.getDenominator() < 0
+ || v.getNominator() > UNSIGNED_LONG_MAX
+ || v.getDenominator() > UNSIGNED_LONG_MAX) {
+ throw new IllegalArgumentException(
+ "Tag " + mTagId+ ": Value" + v +
+ " is illegal for type UNSIGNED_RATIONAL");
+ }
+ }
+ }
+
+ private void checkOverflowForRational(Rational[] value) {
+ for (Rational v: value) {
+ if (v.getNominator() < LONG_MIN || v.getDenominator() < LONG_MIN
+ || v.getNominator() > LONG_MAX
+ || v.getDenominator() > LONG_MAX) {
+ throw new IllegalArgumentException(
+ "Tag " + mTagId+ ": Value" + v +
+ " is illegal for type RATIONAL");
+ }
+ }
+ }
+
+ /**
+ * Sets integer values into this tag.
+ * @exception IllegalArgumentException for the following situation:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
+ *
The value overflows.
+ *
The value.length does NOT match the definition of component count in
+ * EXIF standard.
+ *
+ */
+ public void setValue(int[] value) {
+ checkComponentCountOrThrow(value.length);
+ if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
+ mDataType != TYPE_UNSIGNED_LONG) {
+ throwTypeNotMatchedException("int");
+ }
+ if (mDataType == TYPE_UNSIGNED_SHORT) {
+ checkOverflowForUnsignedShort(value);
+ } else if (mDataType == TYPE_UNSIGNED_LONG) {
+ checkOverflowForUnsignedLong(value);
+ }
+
+ long[] data = new long[value.length];
+ for (int i = 0; i < value.length; i++) {
+ data[i] = value[i];
+ }
+ mValue = data;
+ mComponentCount = value.length;
+ }
+
+ /**
+ * Sets integer values into this tag.
+ * @exception IllegalArgumentException For the following situation:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
+ *
The value overflows.
+ *
The component count in the definition of EXIF standard is not 1.
+ *
+ */
+ public void setValue(int value) {
+ checkComponentCountOrThrow(1);
+ setValue(new int[] {value});
+ }
+
+ /**
+ * Sets long values into this tag.
+ * @exception IllegalArgumentException For the following situation:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
+ *
The value overflows.
+ *
The value.length does NOT match the definition of component count in
+ * EXIF standard.
+ *
+ */
+ public void setValue(long[] value) {
+ checkComponentCountOrThrow(value.length);
+ if (mDataType != TYPE_UNSIGNED_LONG) {
+ throwTypeNotMatchedException("long");
+ }
+ checkOverflowForUnsignedLong(value);
+ mValue = value;
+ mComponentCount = value.length;
+ }
+
+ /**
+ * Sets long values into this tag.
+ * @exception IllegalArgumentException For the following situation:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
+ *
The value overflows.
+ *
The component count in the definition of EXIF standard is not 1.
+ *
+ */
+ public void setValue(long value) {
+ setValue(new long[] {value});
+ }
+
+ /**
+ * Sets string values into this tag.
+ * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII}
+ * or value.length() + 1 does NOT fit the definition of the component count in the
+ * EXIF standard.
+ */
+ public void setValue(String value) {
+ checkComponentCountOrThrow(value.length() + 1);
+ if (mDataType != TYPE_ASCII) {
+ throwTypeNotMatchedException("String");
+ }
+ mComponentCount = value.length() + 1;
+ mValue = value;
+ }
+
+ /**
+ * Sets Rational values into this tag.
+ * @exception IllegalArgumentException For the following situation:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or
+ * {@link #TYPE_RATIONAL} .
+ *
The value overflows.
+ *
The value.length does NOT match the definition of component count in
+ * EXIF standard.
+ *
+ */
+ public void setValue(Rational[] value) {
+ if (mDataType == TYPE_UNSIGNED_RATIONAL) {
+ checkOverflowForUnsignedRational(value);
+ } else if (mDataType == TYPE_RATIONAL) {
+ checkOverflowForRational(value);
+ } else {
+ throwTypeNotMatchedException("Rational");
+ }
+ checkComponentCountOrThrow(value.length);
+ mValue = value;
+ mComponentCount = value.length;
+ }
+
+ /**
+ * Sets Rational values into this tag.
+ * @exception IllegalArgumentException For the following situation:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or
+ * {@link #TYPE_RATIONAL} .
+ *
The value overflows.
+ *
The component count in the definition of EXIF standard is not 1.
+ *
+ * */
+ public void setValue(Rational value) {
+ setValue(new Rational[] {value});
+ }
+
+ /**
+ * Sets byte values into this tag.
+ * @exception IllegalArgumentException For the following situation:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .
+ *
The length does NOT match the definition of component count in EXIF standard.
+ *
+ * */
+ public void setValue(byte[] value, int offset, int length) {
+ checkComponentCountOrThrow(length);
+ if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
+ throwTypeNotMatchedException("byte");
+ }
+ mValue = new byte[length];
+ System.arraycopy(value, offset, mValue, 0, length);
+ mComponentCount = length;
+ }
+
+ /**
+ * Equivalent to setValue(value, 0, value.length).
+ */
+ public void setValue(byte[] value) {
+ setValue(value, 0, value.length);
+ }
+
+ private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
+
+ /**
+ * Sets a timestamp to this tag. The method converts the timestamp with the format of
+ * "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}.
+ *
+ * @param time the number of milliseconds since Jan. 1, 1970 GMT
+ * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII}
+ * or the component count of this tag is not 20 or undefined
+ */
+ public void setTimeValue(long time) {
+ // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+ synchronized (TIME_FORMAT) {
+ setValue(TIME_FORMAT.format(new Date(time)));
+ }
+ }
+
+ /**
+ * Gets the {@link #TYPE_UNSIGNED_SHORT} data.
+ * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_SHORT}.
+ */
+ public int getUnsignedShort(int index) {
+ if (mDataType != TYPE_UNSIGNED_SHORT) {
+ throw new IllegalArgumentException("Cannot get UNSIGNED_SHORT value from "
+ + convertTypeToString(mDataType));
+ }
+ return (int) (((long[]) mValue) [index]);
+ }
+
+ /**
+ * Gets the {@link #TYPE_LONG} data.
+ * @exception IllegalArgumentException If the type is NOT {@link #TYPE_LONG}.
+ */
+ public int getLong(int index) {
+ if (mDataType != TYPE_LONG) {
+ throw new IllegalArgumentException("Cannot get LONG value from "
+ + convertTypeToString(mDataType));
+ }
+ return (int) (((long[]) mValue) [index]);
+ }
+
+ /**
+ * Gets the {@link #TYPE_UNSIGNED_LONG} data.
+ * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_LONG}.
+ */
+ public long getUnsignedLong(int index) {
+ if (mDataType != TYPE_UNSIGNED_LONG) {
+ throw new IllegalArgumentException("Cannot get UNSIGNED LONG value from "
+ + convertTypeToString(mDataType));
+ }
+ return ((long[]) mValue) [index];
+ }
+
+ /**
+ * Gets the {@link #TYPE_ASCII} data.
+ * @exception IllegalArgumentException If the type is NOT {@link #TYPE_ASCII}.
+ */
+ public String getString() {
+ if (mDataType != TYPE_ASCII) {
+ throw new IllegalArgumentException("Cannot get ASCII value from "
+ + convertTypeToString(mDataType));
+ }
+ return (String) mValue;
+ }
+
+ /**
+ * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
+ * @exception IllegalArgumentException If the type is NOT {@link #TYPE_RATIONAL} or
+ * {@link #TYPE_UNSIGNED_RATIONAL}.
+ */
+ public Rational getRational(int index) {
+ if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
+ throw new IllegalArgumentException("Cannot get RATIONAL value from "
+ + convertTypeToString(mDataType));
+ }
+ return ((Rational[]) mValue) [index];
+ }
+
+ /**
+ * Equivalent to getBytes(buffer, 0, buffer.length).
+ */
+ public void getBytes(byte[] buf) {
+ getBytes(buf, 0, buf.length);
+ }
+
+ /**
+ * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
+ *
+ * @param buf the byte array in which to store the bytes read.
+ * @param offset the initial position in buffer to store the bytes.
+ * @param length the maximum number of bytes to store in buffer. If length > component count,
+ * only the valid bytes will be stored.
+ *
+ * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNDEFINED} or
+ * {@link #TYPE_UNSIGNED_BYTE}.
+ */
+ public void getBytes(byte[] buf, int offset, int length) {
+ if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
+ throw new IllegalArgumentException("Cannot get BYTE value from "
+ + convertTypeToString(mDataType));
+ }
+ System.arraycopy(mValue, 0, buf, offset,
+ (length > mComponentCount) ? mComponentCount : length);
+ }
+
+ /**
+ * Returns a string representation of the value of this tag.
+ */
+ public String valueToString() {
+ StringBuilder sbuilder = new StringBuilder();
+ switch (getDataType()) {
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ byte buf[] = new byte[getComponentCount()];
+ getBytes(buf);
+ for(int i = 0, n = getComponentCount(); i < n; i++) {
+ if(i != 0) sbuilder.append(" ");
+ sbuilder.append(String.format("%02x", buf[i]));
+ }
+ break;
+ case ExifTag.TYPE_ASCII:
+ sbuilder.append(getString());
+ break;
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for(int i = 0, n = getComponentCount(); i < n; i++) {
+ if(i != 0) sbuilder.append(" ");
+ sbuilder.append(getUnsignedLong(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for(int i = 0, n = getComponentCount(); i < n; i++) {
+ Rational r = getRational(i);
+ if(i != 0) sbuilder.append(" ");
+ sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for(int i = 0, n = getComponentCount(); i < n; i++) {
+ if(i != 0) sbuilder.append(" ");
+ sbuilder.append(getUnsignedShort(i));
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ for(int i = 0, n = getComponentCount(); i < n; i++) {
+ if(i != 0) sbuilder.append(" ");
+ sbuilder.append(getLong(i));
+ }
+ break;
+ }
+ return sbuilder.toString();
+ }
+
+ /**
+ * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD},
+ * {@link #TAG_GPS_IFD}, {@link #TAG_JPEG_INTERCHANGE_FORMAT},
+ * {@link #TAG_STRIP_OFFSETS}, {@link #TAG_INTEROPERABILITY_IFD}
+ */
+ static boolean isOffsetTag(short tagId) {
+ return tagId == TAG_EXIF_IFD
+ || tagId == TAG_GPS_IFD
+ || tagId == TAG_JPEG_INTERCHANGE_FORMAT
+ || tagId == TAG_STRIP_OFFSETS
+ || tagId == TAG_INTEROPERABILITY_IFD;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ExifTag) {
+ ExifTag tag = (ExifTag) obj;
+ if (mValue != null) {
+ if (mValue instanceof long[]) {
+ if (!(tag.mValue instanceof long[])) return false;
+ return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
+ } else if (mValue instanceof Rational[]) {
+ if (!(tag.mValue instanceof Rational[])) return false;
+ return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
+ } else if (mValue instanceof byte[]) {
+ if (!(tag.mValue instanceof byte[])) return false;
+ return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
+ } else {
+ return mValue.equals(tag.mValue);
+ }
+ } else {
+ return tag.mValue == null;
+ }
+ }
+ return false;
+ }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/IfdData.java b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java
new file mode 100644
index 0000000000000000000000000000000000000000..78f9173ccb63c660cb6bc60f318c721e5e00d466
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class stores all the tags in an IFD.
+ *
+ * @see ExifData
+ * @see ExifTag
+ */
+class IfdData {
+
+ private final int mIfdId;
+ private final Map mExifTags = new HashMap();
+ private int mOffsetToNextIfd = 0;
+
+ /**
+ * Creates an IfdData with given IFD ID.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ public IfdData(int ifdId) {
+ mIfdId = ifdId;
+ }
+
+ /**
+ * Get a array the contains all {@link ExifTag} in this IFD.
+ */
+ public ExifTag[] getAllTags() {
+ return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
+ }
+
+ /**
+ * Gets the ID of this IFD.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ public int getId() {
+ return mIfdId;
+ }
+
+ /**
+ * Gets the {@link ExifTag} with given tag id. Return null if there is no such tag.
+ */
+ public ExifTag getTag(short tagId) {
+ return mExifTags.get(tagId);
+ }
+
+ /**
+ * Adds or replaces a {@link ExifTag}.
+ */
+ public void setTag(ExifTag tag) {
+ mExifTags.put(tag.getTagId(), tag);
+ }
+
+ /**
+ * Gets the tags count in the IFD.
+ */
+ public int getTagCount() {
+ return mExifTags.size();
+ }
+
+ /**
+ * Sets the offset of next IFD.
+ */
+ void setOffsetToNextIfd(int offset) {
+ mOffsetToNextIfd = offset;
+ }
+
+ /**
+ * Gets the offset of next IFD.
+ */
+ int getOffsetToNextIfd() {
+ return mOffsetToNextIfd;
+ }
+
+ /**
+ * Returns true if all tags in this two IFDs are equal. Note that tags of IFDs offset or
+ * thumbnail offset will be ignored.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof IfdData) {
+ IfdData data = (IfdData) obj;
+ if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
+ ExifTag[] tags = data.getAllTags();
+ for (ExifTag tag: tags) {
+ if (ExifTag.isOffsetTag(tag.getTagId())) continue;
+ ExifTag tag2 = mExifTags.get(tag.getTagId());
+ if (!tag.equals(tag2)) return false;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/gallerycommon/src/com/android/gallery3d/exif/IfdId.java b/gallerycommon/src/com/android/gallery3d/exif/IfdId.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b96343694625dab8ea0188989ac22e166318fd5
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/IfdId.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+package com.android.gallery3d.exif;
+
+public interface IfdId {
+ public static final int TYPE_IFD_0 = 0;
+ public static final int TYPE_IFD_1 = 1;
+ public static final int TYPE_IFD_EXIF = 2;
+ public static final int TYPE_IFD_INTEROPERABILITY = 3;
+ public static final int TYPE_IFD_GPS = 4;
+ /* This is use in ExifData to allocate enough IfdData */
+ static final int TYPE_IFD_COUNT = 5;
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/JpegHeader.java b/gallerycommon/src/com/android/gallery3d/exif/JpegHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3e787eff34b1840096a4c68d7c903afa98a05f7
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/JpegHeader.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+class JpegHeader {
+ public static final short SOI = (short) 0xFFD8;
+ public static final short APP1 = (short) 0xFFE1;
+ public static final short APP0 = (short) 0xFFE0;
+ public static final short EOI = (short) 0xFFD9;
+
+ /**
+ * SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG,
+ * and DAC marker.
+ */
+ public static final short SOF0 = (short) 0xFFC0;
+ public static final short SOF15 = (short) 0xFFCF;
+ public static final short DHT = (short) 0xFFC4;
+ public static final short JPG = (short) 0xFFC8;
+ public static final short DAC = (short) 0xFFCC;
+
+ public static final boolean isSofMarker(short marker) {
+ return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG
+ && marker != DAC;
+ }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f785a889c9b5f5c4438d68bb0eb6db7d3307783
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+class OrderedDataOutputStream extends FilterOutputStream {
+ private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
+
+ public OrderedDataOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ public void setByteOrder(ByteOrder order) {
+ mByteBuffer.order(order);
+ }
+
+ public void writeShort(short value) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putShort(value);
+ out.write(mByteBuffer.array(), 0, 2);
+ }
+
+ public void writeInt(int value) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putInt(value);
+ out.write(mByteBuffer.array());
+ }
+
+ public void writeRational(Rational rational) throws IOException {
+ writeInt((int) rational.getNominator());
+ writeInt((int) rational.getDenominator());
+ }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/Rational.java b/gallerycommon/src/com/android/gallery3d/exif/Rational.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d902626117b467db750b83eb0b58bb34b004318
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/Rational.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+public class Rational {
+
+ private final long mNominator;
+ private final long mDenominator;
+
+ public Rational(long nominator, long denominator) {
+ mNominator = nominator;
+ mDenominator = denominator;
+ }
+
+ public long getNominator() {
+ return mNominator;
+ }
+
+ public long getDenominator() {
+ return mDenominator;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Rational) {
+ Rational data = (Rational) obj;
+ return mNominator == data.mNominator && mDenominator == data.mDenominator;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/gallerycommon/src/com/android/gallery3d/exif/Util.java b/gallerycommon/src/com/android/gallery3d/exif/Util.java
new file mode 100644
index 0000000000000000000000000000000000000000..594d6fc7f843ad73f7d19d7b0881151e0f2c4eda
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/Util.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.io.Closeable;
+
+class Util {
+ public static boolean equals(Object a, Object b) {
+ return (a == b) || (a == null ? false : a.equals(b));
+ }
+
+ public static void closeSilently(Closeable c) {
+ if (c == null) return;
+ try {
+ c.close();
+ } catch (Throwable t) {
+ // do nothing
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/util/Future.java b/gallerycommon/src/com/android/gallery3d/util/Future.java
similarity index 100%
rename from src/com/android/gallery3d/util/Future.java
rename to gallerycommon/src/com/android/gallery3d/util/Future.java
diff --git a/src/com/android/gallery3d/util/FutureListener.java b/gallerycommon/src/com/android/gallery3d/util/FutureListener.java
similarity index 100%
rename from src/com/android/gallery3d/util/FutureListener.java
rename to gallerycommon/src/com/android/gallery3d/util/FutureListener.java
diff --git a/src/com/android/gallery3d/util/PriorityThreadFactory.java b/gallerycommon/src/com/android/gallery3d/util/PriorityThreadFactory.java
similarity index 99%
rename from src/com/android/gallery3d/util/PriorityThreadFactory.java
rename to gallerycommon/src/com/android/gallery3d/util/PriorityThreadFactory.java
index 67b215274f54fcc1bd5c9850cfe587e0c073936d..30d8e4a96da973b8bc3cf522d2f18ea0b0a4993b 100644
--- a/src/com/android/gallery3d/util/PriorityThreadFactory.java
+++ b/gallerycommon/src/com/android/gallery3d/util/PriorityThreadFactory.java
@@ -35,6 +35,7 @@ public class PriorityThreadFactory implements ThreadFactory {
mPriority = priority;
}
+ @Override
public Thread newThread(Runnable r) {
return new Thread(r, mName + '-' + mNumber.getAndIncrement()) {
@Override
diff --git a/src/com/android/gallery3d/util/ThreadPool.java b/gallerycommon/src/com/android/gallery3d/util/ThreadPool.java
similarity index 94%
rename from src/com/android/gallery3d/util/ThreadPool.java
rename to gallerycommon/src/com/android/gallery3d/util/ThreadPool.java
index 71bb3c5b7400df83b4d49c940ae8a3699183a406..115dc66258122eed74e477e55c0992a6b25ce68a 100644
--- a/src/com/android/gallery3d/util/ThreadPool.java
+++ b/gallerycommon/src/com/android/gallery3d/util/ThreadPool.java
@@ -16,12 +16,15 @@
package com.android.gallery3d.util;
+import android.util.Log;
+
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
+ @SuppressWarnings("unused")
private static final String TAG = "ThreadPool";
private static final int CORE_POOL_SIZE = 4;
private static final int MAX_POOL_SIZE = 8;
@@ -78,8 +81,12 @@ public class ThreadPool {
private final Executor mExecutor;
public ThreadPool() {
+ this(CORE_POOL_SIZE, MAX_POOL_SIZE);
+ }
+
+ public ThreadPool(int initPoolSize, int maxPoolSize) {
mExecutor = new ThreadPoolExecutor(
- CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
+ initPoolSize, maxPoolSize, KEEP_ALIVE_TIME,
TimeUnit.SECONDS, new LinkedBlockingQueue(),
new PriorityThreadFactory("thread-pool",
android.os.Process.THREAD_PRIORITY_BACKGROUND));
@@ -98,6 +105,7 @@ public class ThreadPool {
}
private class Worker implements Runnable, Future, JobContext {
+ @SuppressWarnings("hiding")
private static final String TAG = "Worker";
private Job mJob;
private FutureListener mListener;
@@ -114,6 +122,7 @@ public class ThreadPool {
}
// This is called by a thread in the thread pool.
+ @Override
public void run() {
T result = null;
@@ -137,6 +146,7 @@ public class ThreadPool {
}
// Below are the methods for Future.
+ @Override
public synchronized void cancel() {
if (mIsCancelled) return;
mIsCancelled = true;
@@ -150,14 +160,17 @@ public class ThreadPool {
}
}
+ @Override
public boolean isCancelled() {
return mIsCancelled;
}
+ @Override
public synchronized boolean isDone() {
return mIsDone;
}
+ @Override
public synchronized T get() {
while (!mIsDone) {
try {
@@ -170,12 +183,14 @@ public class ThreadPool {
return mResult;
}
+ @Override
public void waitDone() {
get();
}
// Below are the methods for JobContext (only called from the
// thread running the job)
+ @Override
public synchronized void setCancelListener(CancelListener listener) {
mCancelListener = listener;
if (mIsCancelled && mCancelListener != null) {
@@ -183,6 +198,7 @@ public class ThreadPool {
}
}
+ @Override
public boolean setMode(int mode) {
// Release old resource
ResourceCounter rc = modeToCounter(mMode);
diff --git a/jni/Android.mk b/jni/Android.mk
index eedfa0bffcf26a77100e19ad18b2b952d3bfe038..db31bfdf6e10dbb623df487e4d152fa57f1e02fa 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -6,11 +6,44 @@ LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES
LOCAL_SRC_FILES := jni_egl_fence.cpp
-LOCAL_SHARED_LIBRARIES := libcutils libEGL
+LOCAL_SDK_VERSION := 9
+
+LOCAL_LDFLAGS := -llog -lEGL
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := libjni_eglfence
+
include $(BUILD_SHARED_LIBRARY)
+# Filtershow
+
+include $(CLEAR_VARS)
+
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_LDFLAGS := -llog -ljnigraphics
+LOCAL_SDK_VERSION := 9
+LOCAL_MODULE := libjni_filtershow_filters
+LOCAL_SRC_FILES := filters/bw.c \
+ filters/gradient.c \
+ filters/saturated.c \
+ filters/exposure.c \
+ filters/contrast.c \
+ filters/hue.c \
+ filters/shadows.c \
+ filters/hsv.c \
+ filters/vibrance.c \
+ filters/geometry.c \
+ filters/vignette.c \
+ filters/redEyeMath.c \
+ filters/fx.c \
+ filters/wbalance.c \
+ filters/redeye.c \
+ filters/bwfilter.c \
+ filters/tinyplanet.cc
+
+LOCAL_CFLAGS += -ffast-math -O3 -funroll-loops
+LOCAL_ARM_MODE := arm
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/Application.mk b/jni/Application.mk
new file mode 100644
index 0000000000000000000000000000000000000000..22d188e595e048a3ba5ba5bf509af58e62df7baf
--- /dev/null
+++ b/jni/Application.mk
@@ -0,0 +1 @@
+APP_PLATFORM := android-9
diff --git a/jni/filters/bw.c b/jni/filters/bw.c
new file mode 100644
index 0000000000000000000000000000000000000000..f075d307e28d6b47c35d59071565e498c8f633d8
--- /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/bwfilter.c b/jni/filters/bwfilter.c
new file mode 100644
index 0000000000000000000000000000000000000000..f7fb31ad1781e78ac958bfa5b525a22819f5fa8c
--- /dev/null
+++ b/jni/filters/bwfilter.c
@@ -0,0 +1,55 @@
+/*
+ * 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
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterBwFilter, nativeApplyFilter, jobject bitmap, jint width, jint height, jint rw, jint gw, jint bw)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ unsigned char * rgb = (unsigned char * )destination;
+ float sr = rw;
+ float sg = gw;
+ float sb = bw;
+
+ float min = MIN(sg,sb);
+ min = MIN(sr,min);
+ float max = MAX(sg,sb);
+ max = MAX(sr,max);
+ float avg = (min+max)/2;
+ sb /= avg;
+ sg /= avg;
+ sr /= avg;
+ int i;
+ int len = width * height * 4;
+
+ for (i = 0; i < len; i+=4)
+ {
+ float r = sr *rgb[RED];
+ float g = sg *rgb[GREEN];
+ float b = sb *rgb[BLUE];
+ min = MIN(g,b);
+ min = MIN(r,min);
+ max = MAX(g,b);
+ max = MAX(r,max);
+ avg =(min+max)/2;
+ rgb[RED] = CLAMP(avg);
+ rgb[GREEN] = rgb[RED];
+ rgb[BLUE] = rgb[RED];
+ }
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/jni/filters/contrast.c b/jni/filters/contrast.c
new file mode 100644
index 0000000000000000000000000000000000000000..6c1b976cf24c2436ab0a39a0c408945e7722f44b
--- /dev/null
+++ b/jni/filters/contrast.c
@@ -0,0 +1,47 @@
+/*
+ * 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
+#include "filters.h"
+
+unsigned char clamp(int c)
+{
+ int N = 255;
+ c &= ~(c >> 31);
+ c -= N;
+ c &= (c >> 31);
+ c += N;
+ return (unsigned char) c;
+}
+
+void JNIFUNCF(ImageFilterContrast, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloat bright)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ unsigned char * rgb = (unsigned char * )destination;
+ int i;
+ int len = width * height * 4;
+ float m = (float)pow(2, bright/100.);
+ float c = 127-m*127;
+
+ for (i = 0; i < len; i+=4) {
+ rgb[RED] = clamp((int)(m*rgb[RED]+c));
+ rgb[GREEN] = clamp((int)(m*rgb[GREEN]+c));
+ rgb[BLUE] = clamp((int)(m*rgb[BLUE]+c));
+ }
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
+
diff --git a/jni/filters/exposure.c b/jni/filters/exposure.c
new file mode 100644
index 0000000000000000000000000000000000000000..6b32798c887fe809a1fcec1e644941e44de9bdc0
--- /dev/null
+++ b/jni/filters/exposure.c
@@ -0,0 +1,37 @@
+/*
+ * 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(ImageFilterExposure, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloat bright)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ unsigned char * rgb = (unsigned char * )destination;
+ int i;
+ int len = width * height * 4;
+
+ int m = (255-bright);
+
+ for (i = 0; i < len; i+=4)
+ {
+ rgb[RED] = clamp((255*(rgb[RED]))/m);
+ rgb[GREEN] = clamp((255*(rgb[GREEN]))/m);
+ rgb[BLUE] = clamp((255*(rgb[BLUE]))/m);
+ }
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
+
diff --git a/jni/filters/filters.h b/jni/filters/filters.h
new file mode 100644
index 0000000000000000000000000000000000000000..d518b6398e8c3667a1f32849f5f079884a3784f1
--- /dev/null
+++ b/jni/filters/filters.h
@@ -0,0 +1,52 @@
+/*
+ * 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
+#include
+#include
+#include
+
+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 obj, vars)
+
+#define RED i
+#define GREEN i+1
+#define BLUE i+2
+#define ALPHA i+3
+#define CLAMP(c) (MAX(0, MIN(255, c)))
+
+__inline__ unsigned char clamp(int c);
+
+extern void rgb2hsv( unsigned char *rgb,int rgbOff,unsigned short *hsv,int hsvOff);
+extern void hsv2rgb(unsigned short *hsv,int hsvOff,unsigned char *rgb,int rgbOff);
+extern void filterRedEye(unsigned char *src, unsigned char *dest, int iw, int ih, short *rect);
+extern double fastevalPoly(double *poly,int n, double x);
+#endif // FILTERS_H
diff --git a/jni/filters/fx.c b/jni/filters/fx.c
new file mode 100644
index 0000000000000000000000000000000000000000..24fa5e0d7d14aa32ad376568a777c4c4e5caca9a
--- /dev/null
+++ b/jni/filters/fx.c
@@ -0,0 +1,88 @@
+/*
+ * 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"
+
+__inline__ int interp(unsigned char *src, int p , int *off ,float dr,float dg, float db){
+
+ float fr00 = (src[p+off[0]])*(1-dr)+(src[p+off[1]])*dr;
+ float fr01 = (src[p+off[2]])*(1-dr)+(src[p+off[3]])*dr;
+ float fr10 = (src[p+off[4]])*(1-dr)+(src[p+off[5]])*dr;
+ float fr11 = (src[p+off[6]])*(1-dr)+(src[p+off[7]])*dr;
+ float frb0 = fr00 * (1-db)+fr01*db;
+ float frb1 = fr10 * (1-db)+fr11*db;
+ float frbg = frb0 * (1-dg)+frb1*dg;
+
+ return (int)frbg ;
+}
+
+void JNIFUNCF(ImageFilterFx, nativeApplyFilter, jobject bitmap, jint width, jint height, jobject lutbitmap,jint lutwidth, jint lutheight )
+{
+ char* destination = 0;
+ char* lut = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ AndroidBitmap_lockPixels(env, lutbitmap, (void**) &lut);
+ unsigned char * rgb = (unsigned char * )destination;
+ unsigned char * lutrgb = (unsigned char * )lut;
+ int lutdim_r = lutheight;
+ int lutdim_g = lutheight;;
+ int lutdim_b = lutwidth/lutheight;;
+ int STEP = 4;
+
+ int off[8] = {
+ 0,
+ STEP*1,
+ STEP*lutdim_r,
+ STEP*(lutdim_r + 1),
+ STEP*(lutdim_r*lutdim_b),
+ STEP*(lutdim_r*lutdim_b+1),
+ STEP*(lutdim_r*lutdim_b+lutdim_r),
+ STEP*(lutdim_r*lutdim_b+lutdim_r + 1)
+ };
+
+ float scale_R = (lutdim_r-1.f)/256.f;
+ float scale_G = (lutdim_g-1.f)/256.f;
+ float scale_B = (lutdim_b-1.f)/256.f;
+
+ int i;
+ int len = width * height * STEP;
+
+ for (i = 0; i < len; i+=STEP)
+ {
+ int r = rgb[RED];
+ int g = rgb[GREEN];
+ int b = rgb[BLUE];
+
+ float fb = b*scale_B;
+ float fg = g*scale_G;
+ float fr = r*scale_R;
+ int lut_b = (int)fb;
+ int lut_g = (int)fg;
+ int lut_r = (int)fr;
+ int p = lut_r+lut_b*lutdim_r+lut_g*lutdim_r*lutdim_b;
+ p*=STEP;
+ float dr = fr-lut_r;
+ float dg = fg-lut_g;
+ float db = fb-lut_b;
+ rgb[RED] = clamp(interp(lutrgb,p ,off,dr,dg,db));
+ rgb[GREEN] = clamp(interp(lutrgb,p+1,off,dr,dg,db));
+ rgb[BLUE] = clamp(interp(lutrgb,p+2,off,dr,dg,db));
+
+ }
+
+ AndroidBitmap_unlockPixels(env, bitmap);
+ AndroidBitmap_unlockPixels(env, lutbitmap);
+}
diff --git a/jni/filters/geometry.c b/jni/filters/geometry.c
new file mode 100644
index 0000000000000000000000000000000000000000..a0b5aaacf6b7bd71e26ac05c4f7494147b61f6e6
--- /dev/null
+++ b/jni/filters/geometry.c
@@ -0,0 +1,184 @@
+/*
+ * 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"
+#include
+
+__inline__ void flipVertical(char * source, int srcWidth, int srcHeight, char * destination, int dstWidth, int dstHeight){
+ //Vertical
+ size_t cpy_bytes = sizeof(char) * 4;
+ int width = cpy_bytes * srcWidth;
+ int length = srcHeight;
+ int total = length * width;
+ size_t bytes_to_copy = sizeof(char) * width;
+ int i = 0;
+ int temp = total - width;
+ for (i = 0; i < total; i += width) {
+ memcpy(destination + temp - i, source + i, bytes_to_copy);
+ }
+}
+
+__inline__ void flipHorizontal(char * source, int srcWidth, int srcHeight, char * destination, int dstWidth, int dstHeight){
+ //Horizontal
+ size_t cpy_bytes = sizeof(char) * 4;
+ int width = cpy_bytes * srcWidth;
+ int length = srcHeight;
+ int total = length * width;
+ int i = 0;
+ int j = 0;
+ int temp = 0;
+ for (i = 0; i < total; i+= width) {
+ temp = width + i - cpy_bytes;
+ for (j = 0; j < width; j+=cpy_bytes) {
+ memcpy(destination + temp - j, source + i + j, cpy_bytes);
+ }
+ }
+}
+
+__inline__ void flip_fun(int flip, char * source, int srcWidth, int srcHeight, char * destination, int dstWidth, int dstHeight){
+ int horiz = (flip & 1) != 0;
+ int vert = (flip & 2) != 0;
+ if (horiz && vert){
+ int arr_len = dstWidth * dstHeight * sizeof(char) * 4;
+ char* temp = (char *) malloc(arr_len);
+ flipHorizontal(source, srcWidth, srcHeight, temp, dstWidth, dstHeight);
+ flipVertical(temp, dstWidth, dstHeight, destination, dstWidth, dstHeight);
+ free(temp);
+ return;
+ }
+ if (horiz){
+ flipHorizontal(source, srcWidth, srcHeight, destination, dstWidth, dstHeight);
+ return;
+ }
+ if (vert){
+ flipVertical(source, srcWidth, srcHeight, destination, dstWidth, dstHeight);
+ return;
+ }
+}
+
+//90 CCW (opposite of what's used in UI?)
+__inline__ void rotate90(char * source, int srcWidth, int srcHeight, char * destination, int dstWidth, int dstHeight){
+ size_t cpy_bytes = sizeof(char) * 4;
+ int width = cpy_bytes * srcWidth;
+ int length = srcHeight;
+ int total = length * width;
+ int i = 0;
+ int j = 0;
+ for (j = 0; j < length * cpy_bytes; j+= cpy_bytes){
+ for (i = 0; i < width; i+=cpy_bytes){
+ int column_disp = (width - cpy_bytes - i) * length;
+ int row_disp = j;
+ memcpy(destination + column_disp + row_disp , source + j * srcWidth + i, cpy_bytes);
+ }
+ }
+}
+
+__inline__ void rotate180(char * source, int srcWidth, int srcHeight, char * destination, int dstWidth, int dstHeight){
+ flip_fun(3, source, srcWidth, srcHeight, destination, dstWidth, dstHeight);
+}
+
+__inline__ void rotate270(char * source, int srcWidth, int srcHeight, char * destination, int dstWidth, int dstHeight){
+ rotate90(source, srcWidth, srcHeight, destination, dstWidth, dstHeight);
+ flip_fun(3, destination, dstWidth, dstHeight, destination, dstWidth, dstHeight);
+}
+
+// rotate == 1 is 90 degrees, 2 is 180, 3 is 270 (positive is CCW).
+__inline__ void rotate_fun(int rotate, char * source, int srcWidth, int srcHeight, char * destination, int dstWidth, int dstHeight){
+ switch( rotate )
+ {
+ case 1:
+ rotate90(source, srcWidth, srcHeight, destination, dstWidth, dstHeight);
+ break;
+ case 2:
+ rotate180(source, srcWidth, srcHeight, destination, dstWidth, dstHeight);
+ break;
+ case 3:
+ rotate270(source, srcWidth, srcHeight, destination, dstWidth, dstHeight);
+ break;
+ default:
+ break;
+ }
+}
+
+__inline__ void crop(char * source, int srcWidth, int srcHeight, char * destination, int dstWidth, int dstHeight, int offsetWidth, int offsetHeight){
+ size_t cpy_bytes = sizeof(char) * 4;
+ int row_width = cpy_bytes * srcWidth;
+ int new_row_width = cpy_bytes * dstWidth;
+ if ((srcWidth > dstWidth + offsetWidth) || (srcHeight > dstHeight + offsetHeight)){
+ return;
+ }
+ int i = 0;
+ int j = 0;
+ for (j = offsetHeight; j < offsetHeight + dstHeight; j++){
+ memcpy(destination + (j - offsetHeight) * new_row_width, source + j * row_width + offsetWidth * cpy_bytes, cpy_bytes * dstWidth );
+ }
+}
+
+void JNIFUNCF(ImageFilterGeometry, nativeApplyFilterFlip, jobject src, jint srcWidth, jint srcHeight, jobject dst, jint dstWidth, jint dstHeight, jint flip) {
+ char* destination = 0;
+ char* source = 0;
+ if (srcWidth != dstWidth || srcHeight != dstHeight) {
+ return;
+ }
+ AndroidBitmap_lockPixels(env, src, (void**) &source);
+ AndroidBitmap_lockPixels(env, dst, (void**) &destination);
+ flip_fun(flip, source, srcWidth, srcHeight, destination, dstWidth, dstHeight);
+ AndroidBitmap_unlockPixels(env, dst);
+ AndroidBitmap_unlockPixels(env, src);
+}
+
+void JNIFUNCF(ImageFilterGeometry, nativeApplyFilterRotate, jobject src, jint srcWidth, jint srcHeight, jobject dst, jint dstWidth, jint dstHeight, jint rotate) {
+ char* destination = 0;
+ char* source = 0;
+ int len = dstWidth * dstHeight * 4;
+ AndroidBitmap_lockPixels(env, src, (void**) &source);
+ AndroidBitmap_lockPixels(env, dst, (void**) &destination);
+ rotate_fun(rotate, source, srcWidth, srcHeight, destination, dstWidth, dstHeight);
+ AndroidBitmap_unlockPixels(env, dst);
+ AndroidBitmap_unlockPixels(env, src);
+}
+
+void JNIFUNCF(ImageFilterGeometry, nativeApplyFilterCrop, jobject src, jint srcWidth, jint srcHeight, jobject dst, jint dstWidth, jint dstHeight, jint offsetWidth, jint offsetHeight) {
+ char* destination = 0;
+ char* source = 0;
+ int len = dstWidth * dstHeight * 4;
+ AndroidBitmap_lockPixels(env, src, (void**) &source);
+ AndroidBitmap_lockPixels(env, dst, (void**) &destination);
+ crop(source, srcWidth, srcHeight, destination, dstWidth, dstHeight, offsetWidth, offsetHeight);
+ AndroidBitmap_unlockPixels(env, dst);
+ AndroidBitmap_unlockPixels(env, src);
+}
+
+void JNIFUNCF(ImageFilterGeometry, nativeApplyFilterStraighten, jobject src, jint srcWidth, jint srcHeight, jobject dst, jint dstWidth, jint dstHeight, jfloat straightenAngle) {
+ char* destination = 0;
+ char* source = 0;
+ int len = dstWidth * dstHeight * 4;
+ AndroidBitmap_lockPixels(env, src, (void**) &source);
+ AndroidBitmap_lockPixels(env, dst, (void**) &destination);
+ // TODO: implement straighten
+ int i = 0;
+ for (; i < len; i += 4) {
+ int r = source[RED];
+ int g = source[GREEN];
+ int b = source[BLUE];
+ destination[RED] = 128;
+ destination[GREEN] = g;
+ destination[BLUE] = 128;
+ }
+ AndroidBitmap_unlockPixels(env, dst);
+ AndroidBitmap_unlockPixels(env, src);
+}
+
diff --git a/jni/filters/gradient.c b/jni/filters/gradient.c
new file mode 100644
index 0000000000000000000000000000000000000000..1a85697868c4bc70c063d6633ca738cd4ac3eae8
--- /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/hsv.c b/jni/filters/hsv.c
new file mode 100644
index 0000000000000000000000000000000000000000..aabd053fe53bc0fccd18552ef224baf8a787adef
--- /dev/null
+++ b/jni/filters/hsv.c
@@ -0,0 +1,156 @@
+/*
+ * 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
+#include "filters.h"
+
+double fastevalPoly(double *poly,int n, double x){
+
+ double f =x;
+ double sum = poly[0]+poly[1]*f;
+ int i;
+ for (i = 2; i < n; i++) {
+ f*=x;
+ sum += poly[i]*f;
+ }
+ return sum;
+}
+
+void rgb2hsv( unsigned char *rgb,int rgbOff,unsigned short *hsv,int hsvOff)
+{
+ int iMin,iMax,chroma;
+ int ABITS = 4;
+ int HSCALE = 256;
+
+ int k1=255 << ABITS;
+ int k2=HSCALE << ABITS;
+
+ int ri = rgb[rgbOff+0];
+ int gi = rgb[rgbOff+1];
+ int bi = rgb[rgbOff+2];
+ short rv,rs,rh;
+
+ if (ri > gi) {
+ iMax = MAX (ri, bi);
+ iMin = MIN (gi, bi);
+ } else {
+ iMax = MAX (gi, bi);
+ iMin = MIN (ri, bi);
+ }
+
+ chroma = iMax - iMin;
+ // set value
+ rv = (short)( iMax << ABITS);
+
+ // set saturation
+ if (rv == 0)
+ rs = 0;
+ else
+ rs = (short)((k1*chroma)/iMax);
+
+ // set hue
+ if (rs == 0)
+ rh = 0;
+ else {
+ if ( ri == iMax ) {
+ rh = (short)( (k2*(6*chroma+gi - bi))/(6*chroma));
+ if (rh >= k2) rh -= k2;
+ } else if (gi == iMax)
+ rh = (short)( (k2*(2*chroma+bi - ri ))/(6*chroma));
+ else // (bi == iMax )
+ rh = (short)( (k2*(4*chroma+ri - gi ))/(6*chroma));
+ }
+ hsv[hsvOff+0] = rv;
+ hsv[hsvOff+1] = rs;
+ hsv[hsvOff+2] = rh;
+}
+
+void hsv2rgb(unsigned short *hsv,int hsvOff, unsigned char *rgb,int rgbOff)
+{
+ int ABITS = 4;
+ int HSCALE = 256;
+ int m;
+ int H,X,ih,is,iv;
+ int k1=255< cs == 0 --> m=cv
+ if (cs == 0) {
+ rb = ( rg = ( rr =( cv >> ABITS) ));
+ } else {
+ ih=(int)ch;
+ is=(int)cs;
+ iv=(int)cv;
+
+ H = (6*ih)/k2;
+ X = ((iv*is)/k2)*(k2- abs(6*ih- 2*(H>>1)*k2 - k2)) ;
+
+ // removing additional bits --> unit8
+ X=( (X+iv*(k1 - is ))/k1 + k3 ) >> ABITS;
+ m=m >> ABITS;
+
+ // ( chroma + m ) --> cv ;
+ cv=(short) (cv >> ABITS);
+ switch (H) {
+ case 0:
+ rr = cv;
+ rg = X;
+ rb = m;
+ break;
+ case 1:
+ rr = X;
+ rg = cv;
+ rb = m;
+ break;
+ case 2:
+ rr = m;
+ rg = cv;
+ rb = X;
+ break;
+ case 3:
+ rr = m;
+ rg = X;
+ rb = cv;
+ break;
+ case 4:
+ rr = X;
+ rg = m;
+ rb = cv;
+ break;
+ case 5:
+ rr = cv;
+ rg = m ;
+ rb = X;
+ break;
+ }
+ }
+ rgb[rgbOff+0] = rr;
+ rgb[rgbOff+1] = rg;
+ rgb[rgbOff+2] = rb;
+}
+
diff --git a/jni/filters/hue.c b/jni/filters/hue.c
new file mode 100644
index 0000000000000000000000000000000000000000..a4aef936dbd9c2c198bd0dda3c5aa75691590c28
--- /dev/null
+++ b/jni/filters/hue.c
@@ -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.
+ */
+
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterHue, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloatArray matrix)
+{
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ unsigned char * rgb = (unsigned char * )destination;
+ int i;
+ int len = width * height * 4;
+ jfloat* mat = (*env)->GetFloatArrayElements(env, matrix,0);
+
+ for (i = 0; i < len; i+=4)
+ {
+ int r = rgb[RED];
+ int g = rgb[GREEN];
+ int b = rgb[BLUE];
+
+ float rf = r*mat[0] + g*mat[4] + b*mat[8] + mat[12];
+ float gf = r*mat[1] + g*mat[5] + b*mat[9] + mat[13];
+ float bf = r*mat[2] + g*mat[6] + b*mat[10] + mat[14];
+
+ rgb[RED] = clamp((int)rf);
+ rgb[GREEN] = clamp((int)gf);
+ rgb[BLUE] = clamp((int)bf);
+ }
+
+ (*env)->ReleaseFloatArrayElements(env, matrix, mat, 0);
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
+
diff --git a/jni/filters/redEyeMath.c b/jni/filters/redEyeMath.c
new file mode 100644
index 0000000000000000000000000000000000000000..26f3f76a47d2a0534bfd69be6b7b5e1dd10c099e
--- /dev/null
+++ b/jni/filters/redEyeMath.c
@@ -0,0 +1,172 @@
+/*
+ * 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
+#include "filters.h"
+
+int value(int r, int g, int b) {
+ return MAX(r, MAX(g, b));
+}
+
+int isRed(unsigned char *src, int p) {
+ int b = src[p + 2];
+ int g = src[p + 1];
+ int r = src[p];
+ int max = MAX(g, b);
+
+ return ((r * 100 / (max + 2) > 160) & (max < 80));
+}
+
+void findPossible(unsigned char *src, unsigned char *mask, int iw, int ih,
+ short *rect) {
+ int recX = rect[0], recY = rect[1], recW = rect[2], recH = rect[3];
+ int y, x;
+
+ for (y = 0; y < recH; y++) {
+ int sy = (recY + y) * iw;
+ for (x = 0; x < recW; x++) {
+ int p = (recX + x + sy) * 4;
+
+ int b = src[p + 2];
+ int g = src[p + 1];
+ int r = src[p];
+ mask[x + y * recW] = (
+ mask[x + y * recW] > 0 && (value(r, g, b) > 240) ? 1 : 0);
+
+ }
+
+ }
+}
+
+void findReds(unsigned char *src, unsigned char *mask, int iw, int ih,
+ short *rect) {
+ int recX = rect[0], recY = rect[1], recW = rect[2], recH = rect[3];
+ int y, x;
+
+ for (y = 0; y < recH; y++) {
+ int sy = (recY + y) * iw;
+ for (x = 0; x < recW; x++) {
+ int p = (recX + x + sy) * 4;
+
+ mask[x + y * recW] = ((isRed(src, p)) ? 1 : 0);
+
+ }
+
+ }
+}
+
+void dialateMaskIfRed(unsigned char *src, int iw, int ih, unsigned char *mask,
+ unsigned char *out, short *rect) {
+ int recX = rect[0], recY = rect[1], recW = rect[2], recH = rect[3];
+ int y, x;
+
+ for (y = 1; y < recH - 1; y++) {
+ int row = recW * y;
+ int sy = (recY + y) * iw;
+ for (x = 1; x < recW - 1; x++) {
+ int p = (recX + x + sy) * 4;
+
+ char b = (mask[row + x] | mask[row + x + 1] | mask[row + x - 1]
+ | mask[row + x - recW] | mask[row + x + recW]);
+ if (b != 0 && isRed(src, p))
+ out[row + x] = 1;
+ else
+ out[row + x] = mask[row + x];
+ }
+ }
+}
+
+void dialateMask(unsigned char *mask, unsigned char *out, int mw, int mh) {
+ int y, x;
+ for (y = 1; y < mh - 1; y++) {
+ int row = mw * y;
+ for (x = 1; x < mw - 1; x++) {
+ out[row + x] = (mask[row + x] | mask[row + x + 1]
+ | mask[row + x - 1] | mask[row + x - mw]
+ | mask[row + x + mw]);
+ }
+ }
+}
+
+void stuff(int r, int g, int b, unsigned char *img, int off) {
+ img[off + 2] = b;
+ img[off + 1] = g;
+ img[off] = r;
+}
+
+void filterRedEye(unsigned char *src, unsigned char *dest, int iw, int ih, short *rect) {
+ int recX = rect[0], recY = rect[1], recW = rect[2], recH = rect[3];
+ unsigned char *mask1 = (unsigned char *) malloc(recW * recH);
+ unsigned char *mask2 = (unsigned char *)malloc(recW*recH);
+ int QUE_LEN = 100;
+ int y, x, i;
+
+ rect[0] = MAX(rect[0],0);
+ rect[1] = MAX(rect[1],0);
+ rect[2] = MIN(rect[2]+rect[0],iw)-rect[0];
+ rect[3] = MIN(rect[3]+rect[1],ih)-rect[1];
+
+ findReds(src, mask2, iw, ih, rect);
+ dialateMask(mask2, mask1, recW, recH);
+ dialateMask(mask1, mask2, recW, recH);
+ dialateMask(mask2, mask1, recW, recH);
+ dialateMask(mask1, mask2, recW, recH);
+ findPossible(src, mask2, iw, ih, rect);
+ dialateMask(mask2, mask1, recW, recH);
+
+ for (i = 0; i < 12; i++) {
+ dialateMaskIfRed(src, iw, ih, mask1, mask2, rect);
+ dialateMaskIfRed(src, iw, ih, mask2, mask1, rect);
+ }
+ dialateMask(mask1, mask2, recW, recH);
+ dialateMask(mask2, mask1, recW, recH);
+
+ for (y = 3; y < recH-3; y++) {
+ int sy = (recY + y) * iw;
+ for (x = 3; x < recW-3; x++) {
+ int p = (recX + x + sy) * 4;
+
+ int b = src[p + 2];
+ int g = src[p + 1];
+ int r = src[p];
+
+ if (mask1[x + y * recW] != 0) {
+ int m = MAX(g,b);
+ float rr = (r - m) / (float) m;
+ if (rr > .7f && g < 60 && b < 60) {
+ dest[p + 2] = (0);
+ dest[p + 1] = (0);
+ dest[p] = (0);
+ } else {
+ if (mask2[x + y * recW] != 0) {
+ stuff(r / 2, g / 2, b / 2, dest, p);
+ } else
+ stuff((2 * r) / 3, (2 * g) / 3, (2 * b) / 3, dest, p);
+ }
+
+ } else
+ stuff(r, g, b, dest, p);
+
+ //dest[p + 2] = dest[p + 1] =dest[p]=src[p];
+ }
+
+ }
+
+ free(mask1);
+ free(mask2);
+}
+
+
diff --git a/jni/filters/redeye.c b/jni/filters/redeye.c
new file mode 100644
index 0000000000000000000000000000000000000000..9a358dd3dcfc1e3f00bbca9cc1d16da8728313b8
--- /dev/null
+++ b/jni/filters/redeye.c
@@ -0,0 +1,31 @@
+/*
+ * 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
+#include "filters.h"
+
+ void JNIFUNCF(ImageFilterRedEye, nativeApplyFilter, jobject bitmap, jint width, jint height, jshortArray vrect)
+ {
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ unsigned char * rgb = (unsigned char * )destination;
+ short* rect = (*env)->GetShortArrayElements(env, vrect,0);
+
+ filterRedEye(rgb,rgb,width,height,rect);
+
+ (*env)->ReleaseShortArrayElements(env, vrect, rect, 0);
+ AndroidBitmap_unlockPixels(env, bitmap);
+ }
diff --git a/jni/filters/saturated.c b/jni/filters/saturated.c
new file mode 100644
index 0000000000000000000000000000000000000000..1bc0cc56be76b5f3c4d8c0fa961d2c8b28f214b7
--- /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/shadows.c b/jni/filters/shadows.c
new file mode 100644
index 0000000000000000000000000000000000000000..38d64c8b5fe88821aa6164e5773b1378dd823f98
--- /dev/null
+++ b/jni/filters/shadows.c
@@ -0,0 +1,57 @@
+/*
+ * 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
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterShadows, nativeApplyFilter, jobject bitmap, jint width, jint height, float scale){
+ double shadowFilterMap[] = {
+ -0.00591, 0.0001,
+ 1.16488, 0.01668,
+ -0.18027, -0.06791,
+ -0.12625, 0.09001,
+ 0.15065, -0.03897
+ };
+
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+ unsigned char * rgb = (unsigned char * )destination;
+ int i;
+ double s = (scale>=0)?scale:scale/5;
+ int len = width * height * 4;
+
+ double *poly = (double *) malloc(5*sizeof(double));
+ for (i = 0; i < 5; i++) {
+ poly[i] = fastevalPoly(shadowFilterMap+i*2,2 , s);
+ }
+
+ unsigned short * hsv = (unsigned short *)malloc(3*sizeof(short));
+
+ for (i = 0; i < len; i+=4)
+ {
+ rgb2hsv(rgb,i,hsv,0);
+
+ double v = (fastevalPoly(poly,5,hsv[0]/4080.)*4080);
+ if (v>4080) v = 4080;
+ hsv[0] = (unsigned short) ((v>0)?v:0);
+
+ hsv2rgb(hsv,0, rgb,i);
+ }
+
+ free(poly);
+ free(hsv);
+ AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/jni/filters/tinyplanet.cc b/jni/filters/tinyplanet.cc
new file mode 100644
index 0000000000000000000000000000000000000000..a40470d3410a39eff4779147cf289d82e7db3018
--- /dev/null
+++ b/jni/filters/tinyplanet.cc
@@ -0,0 +1,149 @@
+/*
+ * 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"
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define PI_F 3.141592653589f
+
+class ImageRGBA {
+ public:
+ ImageRGBA(unsigned char* image, int width, int height)
+ : image_(image), width_(width), height_(height) {
+ width_step_ = width * 4;
+ }
+
+ int Width() const {
+ return width_;
+ }
+
+ int Height() const {
+ return height_;
+ }
+
+ // Pixel accessor.
+ unsigned char* operator()(int x, int y) {
+ return image_ + y * width_step_ + x * 4;
+ }
+ const unsigned char* operator()(int x, int y) const {
+ return image_ + y * width_step_ + x * 4;
+ }
+
+ private:
+ unsigned char* image_;
+ int width_;
+ int height_;
+ int width_step_;
+};
+
+// Interpolate a pixel in a 3 channel image.
+inline void InterpolatePixel(const ImageRGBA &image, float x, float y,
+ unsigned char* dest) {
+ // Get pointers and scale factors for the source pixels.
+ float ax = x - floor(x);
+ float ay = y - floor(y);
+ float axn = 1.0f - ax;
+ float ayn = 1.0f - ay;
+ const unsigned char *p = image(x, y);
+ const unsigned char *p2 = image(x, y + 1);
+
+ // Interpolate each image color plane.
+ dest[0] = static_cast(axn * ayn * p[0] + ax * ayn * p[4] +
+ ax * ay * p2[4] + axn * ay * p2[0] + 0.5f);
+ p++;
+ p2++;
+
+ dest[1] = static_cast(axn * ayn * p[0] + ax * ayn * p[4] +
+ ax * ay * p2[4] + axn * ay * p2[0] + 0.5f);
+ p++;
+ p2++;
+
+ dest[2] = static_cast(axn * ayn * p[0] + ax * ayn * p[4] +
+ ax * ay * p2[4] + axn * ay * p2[0] + 0.5f);
+ p++;
+ p2++;
+}
+
+// Wrap circular coordinates around the globe
+inline float wrap(float value, float dimension) {
+ return value - (dimension * floor(value/dimension));
+}
+
+void StereographicProjection(float scale, float angle, unsigned char* input_image,
+ int input_width, int input_height,
+ unsigned char* output_image, int output_width,
+ int output_height) {
+ ImageRGBA input(input_image, input_width, input_height);
+ ImageRGBA output(output_image, output_width, output_height);
+
+ const float image_scale = output_width * scale;
+
+ for (int x = 0; x < output_width; x++) {
+ // Center and scale x
+ float xf = (x - output_width / 2.0f) / image_scale;
+
+ for (int y = 0; y < output_height; y++) {
+ // Center and scale y
+ float yf = (y - output_height / 2.0f) / image_scale;
+
+ // Convert to polar
+ float r = hypotf(xf, yf);
+ float theta = angle+atan2(yf, xf);
+ if (theta>PI_F) theta-=2*PI_F;
+
+ // Project onto plane
+ float phi = 2 * atan(1 / r);
+ // (theta stays the same)
+
+ // Map to panorama image
+ float px = (theta / (2 * PI_F)) * input_width;
+ float py = (phi / PI_F) * input_height;
+
+ // Wrap around the globe
+ px = wrap(px, input_width);
+ py = wrap(py, input_height);
+
+ // Write the interpolated pixel
+ InterpolatePixel(input, px, py, output(x, y));
+ }
+ }
+}
+
+
+void JNIFUNCF(ImageFilterTinyPlanet, nativeApplyFilter, jobject bitmap_in, jint width, jint height, jobject bitmap_out, jint output_size, jfloat scale,jfloat angle)
+{
+ char* source = 0;
+ char* destination = 0;
+ AndroidBitmap_lockPixels(env, bitmap_in, (void**) &source);
+ AndroidBitmap_lockPixels(env, bitmap_out, (void**) &destination);
+ unsigned char * rgb_in = (unsigned char * )source;
+ unsigned char * rgb_out = (unsigned char * )destination;
+
+ StereographicProjection(scale,angle, rgb_in, width, height, rgb_out, output_size, output_size);
+ AndroidBitmap_unlockPixels(env, bitmap_in);
+ AndroidBitmap_unlockPixels(env, bitmap_out);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+
diff --git a/jni/filters/vibrance.c b/jni/filters/vibrance.c
new file mode 100644
index 0000000000000000000000000000000000000000..cb5c536e5f0649e42d05adeee934bcc621cb485c
--- /dev/null
+++ b/jni/filters/vibrance.c
@@ -0,0 +1,62 @@
+/*
+ * 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
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterVibrance, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloat vibrance)
+{
+ 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 Vib = vibrance/100.f;
+ float S = Vib+1;
+ 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];
+ float red = (r-MAX(g, b))/256.f;
+ float sx = (float)(Vib/(1+exp(-red*3)));
+ S = sx+1;
+ MS = 1.0f - S;
+ Rt = Rf * MS;
+ Gt = Gf * MS;
+ Bt = Bf * MS;
+ 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 0000000000000000000000000000000000000000..2799ff001810ba84c7ea02994e1f5c9dc579cf53
--- /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;
+
+__inline__ void 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/jni/filters/wbalance.c b/jni/filters/wbalance.c
new file mode 100644
index 0000000000000000000000000000000000000000..2b92b99786055f57aeef6807c4d9499e4e8be95a
--- /dev/null
+++ b/jni/filters/wbalance.c
@@ -0,0 +1,169 @@
+/*
+ * 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 estmateWhite(unsigned char *src, int len, int *wr, int *wb, int *wg){
+
+ int STEP = 4;
+ int RANGE = 256;
+ int *histR = (int *) malloc(256*sizeof(int));
+ int *histG = (int *) malloc(256*sizeof(int));
+ int *histB = (int *) malloc(256*sizeof(int));
+ int i;
+ for (i = 0; i < 255; i++) {
+ histR[i] = histG[i] = histB[i] =0;
+ }
+
+ for (i = 0; i < len; i+=STEP) {
+ histR[(src[RED])]++;
+ histG[(src[GREEN])]++;
+ histB[(src[BLUE])]++;
+ }
+ int min_r = -1, min_g = -1,min_b = -1;
+ int max_r = 0, max_g = 0,max_b = 0;
+ int sum_r = 0,sum_g=0,sum_b=0;
+
+ for (i = 1; i < RANGE-1; i++) {
+ int r = histR[i];
+ int g = histG[i];
+ int b = histB[i];
+ sum_r += r;
+ sum_g += g;
+ sum_b += b;
+
+ if (r>0){
+ if (min_r < 0) min_r = i;
+ max_r = i;
+ }
+ if (g>0){
+ if (min_g < 0) min_g = i;
+ max_g = i;
+ }
+ if (b>0){
+ if (min_b < 0) min_b = i;
+ max_b = i;
+ }
+ }
+
+ int sum15r = 0,sum15g=0,sum15b=0;
+ int count15r = 0,count15g=0,count15b=0;
+ int tmp_r = 0,tmp_g=0,tmp_b=0;
+
+ for (i = RANGE-2; i >0; i--) {
+ int r = histR[i];
+ int g = histG[i];
+ int b = histB[i];
+ tmp_r += r;
+ tmp_g += g;
+ tmp_b += b;
+
+ if ((tmp_r > sum_r/20) && (tmp_r < sum_r/5)) {
+ sum15r += r*i;
+ count15r += r;
+ }
+ if ((tmp_g > sum_g/20) && (tmp_g < sum_g/5)) {
+ sum15g += g*i;
+ count15g += g;
+ }
+ if ((tmp_b > sum_b/20) && (tmp_b < sum_b/5)) {
+ sum15b += b*i;
+ count15b += b;
+ }
+
+ }
+ free(histR);
+ free(histG);
+ free(histB);
+
+ if ((count15r>0) && (count15g>0) && (count15b>0) ){
+ *wr = sum15r/count15r;
+ *wb = sum15g/count15g;
+ *wg = sum15b/count15b;
+ }else {
+ *wg = *wb = *wr=255;
+ }
+}
+
+void estmateWhiteBox(unsigned char *src, int iw, int ih, int x,int y, int *wr, int *wb, int *wg){
+ int r;
+ int g;
+ int b;
+ int sum;
+ int xp,yp;
+ int bounds = 5;
+ if (x<0) x = bounds;
+ if (y<0) y = bounds;
+ if (x>=(iw-bounds)) x = (iw-bounds-1);
+ if (y>=(ih-bounds)) y = (ih-bounds-1);
+ int startx = x - bounds;
+ int starty = y - bounds;
+ int endx = x + bounds;
+ int endy = y + bounds;
+
+ for(yp= starty;yp
+#include
#include
#include
+#include
+
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR,"egl_fence",__VA_ARGS__)
+
+typedef EGLSyncKHR EGLAPIENTRY (*TypeEglCreateSyncKHR)(EGLDisplay dpy,
+ EGLenum type, const EGLint *attrib_list);
+typedef EGLBoolean EGLAPIENTRY (*TypeEglDestroySyncKHR)(EGLDisplay dpy,
+ EGLSyncKHR sync);
+typedef EGLint EGLAPIENTRY (*TypeEglClientWaitSyncKHR)(EGLDisplay dpy,
+ EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout);
+static TypeEglCreateSyncKHR FuncEglCreateSyncKHR = NULL;
+static TypeEglClientWaitSyncKHR FuncEglClientWaitSyncKHR = NULL;
+static TypeEglDestroySyncKHR FuncEglDestroySyncKHR = NULL;
+static bool initialized = false;
+static bool egl_khr_fence_sync_supported = false;
+
+bool IsEglKHRFenceSyncSupported() {
+ if (!initialized) {
+ EGLDisplay display = eglGetCurrentDisplay();
+ const char* eglExtensions = eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS);
+ if (eglExtensions && strstr(eglExtensions, "EGL_KHR_fence_sync")) {
+ FuncEglCreateSyncKHR = (TypeEglCreateSyncKHR) eglGetProcAddress("eglCreateSyncKHR");
+ FuncEglClientWaitSyncKHR = (TypeEglClientWaitSyncKHR) eglGetProcAddress("eglClientWaitSyncKHR");
+ FuncEglDestroySyncKHR = (TypeEglDestroySyncKHR) eglGetProcAddress("eglDestroySyncKHR");
+ if (FuncEglCreateSyncKHR != NULL && FuncEglClientWaitSyncKHR != NULL
+ && FuncEglDestroySyncKHR != NULL) {
+ egl_khr_fence_sync_supported = true;
+ }
+ }
+ initialized = true;
+ }
+ return egl_khr_fence_sync_supported;
+}
void
Java_com_android_gallery3d_photoeditor_FilterStack_nativeEglSetFenceAndWait(JNIEnv* env,
jobject thiz) {
+ if (!IsEglKHRFenceSyncSupported()) return;
EGLDisplay display = eglGetCurrentDisplay();
// Create a egl fence and wait for egl to return it.
// Additional reference on egl fence sync can be found in:
// http://www.khronos.org/registry/vg/extensions/KHR/EGL_KHR_fence_sync.txt
- EGLSyncKHR fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL);
+ EGLSyncKHR fence = FuncEglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL);
if (fence == EGL_NO_SYNC_KHR) {
return;
}
- EGLint result = eglClientWaitSyncKHR(display,
+ EGLint result = FuncEglClientWaitSyncKHR(display,
fence,
EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
EGL_FOREVER_KHR);
if (result == EGL_FALSE) {
ALOGE("EGL FENCE: error waiting for fence: %#x", eglGetError());
}
- eglDestroySyncKHR(display, fence);
+ FuncEglDestroySyncKHR(display, fence);
}
diff --git a/proguard.flags b/proguard.flags
index 82509333cf6529bc6dec84a829323ed3e0a12a05..8d60103bc22bf6dab18935370de1fd85f31c4885 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -19,8 +19,17 @@
public android.content.Intent getResultData();
}
--keep class com.android.camera.VideoCamera {
+-keep class com.android.camera.CameraActivity {
public boolean isRecording();
+ public long getAutoFocusTime();
+ public long getShutterLag();
+ public long getShutterToPictureDisplayedTime();
+ public long getPictureDisplayedToJpegCallbackTime();
+ public long getJpegCallbackFinishTime();
+ public long getCaptureStartTime();
+}
+
+-keep class com.android.camera.VideoModule {
public void onCancelBgTraining(...);
public void onProtectiveCurtainClick(...);
}
@@ -32,3 +41,17 @@
-keep class com.android.camera.CameraHolder {
public static void injectMockCamera(...);
}
+
+# Disable the warnings of using dynamic method calls in EffectsRecorder
+-dontnote com.android.camera.EffectsRecorder
+
+# Required for ActionBarSherlock
+-keep class android.support.v4.app.** { *; }
+-keep interface android.support.v4.app.** { *; }
+-keep class com.actionbarsherlock.** { *; }
+-keep interface com.actionbarsherlock.** { *; }
+-keepattributes *Annotation*
+
+# Required for mp4parser
+-keep public class * implements com.coremedia.iso.boxes.Box
+
diff --git a/res/drawable-hdpi/actionbar_translucent.9.png b/res/drawable-hdpi/actionbar_translucent.9.png
index f18761f04582234da668a23ef7b848c16f92b7c2..4b40967d6ad7ad3a71123415a63b2182b990c790 100644
Binary files a/res/drawable-hdpi/actionbar_translucent.9.png and b/res/drawable-hdpi/actionbar_translucent.9.png differ
diff --git a/res/drawable-hdpi/camera_crop.png b/res/drawable-hdpi/camera_crop.png
new file mode 100644
index 0000000000000000000000000000000000000000..97b1b989e36bf55f01a300641074a432a944f66c
Binary files /dev/null and b/res/drawable-hdpi/camera_crop.png differ
diff --git a/res/drawable-hdpi/filtershow_button_colors_curve.png b/res/drawable-hdpi/filtershow_button_colors_curve.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b7a26060ad2089efae47ae2c17e4ec0ce96902d
Binary files /dev/null and b/res/drawable-hdpi/filtershow_button_colors_curve.png differ
diff --git a/res/drawable-hdpi/ic_360pano_holo_light.png b/res/drawable-hdpi/ic_360pano_holo_light.png
new file mode 100644
index 0000000000000000000000000000000000000000..9873c17dda32b73af370846e21a1fd8efb97d5f2
Binary files /dev/null and b/res/drawable-hdpi/ic_360pano_holo_light.png differ
diff --git a/res/drawable-hdpi/ic_cameraalbum_overlay.png b/res/drawable-hdpi/ic_cameraalbum_overlay.png
new file mode 100644
index 0000000000000000000000000000000000000000..e58777f11dabfcf30a1ca6ca23907f787c965e35
Binary files /dev/null and b/res/drawable-hdpi/ic_cameraalbum_overlay.png differ
diff --git a/res/drawable-hdpi/ic_menu_savephoto.png b/res/drawable-hdpi/ic_menu_savephoto.png
new file mode 100644
index 0000000000000000000000000000000000000000..0c0309f0e6ca5eadb8a3c718481ec071aa42d754
Binary files /dev/null and b/res/drawable-hdpi/ic_menu_savephoto.png differ
diff --git a/res/drawable-hdpi/ic_menu_tiny_planet.png b/res/drawable-hdpi/ic_menu_tiny_planet.png
new file mode 100644
index 0000000000000000000000000000000000000000..12a05511c46441e4ee7e3ab5d7680cfa469af5e7
Binary files /dev/null and b/res/drawable-hdpi/ic_menu_tiny_planet.png differ
diff --git a/res/drawable-hdpi/ic_photoeditor_border.png b/res/drawable-hdpi/ic_photoeditor_border.png
new file mode 100644
index 0000000000000000000000000000000000000000..5fb0d518c6e837cfd957c86ba50aa543b24777ba
Binary files /dev/null and b/res/drawable-hdpi/ic_photoeditor_border.png differ
diff --git a/res/drawable-hdpi/ic_photoeditor_color.png b/res/drawable-hdpi/ic_photoeditor_color.png
new file mode 100644
index 0000000000000000000000000000000000000000..13972c20ac7341e660b371c12b77586447a1adb1
Binary files /dev/null and b/res/drawable-hdpi/ic_photoeditor_color.png differ
diff --git a/res/drawable-hdpi/ic_photoeditor_effects.png b/res/drawable-hdpi/ic_photoeditor_effects.png
new file mode 100644
index 0000000000000000000000000000000000000000..8ec892b9d6c73d8961bc669aa00aebeda9e1ec85
Binary files /dev/null and b/res/drawable-hdpi/ic_photoeditor_effects.png differ
diff --git a/res/drawable-hdpi/ic_photoeditor_fix.png b/res/drawable-hdpi/ic_photoeditor_fix.png
new file mode 100644
index 0000000000000000000000000000000000000000..18b11c7496d51561c572d3100d9e06c35bdeb4d9
Binary files /dev/null and b/res/drawable-hdpi/ic_photoeditor_fix.png differ
diff --git a/res/drawable-hdpi/list_divider_holo_dark.9.png b/res/drawable-hdpi/list_divider_holo_dark.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..986ab0b9746301f2dd9401829da09e00995621b3
Binary files /dev/null and b/res/drawable-hdpi/list_divider_holo_dark.9.png differ
diff --git a/res/drawable-hdpi/menu_dropdown_panel_holo_dark.9.png b/res/drawable-hdpi/menu_dropdown_panel_holo_dark.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d3d208578c61662986fdc16bd15c69759b48d6a
Binary files /dev/null and b/res/drawable-hdpi/menu_dropdown_panel_holo_dark.9.png differ
diff --git a/res/drawable-hdpi/placeholder_camera.png b/res/drawable-hdpi/placeholder_camera.png
new file mode 100644
index 0000000000000000000000000000000000000000..50a4d37c174a1094c99657b5793f0abb35392582
Binary files /dev/null and b/res/drawable-hdpi/placeholder_camera.png differ
diff --git a/res/drawable-hdpi/placeholder_empty.png b/res/drawable-hdpi/placeholder_empty.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f6020387b9474b37a87f2653d0f439317d4b9ad
Binary files /dev/null and b/res/drawable-hdpi/placeholder_empty.png differ
diff --git a/res/drawable-hdpi/placeholder_locked.png b/res/drawable-hdpi/placeholder_locked.png
new file mode 100644
index 0000000000000000000000000000000000000000..073b74f4cb460a90ef413e8e44d3bdadc202ec2f
Binary files /dev/null and b/res/drawable-hdpi/placeholder_locked.png differ
diff --git a/res/drawable-hdpi/text_select_handle_left.png b/res/drawable-hdpi/text_select_handle_left.png
new file mode 100644
index 0000000000000000000000000000000000000000..d2ed06dd5a17df8311a374a934fef685d5dbe641
Binary files /dev/null and b/res/drawable-hdpi/text_select_handle_left.png differ
diff --git a/res/drawable-hdpi/text_select_handle_right.png b/res/drawable-hdpi/text_select_handle_right.png
new file mode 100644
index 0000000000000000000000000000000000000000..e4192499b95b4f1bdc9e7333121a3e8f48f7d79a
Binary files /dev/null and b/res/drawable-hdpi/text_select_handle_right.png differ
diff --git a/res/drawable-mdpi/actionbar_translucent.9.png b/res/drawable-mdpi/actionbar_translucent.9.png
index f78fb8a76c5b28ff4ef38d69969ad9210142704d..a995d44769f66d0d4a127d4e2aa7150c62b42bdd 100644
Binary files a/res/drawable-mdpi/actionbar_translucent.9.png and b/res/drawable-mdpi/actionbar_translucent.9.png differ
diff --git a/res/drawable-mdpi/camera_crop.png b/res/drawable-mdpi/camera_crop.png
new file mode 100644
index 0000000000000000000000000000000000000000..cf385641c1c71679e24c72f858a9e354b267a55f
Binary files /dev/null and b/res/drawable-mdpi/camera_crop.png differ
diff --git a/res/drawable-mdpi/filtershow_button_colors_curve.png b/res/drawable-mdpi/filtershow_button_colors_curve.png
new file mode 100644
index 0000000000000000000000000000000000000000..e339d444a7ad99ee9d7c3dc68cd1b1309ae82596
Binary files /dev/null and b/res/drawable-mdpi/filtershow_button_colors_curve.png differ
diff --git a/res/drawable-mdpi/ic_360pano_holo_light.png b/res/drawable-mdpi/ic_360pano_holo_light.png
new file mode 100644
index 0000000000000000000000000000000000000000..7de1ec9f1ad8702569d9bda76b61857d5a9659ec
Binary files /dev/null and b/res/drawable-mdpi/ic_360pano_holo_light.png differ
diff --git a/res/drawable-mdpi/ic_cameraalbum_overlay.png b/res/drawable-mdpi/ic_cameraalbum_overlay.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d14c32e9384fdd11397e8013a78f032f0ec4739
Binary files /dev/null and b/res/drawable-mdpi/ic_cameraalbum_overlay.png differ
diff --git a/res/drawable-mdpi/ic_menu_savephoto.png b/res/drawable-mdpi/ic_menu_savephoto.png
new file mode 100644
index 0000000000000000000000000000000000000000..b1dd52abbd9c677b34f933f3fdbacb19a902e215
Binary files /dev/null and b/res/drawable-mdpi/ic_menu_savephoto.png differ
diff --git a/res/drawable-mdpi/ic_menu_tiny_planet.png b/res/drawable-mdpi/ic_menu_tiny_planet.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3c3030c16522414c0b442130dd847541f8a3553
Binary files /dev/null and b/res/drawable-mdpi/ic_menu_tiny_planet.png differ
diff --git a/res/drawable-mdpi/ic_photoeditor_border.png b/res/drawable-mdpi/ic_photoeditor_border.png
new file mode 100644
index 0000000000000000000000000000000000000000..2f19021bff23b42bdecfa84d404c0bfa047c9971
Binary files /dev/null and b/res/drawable-mdpi/ic_photoeditor_border.png differ
diff --git a/res/drawable-mdpi/ic_photoeditor_color.png b/res/drawable-mdpi/ic_photoeditor_color.png
new file mode 100644
index 0000000000000000000000000000000000000000..baec740198153217a05a5e48a822c323bce6e586
Binary files /dev/null and b/res/drawable-mdpi/ic_photoeditor_color.png differ
diff --git a/res/drawable-mdpi/ic_photoeditor_effects.png b/res/drawable-mdpi/ic_photoeditor_effects.png
new file mode 100644
index 0000000000000000000000000000000000000000..4547040808790d9083b8b59313441199ad996609
Binary files /dev/null and b/res/drawable-mdpi/ic_photoeditor_effects.png differ
diff --git a/res/drawable-mdpi/ic_photoeditor_fix.png b/res/drawable-mdpi/ic_photoeditor_fix.png
new file mode 100644
index 0000000000000000000000000000000000000000..c54a200e2002f304adf43306db83336a2c4f3f95
Binary files /dev/null and b/res/drawable-mdpi/ic_photoeditor_fix.png differ
diff --git a/res/drawable-mdpi/list_divider_holo_dark.9.png b/res/drawable-mdpi/list_divider_holo_dark.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..986ab0b9746301f2dd9401829da09e00995621b3
Binary files /dev/null and b/res/drawable-mdpi/list_divider_holo_dark.9.png differ
diff --git a/res/drawable-mdpi/menu_dropdown_panel_holo_dark.9.png b/res/drawable-mdpi/menu_dropdown_panel_holo_dark.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..460ec46eb0786706610e21ac9097de489cedfc33
Binary files /dev/null and b/res/drawable-mdpi/menu_dropdown_panel_holo_dark.9.png differ
diff --git a/res/drawable-mdpi/placeholder_camera.png b/res/drawable-mdpi/placeholder_camera.png
new file mode 100644
index 0000000000000000000000000000000000000000..f7cbb1a7f3ff81c46b4ea46cf3a22befc3d82381
Binary files /dev/null and b/res/drawable-mdpi/placeholder_camera.png differ
diff --git a/res/drawable-mdpi/placeholder_empty.png b/res/drawable-mdpi/placeholder_empty.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d6118dd7ab45a0204262a34b72101a7b4159b81
Binary files /dev/null and b/res/drawable-mdpi/placeholder_empty.png differ
diff --git a/res/drawable-mdpi/placeholder_locked.png b/res/drawable-mdpi/placeholder_locked.png
new file mode 100644
index 0000000000000000000000000000000000000000..500aaa0504a0c42278f96e11889c7385eb827cf7
Binary files /dev/null and b/res/drawable-mdpi/placeholder_locked.png differ
diff --git a/res/drawable-mdpi/text_select_handle_left.png b/res/drawable-mdpi/text_select_handle_left.png
new file mode 100644
index 0000000000000000000000000000000000000000..750cdea505da7f82d02c2ad1962efe55dc971ccb
Binary files /dev/null and b/res/drawable-mdpi/text_select_handle_left.png differ
diff --git a/res/drawable-mdpi/text_select_handle_right.png b/res/drawable-mdpi/text_select_handle_right.png
new file mode 100644
index 0000000000000000000000000000000000000000..fc3d14497fdba7835066a35a238003ffb0848cdb
Binary files /dev/null and b/res/drawable-mdpi/text_select_handle_right.png differ
diff --git a/res/drawable-xhdpi/actionbar_translucent.9.png b/res/drawable-xhdpi/actionbar_translucent.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..f4ed5fae9a67e1a437a4b955e380e0716c4cd108
Binary files /dev/null and b/res/drawable-xhdpi/actionbar_translucent.9.png differ
diff --git a/res/drawable-xhdpi/camera_crop.png b/res/drawable-xhdpi/camera_crop.png
new file mode 100644
index 0000000000000000000000000000000000000000..e0a53bcc92c560a5d1ca472950f034ce143f6e8c
Binary files /dev/null and b/res/drawable-xhdpi/camera_crop.png differ
diff --git a/res/drawable-xhdpi/filtershow_button_colors_curve.png b/res/drawable-xhdpi/filtershow_button_colors_curve.png
new file mode 100644
index 0000000000000000000000000000000000000000..8ab69aed45862f694c3e5ad8dbfee04ff15fa07b
Binary files /dev/null and b/res/drawable-xhdpi/filtershow_button_colors_curve.png differ
diff --git a/res/drawable-xhdpi/ic_360pano_holo_light.png b/res/drawable-xhdpi/ic_360pano_holo_light.png
new file mode 100644
index 0000000000000000000000000000000000000000..0ea43100448c56fdd15c9bd96138dabd16522783
Binary files /dev/null and b/res/drawable-xhdpi/ic_360pano_holo_light.png differ
diff --git a/res/drawable-xhdpi/ic_cameraalbum_overlay.png b/res/drawable-xhdpi/ic_cameraalbum_overlay.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf71eaacf4a538ed96aa1a04e160f090de954afe
Binary files /dev/null and b/res/drawable-xhdpi/ic_cameraalbum_overlay.png differ
diff --git a/res/drawable-xhdpi/ic_menu_savephoto.png b/res/drawable-xhdpi/ic_menu_savephoto.png
new file mode 100644
index 0000000000000000000000000000000000000000..4b1210a115d3a9f99545b255f0bfb1fbb17fa2f3
Binary files /dev/null and b/res/drawable-xhdpi/ic_menu_savephoto.png differ
diff --git a/res/drawable-xhdpi/ic_menu_tiny_planet.png b/res/drawable-xhdpi/ic_menu_tiny_planet.png
new file mode 100644
index 0000000000000000000000000000000000000000..565c7d26b7fb54c1479e2be01e349210e080b6cc
Binary files /dev/null and b/res/drawable-xhdpi/ic_menu_tiny_planet.png differ
diff --git a/res/drawable-xhdpi/ic_photoeditor_border.png b/res/drawable-xhdpi/ic_photoeditor_border.png
new file mode 100644
index 0000000000000000000000000000000000000000..df459d2d8ddf73aa2cd603085a6f17840ed40f93
Binary files /dev/null and b/res/drawable-xhdpi/ic_photoeditor_border.png differ
diff --git a/res/drawable-xhdpi/ic_photoeditor_color.png b/res/drawable-xhdpi/ic_photoeditor_color.png
new file mode 100644
index 0000000000000000000000000000000000000000..626d91b36b4352d5568452972838263ed918f97d
Binary files /dev/null and b/res/drawable-xhdpi/ic_photoeditor_color.png differ
diff --git a/res/drawable-xhdpi/ic_photoeditor_effects.png b/res/drawable-xhdpi/ic_photoeditor_effects.png
new file mode 100644
index 0000000000000000000000000000000000000000..0896f80a1d0e53e53a83b3ab261157cc6e4a7633
Binary files /dev/null and b/res/drawable-xhdpi/ic_photoeditor_effects.png differ
diff --git a/res/drawable-xhdpi/ic_photoeditor_fix.png b/res/drawable-xhdpi/ic_photoeditor_fix.png
new file mode 100644
index 0000000000000000000000000000000000000000..911637527cb612da483dba05bbbbc6a06f512659
Binary files /dev/null and b/res/drawable-xhdpi/ic_photoeditor_fix.png differ
diff --git a/res/drawable-xhdpi/list_divider_holo_dark.9.png b/res/drawable-xhdpi/list_divider_holo_dark.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..e62f011d45a2c4c61a60b6451bec014a557a5188
Binary files /dev/null and b/res/drawable-xhdpi/list_divider_holo_dark.9.png differ
diff --git a/res/drawable-xhdpi/menu_dropdown_panel_holo_dark.9.png b/res/drawable-xhdpi/menu_dropdown_panel_holo_dark.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..e2aff72f483ce4cc6ccddf17331249fa612d2a83
Binary files /dev/null and b/res/drawable-xhdpi/menu_dropdown_panel_holo_dark.9.png differ
diff --git a/res/drawable-xhdpi/placeholder_camera.png b/res/drawable-xhdpi/placeholder_camera.png
new file mode 100644
index 0000000000000000000000000000000000000000..e282a609ea95752c3464e6807c5c9ba291aaf740
Binary files /dev/null and b/res/drawable-xhdpi/placeholder_camera.png differ
diff --git a/res/drawable-xhdpi/placeholder_empty.png b/res/drawable-xhdpi/placeholder_empty.png
new file mode 100644
index 0000000000000000000000000000000000000000..83e8e8155166724820addc56142dc6347ad2cd29
Binary files /dev/null and b/res/drawable-xhdpi/placeholder_empty.png differ
diff --git a/res/drawable-xhdpi/placeholder_locked.png b/res/drawable-xhdpi/placeholder_locked.png
new file mode 100644
index 0000000000000000000000000000000000000000..c39d9e3d424837544781654dd68ef95b316e411e
Binary files /dev/null and b/res/drawable-xhdpi/placeholder_locked.png differ
diff --git a/res/drawable-xhdpi/text_select_handle_left.png b/res/drawable-xhdpi/text_select_handle_left.png
new file mode 100644
index 0000000000000000000000000000000000000000..98d10c92928ba133f010b2b9981340a3165771b7
Binary files /dev/null and b/res/drawable-xhdpi/text_select_handle_left.png differ
diff --git a/res/drawable-xhdpi/text_select_handle_right.png b/res/drawable-xhdpi/text_select_handle_right.png
new file mode 100644
index 0000000000000000000000000000000000000000..b3a0c9f47ec354ef8b09fa644c6d7a825a46f079
Binary files /dev/null and b/res/drawable-xhdpi/text_select_handle_right.png differ
diff --git a/res/drawable/filtershow_background.png b/res/drawable/filtershow_background.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7646c33a31b1bd1e17d9294cd4c23fa772c1b67
Binary files /dev/null and b/res/drawable/filtershow_background.png differ
diff --git a/res/drawable/filtershow_border_4x5.9.png b/res/drawable/filtershow_border_4x5.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..4cddf15507f097e7b8f5c231c522b92ea254ddfd
Binary files /dev/null and b/res/drawable/filtershow_border_4x5.9.png differ
diff --git a/res/drawable/filtershow_border_black.9.png b/res/drawable/filtershow_border_black.9.png
new file mode 100755
index 0000000000000000000000000000000000000000..24bb5e11d2282f31a5ae9a4310d1cfe8ffb52634
Binary files /dev/null and b/res/drawable/filtershow_border_black.9.png differ
diff --git a/res/drawable/filtershow_border_brush.9.png b/res/drawable/filtershow_border_brush.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..db51d2425b1aaffdd21b7030d3d4a896848e639c
Binary files /dev/null and b/res/drawable/filtershow_border_brush.9.png differ
diff --git a/res/drawable/filtershow_border_film.png b/res/drawable/filtershow_border_film.png
new file mode 100755
index 0000000000000000000000000000000000000000..9fbd637c130781cc0a251799474bafe9238cc41c
Binary files /dev/null and b/res/drawable/filtershow_border_film.png differ
diff --git a/res/drawable/filtershow_border_film2.9.png b/res/drawable/filtershow_border_film2.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..bef5e10616372de1b7cde4799dd1b8e7049ab237
Binary files /dev/null and b/res/drawable/filtershow_border_film2.9.png differ
diff --git a/res/drawable/filtershow_border_rounded_black.9.png b/res/drawable/filtershow_border_rounded_black.9.png
new file mode 100755
index 0000000000000000000000000000000000000000..590a3439363d5c0d1eed7bc8ae084f0f2c198679
Binary files /dev/null and b/res/drawable/filtershow_border_rounded_black.9.png differ
diff --git a/res/drawable/filtershow_border_rounded_white.9.png b/res/drawable/filtershow_border_rounded_white.9.png
new file mode 100755
index 0000000000000000000000000000000000000000..4ddc97a1964f902ad1c5b0d16b9305244906e1c7
Binary files /dev/null and b/res/drawable/filtershow_border_rounded_white.9.png differ
diff --git a/res/drawable/filtershow_border_white.9.png b/res/drawable/filtershow_border_white.9.png
new file mode 100755
index 0000000000000000000000000000000000000000..d8c88267e845f3eb65b0c70ffccfb7e9bf34d179
Binary files /dev/null and b/res/drawable/filtershow_border_white.9.png differ
diff --git a/res/drawable/filtershow_button_background.xml b/res/drawable/filtershow_button_background.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ee8a92dd9ea898aafac30b63e5a66f5026dac435
--- /dev/null
+++ b/res/drawable/filtershow_button_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/filtershow_button_border.png b/res/drawable/filtershow_button_border.png
new file mode 100644
index 0000000000000000000000000000000000000000..69195a9028f494fb396329a42d70b58e7b065352
Binary files /dev/null and b/res/drawable/filtershow_button_border.png differ
diff --git a/res/drawable/filtershow_button_colors.png b/res/drawable/filtershow_button_colors.png
new file mode 100644
index 0000000000000000000000000000000000000000..566773dbe3fe4103569162d5ccb12962c71bf6f3
Binary files /dev/null and b/res/drawable/filtershow_button_colors.png differ
diff --git a/res/drawable/filtershow_button_colors_contrast.png b/res/drawable/filtershow_button_colors_contrast.png
new file mode 100644
index 0000000000000000000000000000000000000000..ccb2dc63019eccb829fee90ff423b1df40724a6e
Binary files /dev/null and b/res/drawable/filtershow_button_colors_contrast.png differ
diff --git a/res/drawable/filtershow_button_colors_curve.png b/res/drawable/filtershow_button_colors_curve.png
new file mode 100644
index 0000000000000000000000000000000000000000..7046bd8dddc7bb1428b9456312a83769f4b5e095
Binary files /dev/null and b/res/drawable/filtershow_button_colors_curve.png differ
diff --git a/res/drawable/filtershow_button_colors_sharpen.png b/res/drawable/filtershow_button_colors_sharpen.png
new file mode 100644
index 0000000000000000000000000000000000000000..2bd0fff6d0758dbacd3de28c1cd61a8202f99aeb
Binary files /dev/null and b/res/drawable/filtershow_button_colors_sharpen.png differ
diff --git a/res/drawable/filtershow_button_colors_vignette.png b/res/drawable/filtershow_button_colors_vignette.png
new file mode 100644
index 0000000000000000000000000000000000000000..ac3d53fa9c9bece683565002cf7e72f7c8b70020
Binary files /dev/null and b/res/drawable/filtershow_button_colors_vignette.png differ
diff --git a/res/drawable/filtershow_button_current.png b/res/drawable/filtershow_button_current.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c4b379b5edacfc0d73caa96c4e8a4ac1c07356f
Binary files /dev/null and b/res/drawable/filtershow_button_current.png differ
diff --git a/res/drawable/filtershow_button_fx.png b/res/drawable/filtershow_button_fx.png
new file mode 100644
index 0000000000000000000000000000000000000000..c887fe4d1b384f433d4503d1af8e4740ac017851
Binary files /dev/null and b/res/drawable/filtershow_button_fx.png differ
diff --git a/res/drawable/filtershow_button_geometry.png b/res/drawable/filtershow_button_geometry.png
new file mode 100644
index 0000000000000000000000000000000000000000..4b8f3b8814f0dec5726d7b3fbf16de7739eda972
Binary files /dev/null and b/res/drawable/filtershow_button_geometry.png differ
diff --git a/res/drawable/filtershow_button_geometry_crop.png b/res/drawable/filtershow_button_geometry_crop.png
new file mode 100644
index 0000000000000000000000000000000000000000..eb7da1b21c7c42c2991b7658401a4fb0fde080ea
Binary files /dev/null and b/res/drawable/filtershow_button_geometry_crop.png differ
diff --git a/res/drawable/filtershow_button_geometry_flip.png b/res/drawable/filtershow_button_geometry_flip.png
new file mode 100644
index 0000000000000000000000000000000000000000..dd74813a7afd08680e6b05ddb5f9c1a9cc5e7a61
Binary files /dev/null and b/res/drawable/filtershow_button_geometry_flip.png differ
diff --git a/res/drawable/filtershow_button_geometry_rotate.png b/res/drawable/filtershow_button_geometry_rotate.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa50ce26f43ecaede07e469ad08f9ae3bde60b80
Binary files /dev/null and b/res/drawable/filtershow_button_geometry_rotate.png differ
diff --git a/res/drawable/filtershow_button_geometry_straighten.png b/res/drawable/filtershow_button_geometry_straighten.png
new file mode 100644
index 0000000000000000000000000000000000000000..309eb5ae297984aa71725013fb335a204ce5407c
Binary files /dev/null and b/res/drawable/filtershow_button_geometry_straighten.png differ
diff --git a/res/drawable/filtershow_button_operations.png b/res/drawable/filtershow_button_operations.png
new file mode 100644
index 0000000000000000000000000000000000000000..79e9a446b33e71a13757120a65c18c4429f0caad
Binary files /dev/null and b/res/drawable/filtershow_button_operations.png differ
diff --git a/res/drawable/filtershow_button_origin.png b/res/drawable/filtershow_button_origin.png
new file mode 100644
index 0000000000000000000000000000000000000000..0cd0bc28b6a2ca8ba7bb46b03ab221992876d533
Binary files /dev/null and b/res/drawable/filtershow_button_origin.png differ
diff --git a/res/drawable/filtershow_button_redo.png b/res/drawable/filtershow_button_redo.png
new file mode 100644
index 0000000000000000000000000000000000000000..9daa01c3498aa53bb166f54ed75d565333ff74bd
Binary files /dev/null and b/res/drawable/filtershow_button_redo.png differ
diff --git a/res/drawable/filtershow_button_selected_background.9.png b/res/drawable/filtershow_button_selected_background.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb412454cd4a2f1d3e421d800e84cd187071109e
Binary files /dev/null and b/res/drawable/filtershow_button_selected_background.9.png differ
diff --git a/res/drawable/filtershow_button_settings.png b/res/drawable/filtershow_button_settings.png
new file mode 100644
index 0000000000000000000000000000000000000000..df3925aca69194ec4086fc7c4c224d61399b432c
Binary files /dev/null and b/res/drawable/filtershow_button_settings.png differ
diff --git a/res/drawable/filtershow_button_show_original.png b/res/drawable/filtershow_button_show_original.png
new file mode 100644
index 0000000000000000000000000000000000000000..925954bebc7814f45b1290ac873ed37a841fc96a
Binary files /dev/null and b/res/drawable/filtershow_button_show_original.png differ
diff --git a/res/drawable/filtershow_button_undo.png b/res/drawable/filtershow_button_undo.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a7e0d1b8d99ec08f095c228f8f9fcfe653473a0
Binary files /dev/null and b/res/drawable/filtershow_button_undo.png differ
diff --git a/res/drawable/filtershow_fx_0000_vintage.png b/res/drawable/filtershow_fx_0000_vintage.png
new file mode 100644
index 0000000000000000000000000000000000000000..6783bb607b63c6f9d1aa2b0e5dedca2ddc92bdf6
Binary files /dev/null and b/res/drawable/filtershow_fx_0000_vintage.png differ
diff --git a/res/drawable/filtershow_fx_0001_instant.png b/res/drawable/filtershow_fx_0001_instant.png
new file mode 100644
index 0000000000000000000000000000000000000000..1652a4b9f00dd807fabfef1959a516767490ac4c
Binary files /dev/null and b/res/drawable/filtershow_fx_0001_instant.png differ
diff --git a/res/drawable/filtershow_fx_0002_bleach.png b/res/drawable/filtershow_fx_0002_bleach.png
new file mode 100644
index 0000000000000000000000000000000000000000..afb8a82bcb8ea5c291700d9ae9ad4695810f80c4
Binary files /dev/null and b/res/drawable/filtershow_fx_0002_bleach.png differ
diff --git a/res/drawable/filtershow_fx_0003_blue_crush.png b/res/drawable/filtershow_fx_0003_blue_crush.png
new file mode 100644
index 0000000000000000000000000000000000000000..2b238e3f8443ada266bad75cf2be69268810bc03
Binary files /dev/null and b/res/drawable/filtershow_fx_0003_blue_crush.png differ
diff --git a/res/drawable/filtershow_fx_0004_bw_contrast.png b/res/drawable/filtershow_fx_0004_bw_contrast.png
new file mode 100644
index 0000000000000000000000000000000000000000..40eb397dab4568ad6f29b753e4934f83782c9226
Binary files /dev/null and b/res/drawable/filtershow_fx_0004_bw_contrast.png differ
diff --git a/res/drawable/filtershow_fx_0005_punch.png b/res/drawable/filtershow_fx_0005_punch.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7e080379acb104c20948c09d94fe23134e6f392
Binary files /dev/null and b/res/drawable/filtershow_fx_0005_punch.png differ
diff --git a/res/drawable/filtershow_fx_0006_x_process.png b/res/drawable/filtershow_fx_0006_x_process.png
new file mode 100644
index 0000000000000000000000000000000000000000..5de9bb4849358440baa3f21738e1d1671a39914c
Binary files /dev/null and b/res/drawable/filtershow_fx_0006_x_process.png differ
diff --git a/res/drawable/filtershow_fx_0007_washout.png b/res/drawable/filtershow_fx_0007_washout.png
new file mode 100644
index 0000000000000000000000000000000000000000..20dfb5e066fad0c5d8f1acbb6815767307a69bd4
Binary files /dev/null and b/res/drawable/filtershow_fx_0007_washout.png differ
diff --git a/res/drawable/filtershow_fx_0008_washout_color.png b/res/drawable/filtershow_fx_0008_washout_color.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb6602bda45cb67b299d5557e1f905e7b81a5659
Binary files /dev/null and b/res/drawable/filtershow_fx_0008_washout_color.png differ
diff --git a/res/drawable/photopage_bottom_button_background.xml b/res/drawable/photopage_bottom_button_background.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0c772ad215cb254057b397e2408a0efa69ef459c
--- /dev/null
+++ b/res/drawable/photopage_bottom_button_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/res/drawable/transparent_button_background.xml b/res/drawable/transparent_button_background.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f1cc153c40e1e7a72c773c634ab88ae59be9c9b0
--- /dev/null
+++ b/res/drawable/transparent_button_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/action_bar_text.xml b/res/layout/action_bar_text.xml
index 2a1f0318ccfb84e4c0947332abe6f1903d80e0ca..a33264702e818cd12f4b4d982f88d2c2447a6446 100644
--- a/res/layout/action_bar_text.xml
+++ b/res/layout/action_bar_text.xml
@@ -23,5 +23,6 @@
android:gravity="center_vertical"
android:paddingLeft="18dp"
android:paddingRight="18dp"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:singleLine="true"
+ android:minHeight="?attr/listPreferredItemHeightSmall"
/>
diff --git a/res/layout/action_bar_two_line_text.xml b/res/layout/action_bar_two_line_text.xml
new file mode 100644
index 0000000000000000000000000000000000000000..95faffa1256594c724729fca532a8facd98e4611
--- /dev/null
+++ b/res/layout/action_bar_two_line_text.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
diff --git a/res/layout/action_mode.xml b/res/layout/action_mode.xml
index d4b3c23d3eb9e043bebe9e421562942b7c0bd10e..6c516e6180ebdc786c3ac68402b96ec934822107 100644
--- a/res/layout/action_mode.xml
+++ b/res/layout/action_mode.xml
@@ -29,8 +29,10 @@
android:layout_height="match_parent"
android:src="@drawable/dropdown_ic_arrow_normal_holo_dark" />