mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
Add BitmapDecoder interface
PiperOrigin-RevId: 557827954
This commit is contained in:
parent
b814404c56
commit
a8944ef2f0
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user