Add BitmapDecoder interface

PiperOrigin-RevId: 557827954
This commit is contained in:
tofunmi 2023-08-17 16:14:23 +01:00 committed by Julia Bibik
parent b814404c56
commit a8944ef2f0
3 changed files with 67 additions and 25 deletions

View File

@ -15,15 +15,18 @@
*/ */
package androidx.media3.exoplayer.image; package androidx.media3.exoplayer.image;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Matrix; import android.graphics.Matrix;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.test.utils.TestUtil; import androidx.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.nio.ByteBuffer;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -36,16 +39,20 @@ public class DefaultImageDecoderTest {
private static final String PNG_TEST_IMAGE_PATH = "media/png/non-motion-photo-shortened.png"; private static final String PNG_TEST_IMAGE_PATH = "media/png/non-motion-photo-shortened.png";
private static final String JPEG_TEST_IMAGE_PATH = "media/jpeg/non-motion-photo-shortened.jpg"; private static final String JPEG_TEST_IMAGE_PATH = "media/jpeg/non-motion-photo-shortened.jpg";
private DefaultImageDecoder imageDecoder; private DefaultImageDecoder decoder;
private DecoderInputBuffer inputBuffer;
private ImageOutputBuffer outputBuffer;
@Before @Before
public void setUp() { public void setUp() {
imageDecoder = new DefaultImageDecoder(); decoder = new DefaultImageDecoder.Factory().createImageDecoder();
inputBuffer = decoder.createInputBuffer();
outputBuffer = decoder.createOutputBuffer();
} }
@After @After
public void tearDown() { public void tearDown() {
imageDecoder.release(); decoder.release();
} }
@Test @Test
@ -53,7 +60,7 @@ public class DefaultImageDecoderTest {
byte[] imageData = byte[] imageData =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), PNG_TEST_IMAGE_PATH); TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), PNG_TEST_IMAGE_PATH);
Bitmap bitmap = imageDecoder.decode(imageData, imageData.length); Bitmap bitmap = decode(imageData);
assertThat( assertThat(
bitmap.sameAs( bitmap.sameAs(
@ -79,14 +86,22 @@ public class DefaultImageDecoderTest {
rotationMatrix, rotationMatrix,
/* filter= */ false); /* filter= */ false);
Bitmap actualBitmap = imageDecoder.decode(imageData, imageData.length); Bitmap actualBitmap = decode(imageData);
assertThat(actualBitmap.sameAs(expectedBitmap)).isTrue(); assertThat(actualBitmap.sameAs(expectedBitmap)).isTrue();
} }
@Test @Test
public void decodeBitmap_withInvalidData_throws() throws ImageDecoderException { public void decodeBitmap_withInvalidData_throws() throws ImageDecoderException {
assertThrows( assertThrows(ImageDecoderException.class, () -> decode(new byte[1]));
ImageDecoderException.class, () -> imageDecoder.decode(new byte[1], /* length= */ 1)); }
private Bitmap decode(byte[] data) throws Exception {
inputBuffer.data = ByteBuffer.wrap(data);
Exception e = decoder.decode(inputBuffer, outputBuffer, /* reset= */ false);
if (e != null) {
throw e;
}
return checkNotNull(outputBuffer.bitmap);
} }
} }

View File

@ -45,13 +45,27 @@ import java.nio.ByteBuffer;
* alongside one timestamp)). * alongside one timestamp)).
*/ */
@UnstableApi @UnstableApi
public class DefaultImageDecoder public final class DefaultImageDecoder
extends SimpleDecoder<DecoderInputBuffer, ImageOutputBuffer, ImageDecoderException> extends SimpleDecoder<DecoderInputBuffer, ImageOutputBuffer, ImageDecoderException>
implements ImageDecoder { implements ImageDecoder {
/** A functional interface for turning byte arrays into bitmaps. */
public interface BitmapDecoder {
/**
* Decodes data into a {@link Bitmap}.
*
* @param data An array holding the data to be decoded, starting at position 0.
* @param length The length of the input to be decoded.
* @return The decoded {@link Bitmap}.
* @throws ImageDecoderException If a decoding error occurs.
*/
Bitmap decode(byte[] data, int length) throws ImageDecoderException;
}
/** A factory for {@link DefaultImageDecoder} instances. */ /** A factory for {@link DefaultImageDecoder} instances. */
public static final class Factory implements ImageDecoder.Factory { public static final class Factory implements ImageDecoder.Factory {
private final BitmapDecoder bitmapDecoder;
private static final ImmutableSet<String> SUPPORTED_IMAGE_TYPES = private static final ImmutableSet<String> SUPPORTED_IMAGE_TYPES =
ImmutableSet.of( ImmutableSet.of(
MimeTypes.IMAGE_PNG, MimeTypes.IMAGE_PNG,
@ -60,6 +74,22 @@ public class DefaultImageDecoder
MimeTypes.IMAGE_HEIF, MimeTypes.IMAGE_HEIF,
MimeTypes.IMAGE_WEBP); MimeTypes.IMAGE_WEBP);
/**
* Creates an instance using a {@link BitmapFactory} implementation of {@link BitmapDecoder}.
*/
public Factory() {
this.bitmapDecoder = DefaultImageDecoder::decode;
}
/**
* Creates an instance.
*
* @param bitmapDecoder The {@link BitmapDecoder} used to turn a byte arrays into a bitmap.
*/
public Factory(BitmapDecoder bitmapDecoder) {
this.bitmapDecoder = bitmapDecoder;
}
@Override @Override
public @RendererCapabilities.Capabilities int supportsFormat(Format format) { public @RendererCapabilities.Capabilities int supportsFormat(Format format) {
if (!MimeTypes.isImage(format.containerMimeType)) { if (!MimeTypes.isImage(format.containerMimeType)) {
@ -75,13 +105,15 @@ public class DefaultImageDecoder
@Override @Override
public DefaultImageDecoder createImageDecoder() { public DefaultImageDecoder createImageDecoder() {
return new DefaultImageDecoder(); return new DefaultImageDecoder(bitmapDecoder);
} }
} }
/** Creates an instance. */ private final BitmapDecoder bitmapDecoder;
public DefaultImageDecoder() {
private DefaultImageDecoder(BitmapDecoder bitmapDecoder) {
super(new DecoderInputBuffer[1], new ImageOutputBuffer[1]); super(new DecoderInputBuffer[1], new ImageOutputBuffer[1]);
this.bitmapDecoder = bitmapDecoder;
} }
@Override @Override
@ -117,7 +149,7 @@ public class DefaultImageDecoder
ByteBuffer inputData = checkNotNull(inputBuffer.data); ByteBuffer inputData = checkNotNull(inputBuffer.data);
checkState(inputData.hasArray()); checkState(inputData.hasArray());
checkArgument(inputData.arrayOffset() == 0); checkArgument(inputData.arrayOffset() == 0);
outputBuffer.bitmap = decode(inputData.array(), inputData.remaining()); outputBuffer.bitmap = bitmapDecoder.decode(inputData.array(), inputData.remaining());
outputBuffer.timeUs = inputBuffer.timeUs; outputBuffer.timeUs = inputBuffer.timeUs;
return null; return null;
} catch (ImageDecoderException e) { } catch (ImageDecoderException e) {
@ -133,7 +165,7 @@ public class DefaultImageDecoder
* @return The decoded {@link Bitmap}. * @return The decoded {@link Bitmap}.
* @throws ImageDecoderException If a decoding error occurs. * @throws ImageDecoderException If a decoding error occurs.
*/ */
/* package */ Bitmap decode(byte[] data, int length) throws ImageDecoderException { private static Bitmap decode(byte[] data, int length) throws ImageDecoderException {
@Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, length); @Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, length);
if (bitmap == null) { if (bitmap == null) {
throw new ImageDecoderException( throw new ImageDecoderException(

View File

@ -42,22 +42,17 @@ public class DefaultImageDecoderBufferQueueTest {
private Bitmap decodedBitmap1; private Bitmap decodedBitmap1;
private Bitmap decodedBitmap2; private Bitmap decodedBitmap2;
public int decodeCallCount;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
decodeCallCount = 0;
decodedBitmap1 = Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888); decodedBitmap1 = Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888);
decodedBitmap2 = Bitmap.createBitmap(/* width= */ 2, /* height= */ 2, Bitmap.Config.ARGB_8888); decodedBitmap2 = Bitmap.createBitmap(/* width= */ 2, /* height= */ 2, Bitmap.Config.ARGB_8888);
fakeImageDecoder = fakeImageDecoder =
new DefaultImageDecoder() { new DefaultImageDecoder.Factory(
(data, length) -> ++decodeCallCount == 1 ? decodedBitmap1 : decodedBitmap2)
public int decodeCallCount; .createImageDecoder();
/** Overrides the decode method to fake it. */
@Override
protected Bitmap decode(byte[] data, int length) {
decodeCallCount++;
return decodeCallCount == 1 ? decodedBitmap1 : decodedBitmap2;
}
};
} }
@After @After