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" />