Transformer: Decode image in sRGB
The effects pipeline must receive images in the sRGB colorspace due to the color transfers applied in the shaders. Currently the burden to making sure images are in the right colorspaces falls onto apps. This CL ensures that this is not the case anymore. PiperOrigin-RevId: 542323613
This commit is contained in:
parent
1831220a53
commit
be38670391
@ -16,6 +16,7 @@
|
||||
package androidx.media3.common.util;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
@ -28,7 +29,12 @@ public interface BitmapLoader {
|
||||
ListenableFuture<Bitmap> decodeBitmap(byte[] data);
|
||||
|
||||
/** Loads an image from {@code uri}. */
|
||||
ListenableFuture<Bitmap> loadBitmap(Uri uri);
|
||||
default 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
|
||||
|
@ -198,6 +198,24 @@ public class DataSourceBitmapLoaderTest {
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadBitmap_withFileUriAndOptions_loadsDataWithRespectToOptions() throws Exception {
|
||||
byte[] imageData =
|
||||
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TEST_IMAGE_PATH);
|
||||
File file = tempFolder.newFile();
|
||||
Files.write(Paths.get(file.getAbsolutePath()), imageData);
|
||||
Uri uri = Uri.fromFile(file);
|
||||
DataSourceBitmapLoader bitmapLoader =
|
||||
new DataSourceBitmapLoader(MoreExecutors.newDirectExecutorService(), dataSourceFactory);
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inMutable = true;
|
||||
|
||||
Bitmap bitmap = bitmapLoader.loadBitmap(uri, options).get();
|
||||
|
||||
assertThat(bitmap.isMutable()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadBitmap_fileUriWithFileNotExisting_throws() {
|
||||
DataSourceBitmapLoader bitmapLoader =
|
||||
|
@ -78,17 +78,21 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
|
||||
return listeningExecutorService.submit(() -> decode(data));
|
||||
return listeningExecutorService.submit(() -> decode(data, /* options= */ null));
|
||||
}
|
||||
|
||||
/** Loads an image from a {@link Uri}. */
|
||||
@Override
|
||||
public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
|
||||
return listeningExecutorService.submit(() -> load(dataSourceFactory.createDataSource(), uri));
|
||||
public ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options) {
|
||||
return listeningExecutorService.submit(
|
||||
() -> load(dataSourceFactory.createDataSource(), uri, options));
|
||||
}
|
||||
|
||||
private static Bitmap decode(byte[] data) throws IOException {
|
||||
@Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, data.length);
|
||||
// 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)) {
|
||||
@ -111,12 +115,13 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private static Bitmap load(DataSource dataSource, Uri uri) throws IOException {
|
||||
private static Bitmap load(
|
||||
DataSource dataSource, Uri uri, @Nullable BitmapFactory.Options options) throws IOException {
|
||||
try {
|
||||
DataSpec dataSpec = new DataSpec(uri);
|
||||
dataSource.open(dataSpec);
|
||||
byte[] readData = DataSourceUtil.readToEnd(dataSource);
|
||||
return decode(readData);
|
||||
return decode(readData, options);
|
||||
} finally {
|
||||
dataSource.close();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package androidx.media3.session;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.BitmapLoader;
|
||||
@ -59,11 +60,11 @@ public final class CacheBitmapLoader implements BitmapLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
|
||||
public ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options) {
|
||||
if (lastBitmapLoadRequest != null && lastBitmapLoadRequest.matches(uri)) {
|
||||
return lastBitmapLoadRequest.getFuture();
|
||||
}
|
||||
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(uri);
|
||||
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(uri, options);
|
||||
lastBitmapLoadRequest = new BitmapLoadRequest(uri, future);
|
||||
return future;
|
||||
}
|
||||
|
@ -76,21 +76,24 @@ public final class SimpleBitmapLoader implements BitmapLoader {
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
|
||||
return executorService.submit(() -> decode(data));
|
||||
return executorService.submit(() -> decode(data, /* options= */ null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
|
||||
return executorService.submit(() -> load(uri));
|
||||
public ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options) {
|
||||
return executorService.submit(() -> load(uri, options));
|
||||
}
|
||||
|
||||
private static Bitmap decode(byte[] data) {
|
||||
@Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, data.length);
|
||||
// BitmapFactory's options parameter is null-ok.
|
||||
@SuppressWarnings("nullness:argument.type.incompatible")
|
||||
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");
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private static Bitmap load(Uri uri) throws IOException {
|
||||
private static Bitmap load(Uri uri, @Nullable BitmapFactory.Options options) throws IOException {
|
||||
if ("file".equals(uri.getScheme())) {
|
||||
@Nullable String path = uri.getPath();
|
||||
if (path == null) {
|
||||
@ -113,7 +116,7 @@ public final class SimpleBitmapLoader implements BitmapLoader {
|
||||
throw new IOException("Invalid response status code: " + responseCode);
|
||||
}
|
||||
try (InputStream inputStream = httpConnection.getInputStream()) {
|
||||
return decode(ByteStreams.toByteArray(inputStream));
|
||||
return decode(ByteStreams.toByteArray(inputStream), options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.ColorSpace;
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
@ -35,6 +37,7 @@ import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.BitmapLoader;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSourceBitmapLoader;
|
||||
import androidx.media3.datasource.DefaultDataSource;
|
||||
@ -109,7 +112,12 @@ public final class ImageAssetLoader implements AssetLoader {
|
||||
MoreExecutors.listeningDecorator(scheduledExecutorService), dataSourceFactory);
|
||||
MediaItem.LocalConfiguration localConfiguration =
|
||||
checkNotNull(editedMediaItem.mediaItem.localConfiguration);
|
||||
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(localConfiguration.uri);
|
||||
@Nullable BitmapFactory.Options options = null;
|
||||
if (Util.SDK_INT >= 26) {
|
||||
options = new BitmapFactory.Options();
|
||||
options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
}
|
||||
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(localConfiguration.uri, options);
|
||||
Futures.addCallback(
|
||||
future,
|
||||
new FutureCallback<Bitmap>() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user