android_jni: Add method for getting nth frame
GOOGLE_INTERNAL_CL: 525466766
diff --git a/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java b/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java
index c588403..4ab23c3 100644
--- a/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java
+++ b/android_jni/avifandroidjni/src/androidTest/java/org/aomedia/avif/android/AnimatedImageTest.java
@@ -104,6 +104,19 @@
assertThat(decoder.nextFrame(bitmap)).isTrue();
assertThat(frameDurations[i]).isWithin(1.0e-2).of(image.frameDuration);
}
+ // Fetch the first frame again.
+ assertThat(decoder.nthFrame(0, bitmap)).isTrue();
+ // Now nextFrame will return the second frame.
+ assertThat(decoder.nextFrame(bitmap)).isTrue();
+ // Fetch the (frameCount/2)th frame.
+ assertThat(decoder.nthFrame(image.frameCount / 2, bitmap)).isTrue();
+ // Fetch the last frame.
+ assertThat(decoder.nthFrame(image.frameCount - 1, bitmap)).isTrue();
+ // Now nextFrame should return false.
+ assertThat(decoder.nextFrame(bitmap)).isFalse();
+ // Passing out of bound values for n should fail.
+ assertThat(decoder.nthFrame(-1, bitmap)).isFalse();
+ assertThat(decoder.nthFrame(image.frameCount, bitmap)).isFalse();
decoder.release();
}
diff --git a/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java b/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java
index d76c42a..55633fe 100644
--- a/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java
+++ b/android_jni/avifandroidjni/src/main/java/org/aomedia/avif/android/AvifDecoder.java
@@ -189,6 +189,22 @@
private native boolean nextFrame(long decoder, Bitmap bitmap);
+ /**
+ * Decodes the nth frame of the animated AVIF into the bitmap.
+ *
+ * <p>Note that calling this method will change the behavior of subsequent calls to {@link
+ * nextFrame}. {@link nextFrame} will start outputting the frame after this one.
+ *
+ * @param bitmap The decoded pixels will be copied into the bitmap.
+ * @param n The zero-based index of the frame to be decoded.
+ * @return true on success and false on failure.
+ */
+ public boolean nthFrame(int n, Bitmap bitmap) {
+ return nthFrame(decoder, n, bitmap);
+ }
+
+ private native boolean nthFrame(long decoder, int n, Bitmap bitmap);
+
private native long createDecoder(ByteBuffer encoded, int length);
private native void destroyDecoder(long decoder);
diff --git a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
index 0090cf1..a3f887e 100644
--- a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
+++ b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
@@ -76,13 +76,8 @@
return true;
}
-bool DecodeNextImage(JNIEnv* const env, AvifDecoderWrapper* const decoder,
- jobject bitmap) {
- avifResult res = avifDecoderNextImage(decoder->decoder);
- if (res != AVIF_RESULT_OK) {
- LOGE("Failed to decode AVIF image. Status: %d", res);
- return false;
- }
+bool AvifImageToBitmap(JNIEnv* const env, AvifDecoderWrapper* const decoder,
+ jobject bitmap) {
AndroidBitmapInfo bitmap_info;
if (AndroidBitmap_getInfo(env, bitmap, &bitmap_info) < 0) {
LOGE("AndroidBitmap_getInfo failed.");
@@ -128,7 +123,7 @@
// them:
// https://developer.android.com/reference/android/graphics/Bitmap#setPremultiplied(boolean)
rgb_image.alphaPremultiplied = AVIF_TRUE;
- res = avifImageYUVToRGB(decoder->decoder->image, &rgb_image);
+ const avifResult res = avifImageYUVToRGB(decoder->decoder->image, &rgb_image);
AndroidBitmap_unlockPixels(env, bitmap);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to convert YUV Pixels to RGB. Status: %d", res);
@@ -137,6 +132,26 @@
return true;
}
+bool DecodeNextImage(JNIEnv* const env, AvifDecoderWrapper* const decoder,
+ jobject bitmap) {
+ avifResult res = avifDecoderNextImage(decoder->decoder);
+ if (res != AVIF_RESULT_OK) {
+ LOGE("Failed to decode AVIF image. Status: %d", res);
+ return false;
+ }
+ return AvifImageToBitmap(env, decoder, bitmap);
+}
+
+bool DecodeNthImage(JNIEnv* const env, AvifDecoderWrapper* const decoder,
+ uint32_t n, jobject bitmap) {
+ avifResult res = avifDecoderNthImage(decoder->decoder, n);
+ if (res != AVIF_RESULT_OK) {
+ LOGE("Failed to decode AVIF image. Status: %d", res);
+ return false;
+ }
+ return AvifImageToBitmap(env, decoder, bitmap);
+}
+
} // namespace
jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
@@ -256,6 +271,12 @@
return DecodeNextImage(env, decoder, bitmap);
}
+FUNC(jboolean, nthFrame, jlong jdecoder, jint n, jobject bitmap) {
+ AvifDecoderWrapper* const decoder =
+ reinterpret_cast<AvifDecoderWrapper*>(jdecoder);
+ return DecodeNthImage(env, decoder, n, bitmap);
+}
+
FUNC(void, destroyDecoder, jlong jdecoder) {
AvifDecoderWrapper* const decoder =
reinterpret_cast<AvifDecoderWrapper*>(jdecoder);