Move setting bitmapFactory options from interface to implementation
Moves setting bitmapFactory options from BitmapLoader to DatasourceBitmapLoader
BitmapLoader is a general interface for bitmap loading that could use loading implementations other that BitmapFactory, with the rise of Glide being a loader of choice. It's best to correct this interface so that it remains generic
We can't deprecate easily because the other loadBitmap method in that case has a default implementation that relies on the first one, so the change is still breaking. BitmapLoader is public api in common, but it's @UnstableAPI and hasn't been around for very long (be38670391
added it), so it seems this is the best way forward.
PiperOrigin-RevId: 597897098
This commit is contained in:
parent
09a547953b
commit
6879698d7e
@ -16,7 +16,6 @@
|
|||||||
package androidx.media3.common.util;
|
package androidx.media3.common.util;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
@ -29,12 +28,7 @@ public interface BitmapLoader {
|
|||||||
ListenableFuture<Bitmap> decodeBitmap(byte[] data);
|
ListenableFuture<Bitmap> decodeBitmap(byte[] data);
|
||||||
|
|
||||||
/** Loads an image from {@code uri}. */
|
/** Loads an image from {@code uri}. */
|
||||||
default ListenableFuture<Bitmap> loadBitmap(Uri uri) {
|
ListenableFuture<Bitmap> loadBitmap(Uri uri);
|
||||||
return loadBitmap(uri, /* options= */ null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Loads an image from {@code uri} with the given {@link BitmapFactory.Options}. */
|
|
||||||
ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an image from {@link MediaMetadata}. Returns null if {@code metadata} doesn't contain
|
* Loads an image from {@link MediaMetadata}. Returns null if {@code metadata} doesn't contain
|
||||||
|
@ -205,13 +205,13 @@ public class DataSourceBitmapLoaderTest {
|
|||||||
File file = tempFolder.newFile();
|
File file = tempFolder.newFile();
|
||||||
Files.write(Paths.get(file.getAbsolutePath()), imageData);
|
Files.write(Paths.get(file.getAbsolutePath()), imageData);
|
||||||
Uri uri = Uri.fromFile(file);
|
Uri uri = Uri.fromFile(file);
|
||||||
DataSourceBitmapLoader bitmapLoader =
|
|
||||||
new DataSourceBitmapLoader(MoreExecutors.newDirectExecutorService(), dataSourceFactory);
|
|
||||||
|
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inMutable = true;
|
options.inMutable = true;
|
||||||
|
DataSourceBitmapLoader bitmapLoader =
|
||||||
|
new DataSourceBitmapLoader(
|
||||||
|
MoreExecutors.newDirectExecutorService(), dataSourceFactory, options);
|
||||||
|
|
||||||
Bitmap bitmap = bitmapLoader.loadBitmap(uri, options).get();
|
Bitmap bitmap = bitmapLoader.loadBitmap(uri).get();
|
||||||
|
|
||||||
assertThat(bitmap.isMutable()).isTrue();
|
assertThat(bitmap.isMutable()).isTrue();
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ import java.util.concurrent.Executors;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link BitmapLoader} implementation that uses a {@link DataSource} to support fetching images
|
* A {@link BitmapLoader} implementation that uses a {@link DataSource} to support fetching images
|
||||||
* from URIs.
|
* from URIs and {@link BitmapFactory} to load them into {@link Bitmap}.
|
||||||
*
|
*
|
||||||
* <p>Loading tasks are delegated to a {@link ListeningExecutorService} defined during construction.
|
* <p>Loading tasks are delegated to a {@link ListeningExecutorService} defined during construction.
|
||||||
* If no executor service is passed, all tasks are delegated to a single-thread executor service
|
* If no executor service is passed, all tasks are delegated to a single-thread executor service
|
||||||
@ -54,6 +54,7 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
|
|||||||
|
|
||||||
private final ListeningExecutorService listeningExecutorService;
|
private final ListeningExecutorService listeningExecutorService;
|
||||||
private final DataSource.Factory dataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
|
@Nullable private final BitmapFactory.Options options;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance that uses a {@link DefaultHttpDataSource} for image loading and delegates
|
* Creates an instance that uses a {@link DefaultHttpDataSource} for image loading and delegates
|
||||||
@ -72,17 +73,33 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
|
|||||||
*/
|
*/
|
||||||
public DataSourceBitmapLoader(
|
public DataSourceBitmapLoader(
|
||||||
ListeningExecutorService listeningExecutorService, DataSource.Factory dataSourceFactory) {
|
ListeningExecutorService listeningExecutorService, DataSource.Factory dataSourceFactory) {
|
||||||
|
this(listeningExecutorService, dataSourceFactory, /* options= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance that delegates loading tasks to the {@link ListeningExecutorService}.
|
||||||
|
*
|
||||||
|
* @param listeningExecutorService The {@link ListeningExecutorService}.
|
||||||
|
* @param dataSourceFactory The {@link DataSource.Factory} that creates the {@link DataSource}
|
||||||
|
* used to load the image.
|
||||||
|
* @param options The {@link BitmapFactory.Options} the image should be loaded with.
|
||||||
|
*/
|
||||||
|
public DataSourceBitmapLoader(
|
||||||
|
ListeningExecutorService listeningExecutorService,
|
||||||
|
DataSource.Factory dataSourceFactory,
|
||||||
|
@Nullable BitmapFactory.Options options) {
|
||||||
this.listeningExecutorService = listeningExecutorService;
|
this.listeningExecutorService = listeningExecutorService;
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
|
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
|
||||||
return listeningExecutorService.submit(() -> decode(data, /* options= */ null));
|
return listeningExecutorService.submit(() -> decode(data, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options) {
|
public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
|
||||||
return listeningExecutorService.submit(
|
return listeningExecutorService.submit(
|
||||||
() -> load(dataSourceFactory.createDataSource(), uri, options));
|
() -> load(dataSourceFactory.createDataSource(), uri, options));
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ package androidx.media3.session;
|
|||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.BitmapLoader;
|
import androidx.media3.common.util.BitmapLoader;
|
||||||
@ -60,11 +59,11 @@ public final class CacheBitmapLoader implements BitmapLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options) {
|
public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
|
||||||
if (lastBitmapLoadRequest != null && lastBitmapLoadRequest.matches(uri)) {
|
if (lastBitmapLoadRequest != null && lastBitmapLoadRequest.matches(uri)) {
|
||||||
return lastBitmapLoadRequest.getFuture();
|
return lastBitmapLoadRequest.getFuture();
|
||||||
}
|
}
|
||||||
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(uri, options);
|
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(uri);
|
||||||
lastBitmapLoadRequest = new BitmapLoadRequest(uri, future);
|
lastBitmapLoadRequest = new BitmapLoadRequest(uri, future);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
@ -68,24 +68,21 @@ public final class SimpleBitmapLoader implements BitmapLoader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
|
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
|
||||||
return executorService.submit(() -> decode(data, /* options= */ null));
|
return executorService.submit(() -> decode(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options) {
|
public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
|
||||||
return executorService.submit(() -> load(uri, options));
|
return executorService.submit(() -> load(uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
// BitmapFactory's options parameter is null-ok.
|
private static Bitmap decode(byte[] data) {
|
||||||
@SuppressWarnings("nullness:argument.type.incompatible")
|
@Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, data.length);
|
||||||
private static Bitmap decode(byte[] data, @Nullable BitmapFactory.Options options) {
|
|
||||||
@Nullable
|
|
||||||
Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, data.length, options);
|
|
||||||
checkArgument(bitmap != null, "Could not decode image data");
|
checkArgument(bitmap != null, "Could not decode image data");
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Bitmap load(Uri uri, @Nullable BitmapFactory.Options options) throws IOException {
|
private static Bitmap load(Uri uri) throws IOException {
|
||||||
if ("file".equals(uri.getScheme())) {
|
if ("file".equals(uri.getScheme())) {
|
||||||
@Nullable String path = uri.getPath();
|
@Nullable String path = uri.getPath();
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
@ -108,7 +105,7 @@ public final class SimpleBitmapLoader implements BitmapLoader {
|
|||||||
throw new IOException("Invalid response status code: " + responseCode);
|
throw new IOException("Invalid response status code: " + responseCode);
|
||||||
}
|
}
|
||||||
try (InputStream inputStream = httpConnection.getInputStream()) {
|
try (InputStream inputStream = httpConnection.getInputStream()) {
|
||||||
return decode(ByteStreams.toByteArray(inputStream), options);
|
return decode(ByteStreams.toByteArray(inputStream));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,17 +112,18 @@ public final class ImageAssetLoader implements AssetLoader {
|
|||||||
progressState = PROGRESS_STATE_AVAILABLE;
|
progressState = PROGRESS_STATE_AVAILABLE;
|
||||||
listener.onDurationUs(editedMediaItem.durationUs);
|
listener.onDurationUs(editedMediaItem.durationUs);
|
||||||
listener.onTrackCount(1);
|
listener.onTrackCount(1);
|
||||||
BitmapLoader bitmapLoader =
|
|
||||||
new DataSourceBitmapLoader(
|
|
||||||
MoreExecutors.listeningDecorator(scheduledExecutorService), dataSourceFactory);
|
|
||||||
MediaItem.LocalConfiguration localConfiguration =
|
|
||||||
checkNotNull(editedMediaItem.mediaItem.localConfiguration);
|
|
||||||
@Nullable BitmapFactory.Options options = null;
|
@Nullable BitmapFactory.Options options = null;
|
||||||
if (Util.SDK_INT >= 26) {
|
if (Util.SDK_INT >= 26) {
|
||||||
options = new BitmapFactory.Options();
|
options = new BitmapFactory.Options();
|
||||||
options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
|
options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||||
}
|
}
|
||||||
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(localConfiguration.uri, options);
|
BitmapLoader bitmapLoader =
|
||||||
|
new DataSourceBitmapLoader(
|
||||||
|
MoreExecutors.listeningDecorator(scheduledExecutorService), dataSourceFactory, options);
|
||||||
|
MediaItem.LocalConfiguration localConfiguration =
|
||||||
|
checkNotNull(editedMediaItem.mediaItem.localConfiguration);
|
||||||
|
|
||||||
|
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(localConfiguration.uri);
|
||||||
Futures.addCallback(
|
Futures.addCallback(
|
||||||
future,
|
future,
|
||||||
new FutureCallback<Bitmap>() {
|
new FutureCallback<Bitmap>() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user