blob: d76c42a2103a32f276b9d0110fab1ddeb31f0fb9 [file] [log] [blame]
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
package org.aomedia.avif.android;
import android.graphics.Bitmap;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
/**
* An AVIF Decoder. AVIF Specification: https://aomediacodec.github.io/av1-avif/.
*
* <p>There are two ways to use this class.
*
* <p>1) As a static utility class.
*
* <p>This class can be accessed statically without instantiating an object. This is useful to
* simply sniff and decode still AVIF images without having to maintain any decoder state. The
* following are the methods that can be accessed this way: {@link isAvifImage}, {@link getInfo} and
* {@link decode}. The {@link Info} inner class is used only in this case.
*
* <p>2) As an instantiated regular class.
*
* <p>When used this way, the {@link create} method must be used to create an instance of this class
* with a valid AVIF image. This will create a long running underlying decoder object which will be
* used to decode the image(s). Using the returned object, other public methods of the class can be
* called to get information about the image and to get the individual decoded frames. When the
* decoder object is no longer needed, {@link release} must be called to release the underlying
* decoder.
*
* <p>This is useful for decoding animated AVIF images and obtaining each decoded frame one after
* the other.
*
* <p>NOTE: The API for using this as an instantiated regular class is still under development and
* might change.
*/
@SuppressWarnings("CatchAndPrintStackTrace")
public class AvifDecoder {
static {
try {
System.loadLibrary("avif_android");
} catch (UnsatisfiedLinkError exception) {
exception.printStackTrace();
}
}
private long decoder;
private int width;
private int height;
private int depth;
private boolean alphaPresent;
private int frameCount;
private int repetitionCount;
private double[] frameDurations;
private AvifDecoder(ByteBuffer encoded) {
decoder = createDecoder(encoded, encoded.remaining());
}
/** Contains information about the AVIF Image. This class is only used for getInfo(). */
public static class Info {
public int width;
public int height;
public int depth;
public boolean alphaPresent;
}
/**
* Returns true if the bytes in the buffer seem like an AVIF image.
*
* @param buffer The encoded image. buffer.position() must be 0.
* @return true if the bytes seem like an AVIF image, false otherwise.
*/
public static boolean isAvifImage(ByteBuffer buffer) {
return AvifDecoder.isAvifImage(buffer, buffer.remaining());
}
private static native boolean isAvifImage(ByteBuffer encoded, int length);
/**
* Parses the AVIF header and populates the Info.
*
* @param encoded The encoded AVIF image. encoded.position() must be 0.
* @param length Length of the encoded buffer.
* @param info Output parameter whose fields will be populated.
* @return true on success and false on failure.
*/
public static native boolean getInfo(ByteBuffer encoded, int length, Info info);
/**
* Decodes the AVIF image into the bitmap.
*
* @param encoded The encoded AVIF image. encoded.position() must be 0.
* @param length Length of the encoded buffer.
* @param bitmap The decoded pixels will be copied into the bitmap.
* @return true on success and false on failure. A few possible reasons for failure are: 1) Input
* was not valid AVIF. 2) Bitmap was not large enough to store the decoded image.
*/
public static boolean decode(ByteBuffer encoded, int length, Bitmap bitmap) {
return decode(encoded, length, bitmap, 0);
}
/**
* Decodes the AVIF image into the bitmap.
*
* @param encoded The encoded AVIF image. encoded.position() must be 0.
* @param length Length of the encoded buffer.
* @param bitmap The decoded pixels will be copied into the bitmap.
* @param threads Number of threads to be used for the AVIF decode. Zero means use number of CPU
* cores as the thread count. Negative values are invalid. When this value is > 0, it is
* simply mapped to the maxThreads parameter in libavif. For more details, see the
* documentation for maxThreads variable in avif.h.
* @return true on success and false on failure. A few possible reasons for failure are: 1) Input
* was not valid AVIF. 2) Bitmap was not large enough to store the decoded image. 3) Negative
* value was passed for the threads parameter.
*/
public static native boolean decode(ByteBuffer encoded, int length, Bitmap bitmap, int threads);
/** Get the width of the image. */
public int getWidth() {
return width;
}
/** Get the height of the image. */
public int getHeight() {
return height;
}
/** Get the depth (bit depth) of the image. */
public int getDepth() {
return depth;
}
/** Returns true if the image contains a transparency/alpha channel, false otherwise. */
public boolean getAlphaPresent() {
return alphaPresent;
}
/** Get the number of frames in the image. */
public int getFrameCount() {
return frameCount;
}
/**
* Get the number of repetitions for an animated image (see repetitionCount in avif.h for
* details).
*/
public int getRepetitionCount() {
return repetitionCount;
}
/** Get the duration for each frame in the image. */
public double[] getFrameDurations() {
return frameDurations;
}
/** Releases the underlying decoder object. */
public void release() {
if (decoder != 0) {
destroyDecoder(decoder);
}
decoder = 0;
}
/**
* Create and return an AvifDecoder.
*
* @param encoded The encoded AVIF image. encoded.position() must be 0. The memory of this
* ByteBuffer must be kept alive until release() is called.
* @return null on failure. AvifDecoder object on success.
*/
@Nullable
public static AvifDecoder create(ByteBuffer encoded) {
AvifDecoder decoder = new AvifDecoder(encoded);
return (decoder.decoder == 0) ? null : decoder;
}
/**
* Decodes the next frame of the animated AVIF into the bitmap.
*
* @param bitmap The decoded pixels will be copied into the bitmap.
* @return true on success and false on failure. A few possible reasons for failure are: 1) Input
* was not valid AVIF. 2) Bitmap was not large enough to store the decoded image.
*/
public boolean nextFrame(Bitmap bitmap) {
// TODO(vigneshv): Consider returning an avifResult here instead of just a boolean.
return nextFrame(decoder, bitmap);
}
private native boolean nextFrame(long decoder, Bitmap bitmap);
private native long createDecoder(ByteBuffer encoded, int length);
private native void destroyDecoder(long decoder);
}