Move bitmap decoding into datasource util

PiperOrigin-RevId: 608588505
This commit is contained in:
tofunmi 2024-02-20 06:53:45 -08:00 committed by Copybara-Service
parent ebcb4e8b21
commit 7a632a43ba
3 changed files with 59 additions and 68 deletions

View File

@ -15,17 +15,14 @@
*/ */
package androidx.media3.datasource; package androidx.media3.datasource;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.isBitmapFactorySupportedMimeType; import static androidx.media3.common.util.Util.isBitmapFactorySupportedMimeType;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.exifinterface.media.ExifInterface;
import androidx.media3.common.util.BitmapLoader; import androidx.media3.common.util.BitmapLoader;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
@ -33,9 +30,7 @@ import com.google.common.base.Suppliers;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
/** /**
@ -101,7 +96,7 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
@Override @Override
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) { public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
return listeningExecutorService.submit(() -> decode(data, options)); return listeningExecutorService.submit(() -> DataSourceUtil.decode(data, data.length, options));
} }
@Override @Override
@ -110,41 +105,13 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
() -> load(dataSourceFactory.createDataSource(), uri, options)); () -> load(dataSourceFactory.createDataSource(), uri, options));
} }
// BitmapFactory's options parameter is null-ok.
@SuppressWarnings("nullness:argument.type.incompatible")
private static Bitmap decode(byte[] data, @Nullable BitmapFactory.Options options)
throws IOException {
@Nullable
Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, data.length, options);
checkArgument(bitmap != null, "Could not decode image data");
ExifInterface exifInterface;
try (InputStream inputStream = new ByteArrayInputStream(data)) {
exifInterface = new ExifInterface(inputStream);
}
int rotationDegrees = exifInterface.getRotationDegrees();
if (rotationDegrees != 0) {
Matrix matrix = new Matrix();
matrix.postRotate(rotationDegrees);
bitmap =
Bitmap.createBitmap(
bitmap,
/* x= */ 0,
/* y= */ 0,
bitmap.getWidth(),
bitmap.getHeight(),
matrix,
/* filter= */ false);
}
return bitmap;
}
private static Bitmap load( private static Bitmap load(
DataSource dataSource, Uri uri, @Nullable BitmapFactory.Options options) throws IOException { DataSource dataSource, Uri uri, @Nullable BitmapFactory.Options options) throws IOException {
try { try {
DataSpec dataSpec = new DataSpec(uri); DataSpec dataSpec = new DataSpec(uri);
dataSource.open(dataSpec); dataSource.open(dataSpec);
byte[] readData = DataSourceUtil.readToEnd(dataSource); byte[] readData = DataSourceUtil.readToEnd(dataSource);
return decode(readData, options); return DataSourceUtil.decode(readData, readData.length, options);
} finally { } finally {
dataSource.close(); dataSource.close();
} }

View File

@ -15,15 +15,24 @@
*/ */
package androidx.media3.datasource; package androidx.media3.datasource;
import static androidx.media3.common.util.Assertions.checkArgument;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.exifinterface.media.ExifInterface;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
/** Utility methods for {@link DataSource}. */ /** Utility methods for {@link DataSource}. */
@UnstableApi @UnstableApi
public final class DataSourceUtil { public final class DataSourceUtil {
public static final String BITMAP_DECODING_EXCEPTION_MESSAGE = "Could not decode image data";
private DataSourceUtil() {} private DataSourceUtil() {}
@ -90,4 +99,37 @@ public final class DataSourceUtil {
// Ignore. // Ignore.
} }
} }
/**
* Decodes a {@link Bitmap} from a byte array using {@link BitmapFactory} and the {@link
* ExifInterface}.
*/
// BitmapFactory's options parameter is null-ok.
@SuppressWarnings("nullness:argument.type.incompatible")
public static Bitmap decode(byte[] data, int length, @Nullable BitmapFactory.Options options)
throws IOException {
@Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, length, options);
checkArgument(bitmap != null, BITMAP_DECODING_EXCEPTION_MESSAGE);
// BitmapFactory doesn't read the exif header, so we use the ExifInterface to this do ensure the
// bitmap is correctly orientated.
ExifInterface exifInterface;
try (InputStream inputStream = new ByteArrayInputStream(data)) {
exifInterface = new ExifInterface(inputStream);
}
int rotationDegrees = exifInterface.getRotationDegrees();
if (rotationDegrees != 0) {
Matrix matrix = new Matrix();
matrix.postRotate(rotationDegrees);
bitmap =
Bitmap.createBitmap(
bitmap,
/* x= */ 0,
/* y= */ 0,
bitmap.getWidth(),
bitmap.getHeight(),
matrix,
/* filter= */ false);
}
return bitmap;
}
} }

View File

@ -24,21 +24,19 @@ import static androidx.media3.decoder.DecoderInputBuffer.BUFFER_REPLACEMENT_MODE
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.exifinterface.media.ExifInterface;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DataSourceUtil;
import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.decoder.SimpleDecoder; import androidx.media3.decoder.SimpleDecoder;
import androidx.media3.exoplayer.RendererCapabilities; import androidx.media3.exoplayer.RendererCapabilities;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Objects;
/** /**
* An image decoder that uses {@link BitmapFactory} to decode images. * An image decoder that uses {@link BitmapFactory} to decode images.
@ -160,37 +158,21 @@ public final class BitmapFactoryImageDecoder
* @throws ImageDecoderException If a decoding error occurs. * @throws ImageDecoderException If a decoding error occurs.
*/ */
private static 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); try {
if (bitmap == null) { return DataSourceUtil.decode(data, length, /* options= */ null);
} catch (IOException e) {
throw new ImageDecoderException(e);
} catch (IllegalArgumentException e) {
if (Objects.equals(e.getMessage(), DataSourceUtil.BITMAP_DECODING_EXCEPTION_MESSAGE)) {
throw new ImageDecoderException( throw new ImageDecoderException(
"Could not decode image data with BitmapFactory. (data.length = " "Could not decode image data with BitmapFactory. (data.length = "
+ data.length + data.length
+ ", input length = " + ", input length = "
+ length + length
+ ")"); + ")");
} } else {
// BitmapFactory doesn't read the exif header, so we use the ExifInterface to this do ensure the throw e;
// bitmap is correctly orientated. }
ExifInterface exifInterface; }
try (InputStream inputStream = new ByteArrayInputStream(data, /* offset= */ 0, length)) {
exifInterface = new ExifInterface(inputStream);
} catch (IOException e) {
throw new ImageDecoderException(e);
}
int rotationDegrees = exifInterface.getRotationDegrees();
if (rotationDegrees != 0) {
Matrix matrix = new Matrix();
matrix.postRotate(rotationDegrees);
bitmap =
Bitmap.createBitmap(
bitmap,
/* x= */ 0,
/* y= */ 0,
bitmap.getWidth(),
bitmap.getHeight(),
matrix,
/* filter= */ false);
}
return bitmap;
} }
} }