diff options
author | Chris Craik <ccraik@google.com> | 2014-01-06 12:43:42 -0800 |
---|---|---|
committer | Chris Craik <ccraik@google.com> | 2014-01-13 17:56:03 +0000 |
commit | a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4 (patch) | |
tree | a46a47658126ecf2f3365eab0c944b80bead0dd3 | |
parent | 58ce8de3fbb0cfed369b6a38d0f5c92104745eb4 (diff) | |
download | android_frameworks_ex-a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4.tar.gz android_frameworks_ex-a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4.tar.bz2 android_frameworks_ex-a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4.zip |
Import FrameSequence
Change-Id: I09b668925366a22e8e7e80e4abeae24b3a98c639
(cherry picked from commit a1265c3d8a20e805e0c45083d5c7d728d4b70009)
38 files changed, 2421 insertions, 0 deletions
diff --git a/framesequence/Android.mk b/framesequence/Android.mk new file mode 100644 index 0000000..efce18d --- /dev/null +++ b/framesequence/Android.mk @@ -0,0 +1,26 @@ +# +# Copyright (C) 2014 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. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := android-common-framesequence +#LOCAL_SDK_VERSION := 8 +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +include $(BUILD_STATIC_JAVA_LIBRARY) + +include $(call all-makefiles-under, $(LOCAL_PATH))
\ No newline at end of file diff --git a/framesequence/AndroidManifest.xml b/framesequence/AndroidManifest.xml new file mode 100644 index 0000000..f815643 --- /dev/null +++ b/framesequence/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.support.rastermill"> + <uses-sdk android:minSdkVersion="8"/> +</manifest> diff --git a/framesequence/build.xml b/framesequence/build.xml new file mode 100644 index 0000000..b977ef7 --- /dev/null +++ b/framesequence/build.xml @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="rastermill" default="help"> + + <!-- The local.properties file is created and updated by the 'android' tool. + It contains the path to the SDK. It should *NOT* be checked into + Version Control Systems. --> + <property file="local.properties" /> + + <!-- The ant.properties file can be created by you. It is only edited by the + 'android' tool to add properties to it. + This is the place to change some Ant specific build properties. + Here are some properties you may want to change/update: + + source.dir + The name of the source directory. Default is 'src'. + out.dir + The name of the output directory. Default is 'bin'. + + For other overridable properties, look at the beginning of the rules + files in the SDK, at tools/ant/build.xml + + Properties related to the SDK location or the project target should + be updated using the 'android' tool with the 'update' action. + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. + + --> + <property file="ant.properties" /> + + <property name="export.dir" value="exported_libs" /> + + <!-- if sdk.dir was not set from one of the property file, then + get it from the ANDROID_HOME env var. + This must be done before we load project.properties since + the proguard config can use sdk.dir --> + <property environment="env" /> + <condition property="sdk.dir" value="${env.ANDROID_HOME}"> + <isset property="env.ANDROID_HOME" /> + </condition> + + <!-- The project.properties file is created and updated by the 'android' + tool, as well as ADT. + + This contains project specific properties such as project target, and library + dependencies. Lower level build properties are stored in ant.properties + (or in .classpath for Eclipse projects). + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. --> + <loadproperties srcFile="project.properties" /> + + <!-- quick check on sdk.dir --> + <fail + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." + unless="sdk.dir" + /> + + <target name="-pre-build"> + <exec executable="ndk-build" failonerror="true"/> + </target> + + <target name="clean" depends="android_rules.clean"> + <exec executable="ndk-build" failonerror="true"> + <arg value="clean"/> + </exec> + <delete dir="${export.dir}" /> + </target> + + <!-- + Import per project custom build rules if present at the root of the project. + This is the place to put custom intermediary targets such as: + -pre-build + -pre-compile + -post-compile (This is typically used for code obfuscation. + Compiled code location: ${out.classes.absolute.dir} + If this is not done in place, override ${out.dex.input.absolute.dir}) + -post-package + -post-build + -pre-clean + --> + <import file="custom_rules.xml" optional="true" /> + + <!-- Import the actual build file. + + To customize existing targets, there are two options: + - Customize only one target: + - copy/paste the target into this file, *before* the + <import> task. + - customize it to your needs. + - Customize the whole content of build.xml + - copy/paste the content of the rules files (minus the top node) + into this file, replacing the <import> task. + - customize to your needs. + + *********************** + ****** IMPORTANT ****** + *********************** + In all cases you must update the value of version-tag below to read 'custom' instead of an integer, + in order to avoid having your file be overridden by tools such as "android update project" + --> + <!-- version-tag: 1 --> + <import file="${sdk.dir}/tools/ant/build.xml" /> + + <target name="-post-build"> + <delete dir="${export.dir}" /> + <copy file="${out.library.jar.file}" tofile="${export.dir}/rastermill.jar" /> + <copy todir="${export.dir}"> + <fileset dir="${native.libs.absolute.dir}"> + <include name="**/librastermill-native.so" /> + </fileset> + </copy> + </target> + +</project> diff --git a/framesequence/jni/Android.mk b/framesequence/jni/Android.mk new file mode 100644 index 0000000..7cc0af6 --- /dev/null +++ b/framesequence/jni/Android.mk @@ -0,0 +1,41 @@ +# +# Copyright (C) 2014 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. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +## Main library + +LOCAL_SHARED_LIBRARIES += liblog libjnigraphics +LOCAL_STATIC_LIBRARIES += libgif + +LOCAL_C_INCLUDES := \ + external/giflib + +LOCAL_MODULE := libframesequence +LOCAL_SRC_FILES := \ + BitmapDecoderJNI.cpp \ + FrameSequence.cpp \ + FrameSequenceJNI.cpp \ + FrameSequence_gif.cpp \ + JNIHelpers.cpp \ + Registry.cpp \ + Stream.cpp + +LOCAL_CFLAGS += -Wall -Wno-unused-parameter -Wno-unused-variable -Wno-overloaded-virtual +LOCAL_CFLAGS += -fvisibility=hidden + +include $(BUILD_SHARED_LIBRARY) diff --git a/framesequence/jni/Application.mk b/framesequence/jni/Application.mk new file mode 100644 index 0000000..eb51358 --- /dev/null +++ b/framesequence/jni/Application.mk @@ -0,0 +1,6 @@ +APP_PLATFORM := android-8 +APP_ABI := armeabi-v7a +LOCAL_ARM_NEON=true +ARCH_ARM_HAVE_NEON=true +# TODO: Have libjpeg do this +APP_CFLAGS := -D__ARM_HAVE_NEON=1 diff --git a/framesequence/jni/BitmapDecoderJNI.cpp b/framesequence/jni/BitmapDecoderJNI.cpp new file mode 100644 index 0000000..5fe04b4 --- /dev/null +++ b/framesequence/jni/BitmapDecoderJNI.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 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. + */ + +#define LOG_TAG "FancyDecoding" + +#include <android/bitmap.h> +#include <stdlib.h> +#include <stdio.h> +#include "FrameSequenceJNI.h" +#include "JNIHelpers.h" +#include "Stream.h" +#include "utils/log.h" + +void throwException(JNIEnv* env, const char* error) { + jclass clazz = env->FindClass("java/lang/RuntimeException"); + env->ThrowNew(clazz, error); +} + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + return -1; + } + if (FrameSequence_OnLoad(env)) { + ALOGE("Failed to load FrameSequence"); + return -1; + } + if (JavaStream_OnLoad(env)) { + ALOGE("Failed to load JavaStream"); + return -1; + } + + return JNI_VERSION_1_6; +} diff --git a/framesequence/jni/Color.h b/framesequence/jni/Color.h new file mode 100644 index 0000000..e49c64a --- /dev/null +++ b/framesequence/jni/Color.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 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 RASTERMILL_COLOR_H +#define RASTERMILL_COLOR_H + +#include <sys/types.h> + +typedef uint32_t Color8888; + +static const Color8888 COLOR_8888_ALPHA_MASK = 0xff000000; // TODO: handle endianness +static const Color8888 TRANSPARENT = 0x0; + +// TODO: handle endianness +#define ARGB_TO_COLOR8888(a, r, g, b) \ + ((a) << 24 | (b) << 16 | (g) << 8 | (r)) + +#endif // RASTERMILL_COLOR_H diff --git a/framesequence/jni/FrameSequence.cpp b/framesequence/jni/FrameSequence.cpp new file mode 100644 index 0000000..5c34425 --- /dev/null +++ b/framesequence/jni/FrameSequence.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 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 "FrameSequence.h" + +#include "Registry.h" + +FrameSequence* FrameSequence::create(Stream* stream) { + const RegistryEntry* entry = Registry::Find(stream); + return entry ? entry->createFrameSequence(stream) : 0; +} diff --git a/framesequence/jni/FrameSequence.h b/framesequence/jni/FrameSequence.h new file mode 100644 index 0000000..781b7c6 --- /dev/null +++ b/framesequence/jni/FrameSequence.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 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 RASTERMILL_FRAME_SEQUENCE_H +#define RASTERMILL_FRAME_SEQUENCE_H + +#include "Stream.h" +#include "Color.h" + +class FrameSequenceState { +public: + /** + * Produces a frame of animation in the output buffer, drawing (at minimum) the delta since + * previousFrameNr (the current contents of the buffer), or from scratch if previousFrameNr is + * negative + * + * Returns frame's delay time in milliseconds. + */ + virtual long drawFrame(int frameNr, + Color8888* outputPtr, int outputPixelStride, int previousFrameNr) = 0; + virtual ~FrameSequenceState() {} +}; + +class FrameSequence { +public: + /** + * Creates a FrameSequence using data from the data stream + * + * Type determined by header information in the stream + */ + static FrameSequence* create(Stream* stream); + + virtual ~FrameSequence() {} + virtual int getWidth() const = 0; + virtual int getHeight() const = 0; + virtual int getFrameCount() const = 0; + virtual bool isOpaque() const = 0; + + virtual FrameSequenceState* createState() const = 0; +}; + +#endif //RASTERMILL_FRAME_SEQUENCE_H diff --git a/framesequence/jni/FrameSequenceJNI.cpp b/framesequence/jni/FrameSequenceJNI.cpp new file mode 100644 index 0000000..90d0465 --- /dev/null +++ b/framesequence/jni/FrameSequenceJNI.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2013 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 <android/bitmap.h> +#include "JNIHelpers.h" +#include "utils/log.h" +#include "FrameSequence.h" + +#include "FrameSequenceJNI.h" + +#define JNI_PACKAGE "android/support/rastermill" + +static struct { + jclass clazz; + jmethodID ctor; +} gFrameSequenceClassInfo; + +//////////////////////////////////////////////////////////////////////////////// +// Frame sequence +//////////////////////////////////////////////////////////////////////////////// + +static jobject createJavaFrameSequence(JNIEnv* env, FrameSequence* frameSequence) { + if (!frameSequence) { + return NULL; + } + return env->NewObject(gFrameSequenceClassInfo.clazz, gFrameSequenceClassInfo.ctor, + reinterpret_cast<jint>(frameSequence), + frameSequence->getWidth(), + frameSequence->getHeight(), + frameSequence->getFrameCount(), + frameSequence->isOpaque()); +} + +static jobject nativeDecodeByteArray(JNIEnv* env, jobject clazz, + jbyteArray byteArray, jint offset, jint length) { + jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(byteArray, NULL)); + if (bytes == NULL) { + jniThrowException(env, ILLEGAL_STATE_EXEPTION, + "couldn't read array bytes"); + return NULL; + } + bytes += offset; + MemoryStream stream(bytes, length); + FrameSequence* frameSequence = FrameSequence::create(&stream); + env->ReleasePrimitiveArrayCritical(byteArray, bytes, 0); + return createJavaFrameSequence(env, frameSequence); +} + +static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, + jobject istream, jbyteArray byteArray) { + JavaInputStream stream(env, istream, byteArray); + FrameSequence* frameSequence = FrameSequence::create(&stream); + return createJavaFrameSequence(env, frameSequence); +} + +static void nativeDestroyFrameSequence(JNIEnv* env, jobject clazz, + jint frameSequenceInt) { + FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceInt); + delete frameSequence; +} + +static jint nativeCreateState(JNIEnv* env, jobject clazz, jint frameSequenceInt) { + FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceInt); + FrameSequenceState* state = frameSequence->createState(); + return reinterpret_cast<jint>(state); +} + +//////////////////////////////////////////////////////////////////////////////// +// Frame sequence state +//////////////////////////////////////////////////////////////////////////////// + +static void nativeDestroyState( + JNIEnv* env, jobject clazz, jint frameSequenceStateInt) { + FrameSequenceState* frameSequenceState = + reinterpret_cast<FrameSequenceState*>(frameSequenceStateInt); + delete frameSequenceState; +} + +static jlong JNICALL nativeGetFrame( + JNIEnv* env, jobject clazz, jint frameSequenceStateInt, jint frameNr, + jobject bitmap, jint previousFrameNr) { + FrameSequenceState* frameSequenceState = + reinterpret_cast<FrameSequenceState*>(frameSequenceStateInt); + int ret; + AndroidBitmapInfo info; + void* pixels; + if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { + + jniThrowException(env, ILLEGAL_STATE_EXEPTION, + "Couldn't get info from Bitmap "); + return 0; + } + + if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { + jniThrowException(env, ILLEGAL_STATE_EXEPTION, + "Bitmap pixels couldn't be locked"); + return 0; + } + + int pixelStride = info.stride >> 2; + jlong delayMs = frameSequenceState->drawFrame(frameNr, + (Color8888*) pixels, pixelStride, previousFrameNr); + + AndroidBitmap_unlockPixels(env, bitmap); + return delayMs; +} + +static JNINativeMethod gMethods[] = { + { "nativeDecodeByteArray", + "([BII)L" JNI_PACKAGE "/FrameSequence;", + (void*) nativeDecodeByteArray + }, + { "nativeDecodeStream", + "(Ljava/io/InputStream;[B)L" JNI_PACKAGE "/FrameSequence;", + (void*) nativeDecodeStream + }, + { "nativeDestroyFrameSequence", + "(I)V", + (void*) nativeDestroyFrameSequence + }, + { "nativeCreateState", + "(I)I", + (void*) nativeCreateState + }, + { "nativeGetFrame", + "(IILandroid/graphics/Bitmap;I)J", + (void*) nativeGetFrame + }, + { "nativeDestroyFrameSequence", + "(I)V", + (void*) nativeDestroyState + }, +}; + +jint FrameSequence_OnLoad(JNIEnv* env) { + // Get jclass with env->FindClass. + // Register methods with env->RegisterNatives. + gFrameSequenceClassInfo.clazz = env->FindClass(JNI_PACKAGE "/FrameSequence"); + if (!gFrameSequenceClassInfo.clazz) { + ALOGW("Failed to find " JNI_PACKAGE "/FrameSequence"); + return -1; + } + gFrameSequenceClassInfo.clazz = (jclass)env->NewGlobalRef(gFrameSequenceClassInfo.clazz); + + gFrameSequenceClassInfo.ctor = env->GetMethodID(gFrameSequenceClassInfo.clazz, "<init>", "(IIIIZ)V"); + if (!gFrameSequenceClassInfo.ctor) { + ALOGW("Failed to find constructor for FrameSequence - was it stripped?"); + return -1; + } + + return env->RegisterNatives(gFrameSequenceClassInfo.clazz, gMethods, METHOD_COUNT(gMethods)); +} diff --git a/framesequence/jni/FrameSequenceJNI.h b/framesequence/jni/FrameSequenceJNI.h new file mode 100644 index 0000000..a52df8a --- /dev/null +++ b/framesequence/jni/FrameSequenceJNI.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 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 RASTERMILL_FRAMESEQUENCE_JNI +#define RASTERMILL_FRAMESEQUENCE_JNI + +#include <jni.h> + +jint FrameSequence_OnLoad(JNIEnv* env); + +#endif // RASTERMILL_FRAMESEQUENCE_JNI diff --git a/framesequence/jni/FrameSequence_gif.cpp b/framesequence/jni/FrameSequence_gif.cpp new file mode 100644 index 0000000..e9f3ace --- /dev/null +++ b/framesequence/jni/FrameSequence_gif.cpp @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2013 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 <string.h> +#include "JNIHelpers.h" +#include "utils/log.h" +#include "utils/math.h" + +#include "FrameSequence_gif.h" + +#define GIF_DEBUG 0 + +// These constants are chosen to imitate common browser behavior +// Note that 0 delay is undefined behavior in the gif standard +static const long MIN_DELAY_MS = 20; +static const long DEFAULT_DELAY_MS = 100; + +static int streamReader(GifFileType* fileType, GifByteType* out, int size) { + Stream* stream = (Stream*) fileType->UserData; + return (int) stream->read(out, size); +} + +static Color8888 gifColorToColor8888(const GifColorType& color) { + return ARGB_TO_COLOR8888(0xff, color.Red, color.Green, color.Blue); +} + +static long getDelayMs(GraphicsControlBlock& gcb) { + long delayMs = gcb.DelayTime * 10; + if (delayMs < MIN_DELAY_MS) { + return DEFAULT_DELAY_MS; + } + return delayMs; +} + +static bool willBeCleared(const GraphicsControlBlock& gcb) { + return gcb.DisposalMode == DISPOSE_BACKGROUND || gcb.DisposalMode == DISPOSE_PREVIOUS; +} + +//////////////////////////////////////////////////////////////////////////////// +// Frame sequence +//////////////////////////////////////////////////////////////////////////////// + +FrameSequence_gif::FrameSequence_gif(Stream* stream) : + mBgColor(TRANSPARENT), mPreservedFrames(NULL), mRestoringFrames(NULL) { + mGif = DGifOpen(stream, streamReader, NULL); + if (!mGif) { + ALOGW("Gif load failed"); + return; + } + + if (DGifSlurp(mGif) != GIF_OK) { + ALOGW("Gif slurp failed"); + DGifCloseFile(mGif); + mGif = NULL; + return; + } + + long durationMs = 0; + int lastUnclearedFrame = -1; + mPreservedFrames = new bool[mGif->ImageCount]; + mRestoringFrames = new int[mGif->ImageCount]; + + GraphicsControlBlock gcb; + for (int i = 0; i < mGif->ImageCount; i++) { + const SavedImage& image = mGif->SavedImages[i]; + DGifSavedExtensionToGCB(mGif, i, &gcb); + + // timing + durationMs += getDelayMs(gcb); + + // preserve logic + mPreservedFrames[i] = false; + mRestoringFrames[i] = -1; + if (gcb.DisposalMode == DISPOSE_PREVIOUS && lastUnclearedFrame >= 0) { + mPreservedFrames[lastUnclearedFrame] = true; + mRestoringFrames[i] = lastUnclearedFrame; + } + if (!willBeCleared(gcb)) { + lastUnclearedFrame = i; + } + } + +#if GIF_DEBUG + ALOGD("FrameSequence_gif created with size %d %d, frames %d dur %ld", + mGif->SWidth, mGif->SHeight, mGif->ImageCount, durationMs); + for (int i = 0; i < mGif->ImageCount; i++) { + DGifSavedExtensionToGCB(mGif, i, &gcb); + ALOGD(" Frame %d - must preserve %d, restore point %d, trans color %d", + i, mPreservedFrames[i], mRestoringFrames[i], gcb.TransparentColor); + } +#endif + + if (mGif->SColorMap) { + // calculate bg color + GraphicsControlBlock gcb; + DGifSavedExtensionToGCB(mGif, 0, &gcb); + if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) { + mBgColor = gifColorToColor8888(mGif->SColorMap->Colors[mGif->SBackGroundColor]); + } + } +} + +FrameSequence_gif::~FrameSequence_gif() { + if (mGif) { + DGifCloseFile(mGif); + } + delete[] mPreservedFrames; + delete[] mRestoringFrames; +} + +FrameSequenceState* FrameSequence_gif::createState() const { + return new FrameSequenceState_gif(*this); +} + +//////////////////////////////////////////////////////////////////////////////// +// draw helpers +//////////////////////////////////////////////////////////////////////////////// + +// return true if area of 'target' is completely covers area of 'covered' +static bool checkIfCover(const GifImageDesc& target, const GifImageDesc& covered) { + return target.Left <= covered.Left + && covered.Left + covered.Width <= target.Left + target.Width + && target.Top <= covered.Top + && covered.Top + covered.Height <= target.Top + target.Height; +} + +static void copyLine(Color8888* dst, const unsigned char* src, const ColorMapObject* cmap, + int transparent, int width) { + for (; width > 0; width--, src++, dst++) { + if (*src != transparent) { + *dst = gifColorToColor8888(cmap->Colors[*src]); + } + } +} + +static void setLineColor(Color8888* dst, Color8888 color, int width) { + for (; width > 0; width--, dst++) { + *dst = color; + } +} + +static void getCopySize(const GifImageDesc& imageDesc, int maxWidth, int maxHeight, + GifWord& copyWidth, GifWord& copyHeight) { + copyWidth = imageDesc.Width; + if (imageDesc.Left + copyWidth > maxWidth) { + copyWidth = maxWidth - imageDesc.Left; + } + copyHeight = imageDesc.Height; + if (imageDesc.Top + copyHeight > maxHeight) { + copyHeight = maxHeight - imageDesc.Top; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Frame sequence state +//////////////////////////////////////////////////////////////////////////////// + +FrameSequenceState_gif::FrameSequenceState_gif(const FrameSequence_gif& frameSequence) : + mFrameSequence(frameSequence), mPreserveBuffer(NULL), mPreserveBufferFrame(-1) { +} + +FrameSequenceState_gif::~FrameSequenceState_gif() { + delete[] mPreserveBuffer; +} + +void FrameSequenceState_gif::savePreserveBuffer(Color8888* outputPtr, int outputPixelStride, int frameNr) { + if (frameNr == mPreserveBufferFrame) return; + + mPreserveBufferFrame = frameNr; + const int width = mFrameSequence.getWidth(); + const int height = mFrameSequence.getHeight(); + if (!mPreserveBuffer) { + mPreserveBuffer = new Color8888[width * height]; + } + for (int y = 0; y < height; y++) { + memcpy(mPreserveBuffer + width * y, + outputPtr + outputPixelStride * y, + width * 4); + } +} + +void FrameSequenceState_gif::restorePreserveBuffer(Color8888* outputPtr, int outputPixelStride) { + const int width = mFrameSequence.getWidth(); + const int height = mFrameSequence.getHeight(); + if (!mPreserveBuffer) { + ALOGD("preserve buffer not allocated! ah!"); + return; + } + for (int y = 0; y < height; y++) { + memcpy(outputPtr + outputPixelStride * y, + mPreserveBuffer + width * y, + width * 4); + } +} + +long FrameSequenceState_gif::drawFrame(int frameNr, + Color8888* outputPtr, int outputPixelStride, int previousFrameNr) { + + GifFileType* gif = mFrameSequence.getGif(); + if (!gif) { + ALOGD("Cannot drawFrame, mGif is NULL"); + return -1; + } + +#if GIF_DEBUG + ALOGD(" drawFrame on %p nr %d on addr %p, previous frame nr %d", + this, frameNr, outputPtr, previousFrameNr); +#endif + + const int height = mFrameSequence.getHeight(); + const int width = mFrameSequence.getWidth(); + + GraphicsControlBlock gcb; + + int start = max(previousFrameNr + 1, 0); + + for (int i = max(start - 1, 0); i < frameNr; i++) { + int neededPreservedFrame = mFrameSequence.getRestoringFrame(i); + if (neededPreservedFrame >= 0 && (mPreserveBufferFrame != neededPreservedFrame)) { +#if GIF_DEBUG + ALOGD("frame %d needs frame %d preserved, but %d is currently, so drawing from scratch", + i, neededPreservedFrame, mPreserveBufferFrame); +#endif + start = 0; + } + } + + for (int i = start; i <= frameNr; i++) { + DGifSavedExtensionToGCB(gif, i, &gcb); + const SavedImage& frame = gif->SavedImages[i]; + +#if GIF_DEBUG + bool frameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR; + ALOGD("producing frame %d, drawing frame %d (opaque %d, disp %d, del %d)", + frameNr, i, frameOpaque, gcb.DisposalMode, gcb.DelayTime); +#endif + if (i == 0) { + //clear bitmap + Color8888 bgColor = mFrameSequence.getBackgroundColor(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + outputPtr[y * outputPixelStride + x] = bgColor; + } + } + } else { + GraphicsControlBlock prevGcb; + DGifSavedExtensionToGCB(gif, i - 1, &prevGcb); + const SavedImage& prevFrame = gif->SavedImages[i - 1]; + bool prevFrameDisposed = willBeCleared(prevGcb); + + bool newFrameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR; + bool prevFrameCompletelyCovered = newFrameOpaque + && checkIfCover(frame.ImageDesc, prevFrame.ImageDesc); + + if (prevFrameDisposed && !prevFrameCompletelyCovered) { + switch (prevGcb.DisposalMode) { + case DISPOSE_BACKGROUND: { + Color8888* dst = outputPtr + prevFrame.ImageDesc.Left + + prevFrame.ImageDesc.Top * outputPixelStride; + + GifWord copyWidth, copyHeight; + getCopySize(prevFrame.ImageDesc, width, height, copyWidth, copyHeight); + for (; copyHeight > 0; copyHeight--) { + setLineColor(dst, TRANSPARENT, copyWidth); + dst += outputPixelStride; + } + } break; + case DISPOSE_PREVIOUS: { + restorePreserveBuffer(outputPtr, outputPixelStride); + } break; + } + } + + if (mFrameSequence.getPreservedFrame(i - 1)) { + // currently drawn frame will be restored by a following DISPOSE_PREVIOUS draw, so + // we preserve it + savePreserveBuffer(outputPtr, outputPixelStride, i - 1); + } + } + + bool willBeCleared = gcb.DisposalMode == DISPOSE_BACKGROUND + || gcb.DisposalMode == DISPOSE_PREVIOUS; + if (i == frameNr || !willBeCleared) { + const ColorMapObject* cmap = gif->SColorMap; + if (frame.ImageDesc.ColorMap) { + cmap = frame.ImageDesc.ColorMap; + } + + if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) { + ALOGW("Warning: potentially corrupt color map"); + } + + const unsigned char* src = (unsigned char*)frame.RasterBits; + Color8888* dst = outputPtr + frame.ImageDesc.Left + + frame.ImageDesc.Top * outputPixelStride; + GifWord copyWidth, copyHeight; + getCopySize(frame.ImageDesc, width, height, copyWidth, copyHeight); + for (; copyHeight > 0; copyHeight--) { + copyLine(dst, src, cmap, gcb.TransparentColor, copyWidth); + src += frame.ImageDesc.Width; + dst += outputPixelStride; + } + } + } + + return getDelayMs(gcb); +} + +//////////////////////////////////////////////////////////////////////////////// +// Registry +//////////////////////////////////////////////////////////////////////////////// + +#include "Registry.h" + +static bool isGif(void* header, int header_size) { + return !memcmp(GIF_STAMP, header, GIF_STAMP_LEN) + || !memcmp(GIF87_STAMP, header, GIF_STAMP_LEN) + || !memcmp(GIF89_STAMP, header, GIF_STAMP_LEN); +} + +static FrameSequence* createFramesequence(Stream* stream) { + return new FrameSequence_gif(stream); +} + +static RegistryEntry gEntry = { + GIF_STAMP_LEN, + isGif, + createFramesequence, + NULL, +}; +static Registry gRegister(gEntry); + diff --git a/framesequence/jni/FrameSequence_gif.h b/framesequence/jni/FrameSequence_gif.h new file mode 100644 index 0000000..fbc4959 --- /dev/null +++ b/framesequence/jni/FrameSequence_gif.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2013 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 RASTERMILL_FRAMESQUENCE_GIF_H +#define RASTERMILL_FRAMESQUENCE_GIF_H + +#include "config.h" +#include "gif_lib.h" + +#include "Stream.h" +#include "Color.h" +#include "FrameSequence.h" + +class FrameSequence_gif : public FrameSequence { +public: + FrameSequence_gif(Stream* stream); + virtual ~FrameSequence_gif(); + + virtual int getWidth() const { + return mGif ? mGif->SWidth : 0; + } + + virtual int getHeight() const { + return mGif ? mGif->SHeight : 0; + } + + virtual int getFrameCount() const { + return mGif ? mGif->ImageCount : 0; + } + + virtual bool isOpaque() const { + return (mBgColor & COLOR_8888_ALPHA_MASK) == COLOR_8888_ALPHA_MASK; + } + + virtual FrameSequenceState* createState() const; + + GifFileType* getGif() const { return mGif; } + Color8888 getBackgroundColor() const { return mBgColor; } + bool getPreservedFrame(int frameIndex) const { return mPreservedFrames[frameIndex]; } + int getRestoringFrame(int frameIndex) const { return mRestoringFrames[frameIndex]; } + +private: + GifFileType* mGif; + Color8888 mBgColor; + + // array of bool per frame - if true, frame data is used by a later DISPOSE_PREVIOUS frame + bool* mPreservedFrames; + + // array of ints per frame - if >= 0, points to the index of the preserve that frame needs + int* mRestoringFrames; +}; + +class FrameSequenceState_gif : public FrameSequenceState { +public: + FrameSequenceState_gif(const FrameSequence_gif& frameSequence); + virtual ~FrameSequenceState_gif(); + + // returns frame's delay time in ms + virtual long drawFrame(int frameNr, + Color8888* outputPtr, int outputPixelStride, int previousFrameNr); + +private: + void savePreserveBuffer(Color8888* outputPtr, int outputPixelStride, int frameNr); + void restorePreserveBuffer(Color8888* outputPtr, int outputPixelStride); + + const FrameSequence_gif& mFrameSequence; + Color8888* mPreserveBuffer; + int mPreserveBufferFrame; +}; + +#endif //RASTERMILL_FRAMESQUENCE_GIF_H diff --git a/framesequence/jni/JNIHelpers.cpp b/framesequence/jni/JNIHelpers.cpp new file mode 100644 index 0000000..dd0c818 --- /dev/null +++ b/framesequence/jni/JNIHelpers.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013 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 "JNIHelpers.h" +#include "utils/log.h" + +void jniThrowException(JNIEnv* env, const char* className, const char* msg) { + jclass clazz = env->FindClass(className); + if (!clazz) { + ALOGE("Unable to find exception class %s", className); + /* ClassNotFoundException now pending */ + return; + } + + if (env->ThrowNew(clazz, msg) != JNI_OK) { + ALOGE("Failed throwing '%s' '%s'", className, msg); + /* an exception, most likely OOM, will now be pending */ + } + env->DeleteLocalRef(clazz); +} diff --git a/framesequence/jni/JNIHelpers.h b/framesequence/jni/JNIHelpers.h new file mode 100644 index 0000000..bb850d2 --- /dev/null +++ b/framesequence/jni/JNIHelpers.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2013 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 RASTERMILL_JNIHELPERS_H +#define RASTERMILL_JNIHELPERS_H + +#include <jni.h> + +#define METHOD_COUNT(methodArray) (sizeof(methodArray) / sizeof(methodArray[0])) + +#define ILLEGAL_STATE_EXEPTION "java/lang/IllegalStateException" + +void jniThrowException(JNIEnv* env, const char* className, const char* msg); + + +#endif //RASTERMILL_JNIHELPERS_H diff --git a/framesequence/jni/Registry.cpp b/framesequence/jni/Registry.cpp new file mode 100644 index 0000000..125ac66 --- /dev/null +++ b/framesequence/jni/Registry.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 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 "Registry.h" + +#include "Stream.h" + +static Registry* gHead = 0; +static int gHeaderBytesRequired = 0; + +Registry::Registry(const RegistryEntry& entry) { + mImpl = entry; + + mNext = gHead; + gHead = this; + + if (gHeaderBytesRequired < entry.requiredHeaderBytes) { + gHeaderBytesRequired = entry.requiredHeaderBytes; + } +} + +const RegistryEntry* Registry::Find(Stream* stream) { + Registry* registry = gHead; + int headerSize = gHeaderBytesRequired; + char header[headerSize]; + headerSize = stream->peek(header, headerSize); + while (registry) { + if (headerSize >= registry->mImpl.requiredHeaderBytes + && registry->mImpl.checkHeader(header, headerSize)) { + return &(registry->mImpl); + } + registry = registry->mNext; + } + return 0; +} diff --git a/framesequence/jni/Registry.h b/framesequence/jni/Registry.h new file mode 100644 index 0000000..571c611 --- /dev/null +++ b/framesequence/jni/Registry.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013 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 RASTERMILL_REGISTRY_H +#define RASTERMILL_REGISTRY_H + +class FrameSequence; +class Decoder; +class Stream; + +struct RegistryEntry { + int requiredHeaderBytes; + bool (*checkHeader)(void* header, int header_size); + FrameSequence* (*createFrameSequence)(Stream* stream); + Decoder* (*createDecoder)(Stream* stream); +}; + +/** + * Template class for registering subclasses that can produce instances of themselves given a + * DataStream pointer. + * + * The super class / root constructable type only needs to define a single static construction + * meathod that creates an instance by iterating through all factory methods. + */ +class Registry { +public: + Registry(const RegistryEntry& entry); + + static const RegistryEntry* Find(Stream* stream); + +private: + RegistryEntry mImpl; + Registry* mNext; +}; + +#endif // RASTERMILL_REGISTRY_H diff --git a/framesequence/jni/Stream.cpp b/framesequence/jni/Stream.cpp new file mode 100644 index 0000000..b2e0c39 --- /dev/null +++ b/framesequence/jni/Stream.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2013 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. + */ + +#define LOG_TAG "Stream" + +#include "Stream.h" + +#include <string.h> + +#include "JNIHelpers.h" +#include "utils/log.h" +#include "utils/math.h" + +static struct { + jmethodID read; + jmethodID reset; +} gInputStreamClassInfo; + +Stream::Stream() + : mPeekBuffer(0) + , mPeekSize(0) + , mPeekOffset(0) { +} + +Stream::~Stream() { + delete mPeekBuffer; +} + +size_t Stream::peek(void* buffer, size_t size) { + size_t peek_remaining = mPeekSize - mPeekOffset; + if (size > peek_remaining) { + char* old_peek = mPeekBuffer; + mPeekBuffer = new char[size]; + if (old_peek) { + memcpy(mPeekBuffer, old_peek + mPeekOffset, peek_remaining); + delete old_peek; + } + size_t read = doRead(mPeekBuffer + mPeekOffset, size - peek_remaining); + mPeekOffset = 0; + mPeekSize = peek_remaining + read; + } + size = min(size, mPeekSize - mPeekOffset); + memcpy(buffer, mPeekBuffer + mPeekOffset, size); + return size; +} + +size_t Stream::read(void* buffer, size_t size) { + size_t bytes_read = 0; + size_t peek_remaining = mPeekSize - mPeekOffset; + if (peek_remaining) { + bytes_read = min(size, peek_remaining); + memcpy(buffer, mPeekBuffer + mPeekOffset, bytes_read); + mPeekOffset += bytes_read; + if (mPeekOffset == mPeekSize) { + delete mPeekBuffer; + mPeekBuffer = 0; + mPeekOffset = 0; + mPeekSize = 0; + } + size -= bytes_read; + buffer = ((char*) buffer) + bytes_read; + } + if (size) { + bytes_read += doRead(buffer, size); + } + return bytes_read; +} + +size_t MemoryStream::doRead(void* buffer, size_t size) { + size = min(size, mRemaining); + memcpy(buffer, mBuffer, size); + mBuffer += size; + mRemaining -= size; + return size; +} + +size_t FileStream::doRead(void* buffer, size_t size) { + return fread(buffer, 1, size, mFd); +} + +size_t JavaInputStream::doRead(void* dstBuffer, size_t size) { + size_t totalBytesRead = 0; + + do { + size_t requested = min(size, mByteArrayLength); + + jint bytesRead = mEnv->CallIntMethod(mInputStream, + gInputStreamClassInfo.read, mByteArray, 0, requested); + if (mEnv->ExceptionCheck() || bytesRead < 0) { + return 0; + } + + mEnv->GetByteArrayRegion(mByteArray, 0, bytesRead, (jbyte*)dstBuffer); + dstBuffer = (char*)dstBuffer + bytesRead; + totalBytesRead += bytesRead; + size -= bytesRead; + } while (size > 0); + + return totalBytesRead; +} + +jint JavaStream_OnLoad(JNIEnv* env) { + // Skip the verbose logging on error for these, as they won't be subject + // to obfuscators or similar and are thus unlikely to ever fail + jclass inputStreamClazz = env->FindClass("java/io/InputStream"); + if (!inputStreamClazz) { + return -1; + } + gInputStreamClassInfo.read = env->GetMethodID(inputStreamClazz, "read", "([BII)I"); + gInputStreamClassInfo.reset = env->GetMethodID(inputStreamClazz, "reset", "()V"); + if (!gInputStreamClassInfo.read || !gInputStreamClassInfo.reset) { + return -1; + } + return 0; +} diff --git a/framesequence/jni/Stream.h b/framesequence/jni/Stream.h new file mode 100644 index 0000000..f8f2427 --- /dev/null +++ b/framesequence/jni/Stream.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013 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 RASTERMILL_STREAM_H +#define RASTERMILL_STREAM_H + +#include <jni.h> +#include <stdio.h> +#include <sys/types.h> + +class Stream { +public: + Stream(); + virtual ~Stream(); + + size_t peek(void* buffer, size_t size); + size_t read(void* buffer, size_t size); + +protected: + virtual size_t doRead(void* buffer, size_t size) = 0; + +private: + char* mPeekBuffer; + size_t mPeekSize; + size_t mPeekOffset; +}; + +class MemoryStream : public Stream { +public: + MemoryStream(void* buffer, size_t size) : + mBuffer((char*)buffer), + mRemaining(size) {} + +protected: + virtual size_t doRead(void* buffer, size_t size); + +private: + char* mBuffer; + size_t mRemaining; +}; + +class FileStream : public Stream { +public: + FileStream(FILE* fd) : mFd(fd) {} + +protected: + virtual size_t doRead(void* buffer, size_t size); + +private: + FILE* mFd; +}; + +class JavaInputStream : public Stream { +public: + JavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray byteArray) : + mEnv(env), + mInputStream(inputStream), + mByteArray(byteArray), + mByteArrayLength(env->GetArrayLength(byteArray)) {} + +protected: + virtual size_t doRead(void* buffer, size_t size); + +private: + JNIEnv* mEnv; + const jobject mInputStream; + const jbyteArray mByteArray; + const size_t mByteArrayLength; +}; + +jint JavaStream_OnLoad(JNIEnv* env); + +#endif //RASTERMILL_STREAM_H diff --git a/framesequence/jni/utils/log.h b/framesequence/jni/utils/log.h new file mode 100644 index 0000000..5e15f30 --- /dev/null +++ b/framesequence/jni/utils/log.h @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2013 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 LOG_H_ +#define LOG_H_ + +#include <android/log.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// --------------------------------------------------------------------- + +/* + * Normally we strip ALOGV (VERBOSE messages) from release builds. + * You can modify this (for example with "#define LOG_NDEBUG 0" + * at the top of your source file) to change that behavior. + */ +#ifndef LOG_NDEBUG +#ifdef NDEBUG +#define LOG_NDEBUG 1 +#else +#define LOG_NDEBUG 0 +#endif +#endif + +/* + * This is the local tag used for the following simplified + * logging macros. You can change this preprocessor definition + * before using the other macros to change the tag. + */ +#ifndef LOG_TAG +#define LOG_TAG "RasterMill" +#endif + +// --------------------------------------------------------------------- + +/* + * Simplified macro to send a verbose log message using the current LOG_TAG. + */ +#ifndef ALOGV +#if LOG_NDEBUG +#define ALOGV(...) ((void)0) +#else +#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) +#endif +#endif + +#define CONDITION(cond) (__builtin_expect((cond)!=0, 0)) + +#ifndef ALOGV_IF +#if LOG_NDEBUG +#define ALOGV_IF(cond, ...) ((void)0) +#else +#define ALOGV_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif +#endif + +/* + * Simplified macro to send a debug log message using the current LOG_TAG. + */ +#ifndef ALOGD +#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef ALOGD_IF +#define ALOGD_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an info log message using the current LOG_TAG. + */ +#ifndef ALOGI +#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef ALOGI_IF +#define ALOGI_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send a warning log message using the current LOG_TAG. + */ +#ifndef ALOGW +#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef ALOGW_IF +#define ALOGW_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an error log message using the current LOG_TAG. + */ +#ifndef ALOGE +#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef ALOGE_IF +#define ALOGE_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +// --------------------------------------------------------------------- + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * verbose priority. + */ +#ifndef IF_ALOGV +#if LOG_NDEBUG +#define IF_ALOGV() if (false) +#else +#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG) +#endif +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * debug priority. + */ +#ifndef IF_ALOGD +#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * info priority. + */ +#ifndef IF_ALOGI +#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * warn priority. + */ +#ifndef IF_ALOGW +#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * error priority. + */ +#ifndef IF_ALOGE +#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG) +#endif + +// --------------------------------------------------------------------- + +/* + * Log a fatal error. If the given condition fails, this stops program + * execution like a normal assertion, but also generating the given message. + * It is NOT stripped from release builds. Note that the condition test + * is -inverted- from the normal assert() semantics. + */ +#ifndef LOG_ALWAYS_FATAL_IF +#define LOG_ALWAYS_FATAL_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)android_printAssert(#cond, LOG_TAG, ## __VA_ARGS__)) \ + : (void)0 ) +#endif + +#ifndef LOG_ALWAYS_FATAL +#define LOG_ALWAYS_FATAL(...) \ + ( ((void)android_printAssert(NULL, LOG_TAG, ## __VA_ARGS__)) ) +#endif + +/* + * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that + * are stripped out of release builds. + */ +#if LOG_NDEBUG + +#ifndef LOG_FATAL_IF +#define LOG_FATAL_IF(cond, ...) ((void)0) +#endif +#ifndef LOG_FATAL +#define LOG_FATAL(...) ((void)0) +#endif + +#else + +#ifndef LOG_FATAL_IF +#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ## __VA_ARGS__) +#endif +#ifndef LOG_FATAL +#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__) +#endif + +#endif + +/* + * Assertion that generates a log message when the assertion fails. + * Stripped out of release builds. Uses the current LOG_TAG. + */ +#ifndef ALOG_ASSERT +#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__) +//#define ALOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond) +#endif + +// --------------------------------------------------------------------- + +/* + * Basic log message macro. + * + * Example: + * ALOG(LOG_WARN, NULL, "Failed with error %d", errno); + * + * The second argument may be NULL or "" to indicate the "global" tag. + */ +#ifndef ALOG +#define ALOG(priority, tag, ...) \ + LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) +#endif + +/* + * Log macro that allows you to specify a number for the priority. + */ +#ifndef LOG_PRI +#define LOG_PRI(priority, tag, ...) \ + __android_log_print(priority, tag, __VA_ARGS__) +#endif + +/* + * Log macro that allows you to pass in a varargs ("args" is a va_list). + */ +#ifndef LOG_PRI_VA +#define LOG_PRI_VA(priority, tag, fmt, args) \ + __android_log_vprint(priority, NULL, tag, fmt, args) +#endif + +/* + * Conditional given a desired logging priority and tag. + */ +#ifndef IF_ALOG +#define IF_ALOG(priority, tag) \ + if (__android_log_assert(ANDROID_##priority, tag)) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* LOG_H_ */ diff --git a/framesequence/jni/utils/math.h b/framesequence/jni/utils/math.h new file mode 100644 index 0000000..87f100b --- /dev/null +++ b/framesequence/jni/utils/math.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 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 MATH_H_ +#define MATH_H_ + +#define max(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define min(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) + +#endif /* MATH_H_ */ diff --git a/framesequence/project.properties b/framesequence/project.properties new file mode 100644 index 0000000..db721fd --- /dev/null +++ b/framesequence/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-8 +android.library=true diff --git a/framesequence/samples/RastermillSamples/Android.mk b/framesequence/samples/RastermillSamples/Android.mk new file mode 100644 index 0000000..bb8920f --- /dev/null +++ b/framesequence/samples/RastermillSamples/Android.mk @@ -0,0 +1,40 @@ +# Copyright (C) 2014 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_PACKAGE_NAME := FrameSequenceSample + +# java dependency +LOCAL_STATIC_JAVA_LIBRARIES += android-common-framesequence + +# native dependency +ifneq (,$(TARGET_BUILD_APPS)) + LOCAL_JNI_SHARED_LIBRARIES := libframesequence +else + LOCAL_REQUIRED_MODULES := libframesequence +endif + +# proguard for framesequence library code +LOCAL_PROGUARD_FLAG_FILES := proguard.flags + +LOCAL_SDK_VERSION := 19 + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, res) +LOCAL_AAPT_FLAGS := --auto-add-overlay +LOCAL_AAPT_FLAGS += --extra-packages com.android.rastermill.samples + +include $(BUILD_PACKAGE) diff --git a/framesequence/samples/RastermillSamples/AndroidManifest.xml b/framesequence/samples/RastermillSamples/AndroidManifest.xml new file mode 100644 index 0000000..b554021 --- /dev/null +++ b/framesequence/samples/RastermillSamples/AndroidManifest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.rastermill.samples" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk + android:minSdkVersion="15" + android:targetSdkVersion="18" /> + + <application + android:allowBackup="true" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" > + <activity + android:name=".SamplesList" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".AnimatedGifTest" /> + </application> + +</manifest> diff --git a/framesequence/samples/RastermillSamples/build.xml b/framesequence/samples/RastermillSamples/build.xml new file mode 100644 index 0000000..5e55b4e --- /dev/null +++ b/framesequence/samples/RastermillSamples/build.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="RastermillSamples" default="help"> + + <!-- The local.properties file is created and updated by the 'android' tool. + It contains the path to the SDK. It should *NOT* be checked into + Version Control Systems. --> + <property file="local.properties" /> + + <!-- The ant.properties file can be created by you. It is only edited by the + 'android' tool to add properties to it. + This is the place to change some Ant specific build properties. + Here are some properties you may want to change/update: + + source.dir + The name of the source directory. Default is 'src'. + out.dir + The name of the output directory. Default is 'bin'. + + For other overridable properties, look at the beginning of the rules + files in the SDK, at tools/ant/build.xml + + Properties related to the SDK location or the project target should + be updated using the 'android' tool with the 'update' action. + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. + + --> + <property file="ant.properties" /> + + <!-- if sdk.dir was not set from one of the property file, then + get it from the ANDROID_HOME env var. + This must be done before we load project.properties since + the proguard config can use sdk.dir --> + <property environment="env" /> + <condition property="sdk.dir" value="${env.ANDROID_HOME}"> + <isset property="env.ANDROID_HOME" /> + </condition> + + <!-- The project.properties file is created and updated by the 'android' + tool, as well as ADT. + + This contains project specific properties such as project target, and library + dependencies. Lower level build properties are stored in ant.properties + (or in .classpath for Eclipse projects). + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. --> + <loadproperties srcFile="project.properties" /> + + <!-- quick check on sdk.dir --> + <fail + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." + unless="sdk.dir" + /> + + <target name="-pre-build"> + <ant dir="../../" target="release" inheritAll="false" /> + <copy todir="libs"> + <fileset dir="../../exported_libs" /> + </copy> + </target> + + <!-- + Import per project custom build rules if present at the root of the project. + This is the place to put custom intermediary targets such as: + -pre-build + -pre-compile + -post-compile (This is typically used for code obfuscation. + Compiled code location: ${out.classes.absolute.dir} + If this is not done in place, override ${out.dex.input.absolute.dir}) + -post-package + -post-build + -pre-clean + --> + <import file="custom_rules.xml" optional="true" /> + + <!-- Import the actual build file. + + To customize existing targets, there are two options: + - Customize only one target: + - copy/paste the target into this file, *before* the + <import> task. + - customize it to your needs. + - Customize the whole content of build.xml + - copy/paste the content of the rules files (minus the top node) + into this file, replacing the <import> task. + - customize to your needs. + + *********************** + ****** IMPORTANT ****** + *********************** + In all cases you must update the value of version-tag below to read 'custom' instead of an integer, + in order to avoid having your file be overridden by tools such as "android update project" + --> + <!-- version-tag: 1 --> + <import file="${sdk.dir}/tools/ant/build.xml" /> + +</project> diff --git a/framesequence/samples/RastermillSamples/proguard.flags b/framesequence/samples/RastermillSamples/proguard.flags new file mode 100644 index 0000000..4acde2d --- /dev/null +++ b/framesequence/samples/RastermillSamples/proguard.flags @@ -0,0 +1,3 @@ +-keep class android.support.rastermill.** { + *; +} diff --git a/framesequence/samples/RastermillSamples/project.properties b/framesequence/samples/RastermillSamples/project.properties new file mode 100644 index 0000000..ce39f2d --- /dev/null +++ b/framesequence/samples/RastermillSamples/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-18 diff --git a/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png b/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..96a442e --- /dev/null +++ b/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png diff --git a/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png b/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..359047d --- /dev/null +++ b/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png diff --git a/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png b/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..71c6d76 --- /dev/null +++ b/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png diff --git a/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml b/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml new file mode 100644 index 0000000..0b9a2df --- /dev/null +++ b/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:id="@+id/imageview" + android:layout_width="match_parent" + android:layout_height="300dp" /> + <LinearLayout + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <Button + android:id="@+id/start" + android:text="@string/start" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <Button + android:id="@+id/stop" + android:text="@string/stop" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <Button + android:id="@+id/vis" + android:text="@string/vis" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <Button + android:id="@+id/invis" + android:text="@string/invis" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/framesequence/samples/RastermillSamples/res/raw/animated.gif b/framesequence/samples/RastermillSamples/res/raw/animated.gif Binary files differnew file mode 100644 index 0000000..51baf15 --- /dev/null +++ b/framesequence/samples/RastermillSamples/res/raw/animated.gif diff --git a/framesequence/samples/RastermillSamples/res/values/strings.xml b/framesequence/samples/RastermillSamples/res/values/strings.xml new file mode 100644 index 0000000..8b85b8e --- /dev/null +++ b/framesequence/samples/RastermillSamples/res/values/strings.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">Rastermill Samples</string> + <string name="action_settings">Settings</string> + + <string name="start">start</string> + <string name="stop">stop</string> + <string name="vis">vis</string> + <string name="invis">invis</string> + +</resources> diff --git a/framesequence/samples/RastermillSamples/res/values/styles.xml b/framesequence/samples/RastermillSamples/res/values/styles.xml new file mode 100644 index 0000000..737bdc3 --- /dev/null +++ b/framesequence/samples/RastermillSamples/res/values/styles.xml @@ -0,0 +1,7 @@ +<resources> + + <!-- Application theme. --> + <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> + </style> + +</resources> diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java new file mode 100644 index 0000000..45d3415 --- /dev/null +++ b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013 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.rastermill.samples; + +import android.app.Activity; +import android.os.Bundle; +import android.support.rastermill.FrameSequence; +import android.support.rastermill.FrameSequenceDrawable; +import android.view.View; +import android.widget.ImageView; + +import java.io.InputStream; + +public class AnimatedGifTest extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.basic_test_activity); + ImageView imageView = (ImageView) findViewById(R.id.imageview); + + InputStream is = getResources().openRawResource(R.raw.animated); + + FrameSequence fs = FrameSequence.decodeStream(is); + final FrameSequenceDrawable drawable = new FrameSequenceDrawable(fs); + imageView.setImageDrawable(drawable); + + findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + drawable.start(); + } + }); + findViewById(R.id.stop).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + drawable.stop(); + } + }); + findViewById(R.id.vis).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + drawable.setVisible(true, true); + } + }); + findViewById(R.id.invis).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + drawable.setVisible(false, true); + } + }); + } +} diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java new file mode 100644 index 0000000..0447537 --- /dev/null +++ b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 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.rastermill.samples; + +import android.app.ListActivity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ListView; +import android.widget.SimpleAdapter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class SamplesList extends ListActivity { + + static final String KEY_NAME = "name"; + static final String KEY_CLASS = "clazz"; + + static Map<String,?> makeSample(String name, Class<?> activity) { + Map<String,Object> ret = new HashMap<String,Object>(); + ret.put(KEY_NAME, name); + ret.put(KEY_CLASS, activity); + return ret; + } + + @SuppressWarnings("serial") + static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{ + add(makeSample("Animation Test", AnimatedGifTest.class)); + }}; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setListAdapter(new SimpleAdapter(this, SAMPLES, + android.R.layout.simple_list_item_1, new String[] { KEY_NAME }, + new int[] { android.R.id.text1 })); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Class<?> clazz = (Class<?>) SAMPLES.get(position).get(KEY_CLASS); + startActivity(new Intent(this, clazz)); + } + +} diff --git a/framesequence/src/android/support/rastermill/FrameSequence.java b/framesequence/src/android/support/rastermill/FrameSequence.java new file mode 100644 index 0000000..5881ea9 --- /dev/null +++ b/framesequence/src/android/support/rastermill/FrameSequence.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2013 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 android.support.rastermill; + +import android.graphics.Bitmap; + +import java.io.InputStream; + +public class FrameSequence { + static { + System.loadLibrary("framesequence"); + } + + private final int mNativeFrameSequence; + private final int mWidth; + private final int mHeight; + private final int mFrameCount; + private final boolean mOpaque; + + public int getWidth() { return mWidth; } + public int getHeight() { return mHeight; } + public int getFrameCount() { return mFrameCount; } + public boolean isOpaque() { return mOpaque; } + + private static native FrameSequence nativeDecodeByteArray(byte[] data, int offset, int length); + private static native FrameSequence nativeDecodeStream(InputStream is, byte[] tempStorage); + private static native void nativeDestroyFrameSequence(int nativeFrameSequence); + private static native int nativeCreateState(int nativeFrameSequence); + private static native void nativeDestroyState(int nativeState); + private static native long nativeGetFrame(int nativeState, int frameNr, + Bitmap output, int previousFrameNr); + + @SuppressWarnings("unused") // called by native + private FrameSequence(int nativeFrameSequence, int width, int height, + int frameCount, boolean opaque) { + mNativeFrameSequence = nativeFrameSequence; + mWidth = width; + mHeight = height; + mFrameCount = frameCount; + mOpaque = opaque; + } + + public static FrameSequence decodeByteArray(byte[] data) { + return decodeByteArray(data, 0, data.length); + } + + public static FrameSequence decodeByteArray(byte[] data, int offset, int length) { + if (data == null) throw new IllegalArgumentException(); + if (offset < 0 || length < 0 || (offset + length > data.length)) { + throw new IllegalArgumentException("invalid offset/length parameters"); + } + return nativeDecodeByteArray(data, offset, length); + } + + public static FrameSequence decodeStream(InputStream stream) { + if (stream == null) throw new IllegalArgumentException(); + byte[] tempStorage = new byte[16 * 1024]; // TODO: use buffer pool + return nativeDecodeStream(stream, tempStorage); + } + + State createState() { + if (mNativeFrameSequence == 0) { + throw new IllegalStateException("attempted to use incorrectly built FrameSequence"); + } + + int nativeState = nativeCreateState(mNativeFrameSequence); + if (nativeState == 0) { + return null; + } + return new State(nativeState); + } + + @Override + protected void finalize() throws Throwable { + try { + if (mNativeFrameSequence != 0) nativeDestroyFrameSequence(mNativeFrameSequence); + } finally { + super.finalize(); + } + } + + /** + * Playback state used when moving frames forward in a frame sequence. + * + * Note that this doesn't require contiguous frames to be rendered, it just stores + * information (in the case of gif, a recall buffer) that will be used to construct + * frames based upon data recorded before previousFrameNr. + * + * Note: {@link #recycle()} *must* be called before the object is GC'd to free native resources + * + * Note: State holds a native ref to its FrameSequence instance, so its FrameSequence should + * remain ref'd while it is in use + */ + static class State { + private int mNativeState; + + public State(int nativeState) { + mNativeState = nativeState; + } + + public void recycle() { + if (mNativeState != 0) { + nativeDestroyState(mNativeState); + mNativeState = 0; + } + } + + // TODO: consider adding alternate API for drawing into a SurfaceTexture + public long getFrame(int frameNr, Bitmap output, int previousFrameNr) { + if (output == null || output.getConfig() != Bitmap.Config.ARGB_8888) { + throw new IllegalArgumentException("Bitmap passed must be non-null and ARGB_8888"); + } + if (mNativeState == 0) { + throw new IllegalStateException("attempted to draw recycled FrameSequenceState"); + } + return nativeGetFrame(mNativeState, frameNr, output, previousFrameNr); + } + } + + // TODO: add recycle() cleanup method +} diff --git a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java new file mode 100644 index 0000000..94f4da0 --- /dev/null +++ b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2013 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 android.support.rastermill; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.SystemClock; + +public class FrameSequenceDrawable extends Drawable implements Animatable, Runnable { + private static final Object sLock = new Object(); + private static HandlerThread sDecodingThread; + private static Handler sDecodingThreadHandler; + private static void initializeDecodingThread() { + synchronized (sLock) { + if (sDecodingThread != null) return; + + sDecodingThread = new HandlerThread("FrameSequence decoding thread"); + sDecodingThread.start(); + sDecodingThreadHandler = new Handler(sDecodingThread.getLooper()); + } + } + + private final FrameSequence mFrameSequence; + private final FrameSequence.State mFrameSequenceState; + + private final Paint mPaint; + private final Rect mSrcRect; + + //Protects the fields below + private final Object mLock = new Object(); + + private Bitmap mFrontBitmap; + private Bitmap mBackBitmap; + + private static final int STATE_SCHEDULED = 1; + private static final int STATE_DECODING = 2; + private static final int STATE_WAITING_TO_SWAP = 3; + private static final int STATE_READY_TO_SWAP = 4; + + private int mState; + + private long mLastSwap; + private int mNextFrameToDecode; + + /** + * Runs on decoding thread, only modifies mBackBitmap's pixels + */ + private Runnable mDecodeRunnable = new Runnable() { + @Override + public void run() { + int nextFrame; + Bitmap bitmap; + synchronized (mLock) { + nextFrame = mNextFrameToDecode; + if (nextFrame < 0) { + return; + } + bitmap = mBackBitmap; + mState = STATE_DECODING; + } + int lastFrame = nextFrame - 2; + long invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame); + + synchronized (mLock) { + if (mNextFrameToDecode < 0 || mState != STATE_DECODING) return; + invalidateTimeMs += mLastSwap; + + mState = STATE_WAITING_TO_SWAP; + } + scheduleSelf(FrameSequenceDrawable.this, invalidateTimeMs); + } + }; + + + public FrameSequenceDrawable(FrameSequence frameSequence) { + if (frameSequence == null) throw new IllegalArgumentException(); + + mFrameSequence = frameSequence; + mFrameSequenceState = frameSequence.createState(); + // TODO: add callback for requesting bitmaps, to allow for reuse + final int width = frameSequence.getWidth(); + final int height = frameSequence.getHeight(); + + mFrontBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mBackBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mSrcRect = new Rect(0, 0, width, height); + mPaint = new Paint(); + mPaint.setFilterBitmap(true); + + mLastSwap = 0; + + mNextFrameToDecode = -1; + mFrameSequenceState.getFrame(0, mFrontBitmap, -1); + initializeDecodingThread(); + } + + @Override + protected void finalize() throws Throwable { + try { + mFrontBitmap.recycle(); + mBackBitmap.recycle(); + mFrameSequenceState.recycle(); + } finally { + super.finalize(); + } + } + + @Override + public void draw(Canvas canvas) { + synchronized (mLock) { + if (isRunning() && mState == STATE_READY_TO_SWAP) { + // Because draw has occurred, the view system is guaranteed to no longer hold a + // reference to the old mFrontBitmap, so we now use it to produce the next frame + Bitmap tmp = mBackBitmap; + mBackBitmap = mFrontBitmap; + mFrontBitmap = tmp; + + mLastSwap = SystemClock.uptimeMillis(); + scheduleDecodeLocked(); + } + } + + canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint); + } + + private void scheduleDecodeLocked() { + mState = STATE_SCHEDULED; + mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount(); + sDecodingThreadHandler.post(mDecodeRunnable); + } + + @Override + public void run() { + // set ready to swap + synchronized (mLock) { + if (mState != STATE_WAITING_TO_SWAP || mNextFrameToDecode < 0) return; + mState = STATE_READY_TO_SWAP; + } + invalidateSelf(); + } + + @Override + public void start() { + if (!isRunning()) { + synchronized (mLock) { + if (mState == STATE_SCHEDULED) return; // already scheduled + scheduleDecodeLocked(); + } + } + } + + @Override + public void stop() { + if (isRunning()) { + unscheduleSelf(this); + } + } + + @Override + public boolean isRunning() { + synchronized (mLock) { + return mNextFrameToDecode > -1; + } + } + + @Override + public void scheduleSelf(Runnable what, long when) { + super.scheduleSelf(what, when); + } + + @Override + public void unscheduleSelf(Runnable what) { + synchronized (mLock) { + mNextFrameToDecode = -1; + } + super.unscheduleSelf(what); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean changed = super.setVisible(visible, restart); + + if (!visible) { + stop(); + } else if (restart || changed) { + stop(); + start(); + } + + return changed; + } + + // drawing properties + + @Override + public void setFilterBitmap(boolean filter) { + mPaint.setFilterBitmap(filter); + } + + @Override + public void setAlpha(int alpha) { + mPaint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mPaint.setColorFilter(colorFilter); + } + + @Override + public int getIntrinsicWidth() { + return mFrameSequence.getWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mFrameSequence.getHeight(); + } + + @Override + public int getOpacity() { + return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT; + } +} |