Move bitmap decoding into datasource util
PiperOrigin-RevId: 608588505
This commit is contained in:
parent
ebcb4e8b21
commit
7a632a43ba
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
throw new ImageDecoderException(
|
|
||||||
"Could not decode image data with BitmapFactory. (data.length = "
|
|
||||||
+ data.length
|
|
||||||
+ ", input length = "
|
|
||||||
+ length
|
|
||||||
+ ")");
|
|
||||||
}
|
|
||||||
// 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, /* offset= */ 0, length)) {
|
|
||||||
exifInterface = new ExifInterface(inputStream);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ImageDecoderException(e);
|
throw new ImageDecoderException(e);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
if (Objects.equals(e.getMessage(), DataSourceUtil.BITMAP_DECODING_EXCEPTION_MESSAGE)) {
|
||||||
|
throw new ImageDecoderException(
|
||||||
|
"Could not decode image data with BitmapFactory. (data.length = "
|
||||||
|
+ data.length
|
||||||
|
+ ", input length = "
|
||||||
|
+ length
|
||||||
|
+ ")");
|
||||||
|
} else {
|
||||||
|
throw 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user