mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-22 20:18:36 +00:00
Add Glide Downsampler.
This commit is contained in:
committed by
Cody Henthorne
parent
7ca1ac4efb
commit
7d35e685b2
1015
app/src/main/java/org/thoughtcrime/securesms/glide/Downsampler.java
Normal file
1015
app/src/main/java/org/thoughtcrime/securesms/glide/Downsampler.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,338 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.glide;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import com.bumptech.glide.util.Util;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State and constants for interacting with {@link android.graphics.Bitmap.Config#HARDWARE} on Android O+.
|
||||||
|
*/
|
||||||
|
public final class HardwareConfigState {
|
||||||
|
private static final String TAG = "HardwareConfig";
|
||||||
|
private static final boolean enableVerboseLogging = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force the state to wait until a call to allow hardware Bitmaps to be used when they'd otherwise
|
||||||
|
* be eligible to work around a framework issue pre Q that can cause a native crash when
|
||||||
|
* allocating a hardware Bitmap in this specific circumstance. See b/126573603#comment12 for
|
||||||
|
* details.
|
||||||
|
*/
|
||||||
|
public static final boolean BLOCK_HARDWARE_BITMAPS_WHEN_GL_CONTEXT_MIGHT_NOT_BE_INITIALIZED =
|
||||||
|
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support for the hardware bitmap config was added in Android O.
|
||||||
|
*/
|
||||||
|
public static final boolean HARDWARE_BITMAPS_SUPPORTED =
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum size in pixels a {@link Bitmap} must be in both dimensions to be created with the
|
||||||
|
* {@link Bitmap.Config#HARDWARE} configuration.
|
||||||
|
*
|
||||||
|
* <p>This is a quick check that lets us skip wasting FDs (see {@link #FD_SIZE_LIST}) on small
|
||||||
|
* {@link Bitmap}s with relatively low memory costs.
|
||||||
|
*
|
||||||
|
* @see #FD_SIZE_LIST
|
||||||
|
*/
|
||||||
|
@VisibleForTesting static final int MIN_HARDWARE_DIMENSION_O = 128;
|
||||||
|
|
||||||
|
private static final int MIN_HARDWARE_DIMENSION_P = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows us to check to make sure we're not exceeding the FD limit for a process with hardware
|
||||||
|
* {@link Bitmap}s.
|
||||||
|
*
|
||||||
|
* <p>{@link Bitmap.Config#HARDWARE} {@link Bitmap}s require two FDs (depending on the driver).
|
||||||
|
* Processes have an FD limit of 1024 (at least on O). With sufficiently small {@link Bitmap}s
|
||||||
|
* and/or a sufficiently large {@link com.bumptech.glide.load.engine.cache.MemoryCache}, we can
|
||||||
|
* end up with enough {@link Bitmap}s in memory that we blow through the FD limit, which causes
|
||||||
|
* graphics errors, Binder errors, and a variety of crashes.
|
||||||
|
*
|
||||||
|
* <p>Calling list.size() should be relatively efficient (hopefully < 1ms on average) because
|
||||||
|
* /proc is an in-memory FS.
|
||||||
|
*/
|
||||||
|
private static final File FD_SIZE_LIST = new File("/proc/self/fd");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each FD check takes 1-2ms, so to avoid overhead, only check every N decodes. 50 is more or less
|
||||||
|
* arbitrary.
|
||||||
|
*/
|
||||||
|
private static final int MINIMUM_DECODES_BETWEEN_FD_CHECKS = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 700 with an error of 50 Bitmaps in between at two FDs each lets us use up to 800 FDs for
|
||||||
|
* hardware Bitmaps.
|
||||||
|
*
|
||||||
|
* <p>Prior to P, the limit per process was 1024 FDs. In P, the limit was updated to 32k FDs per
|
||||||
|
* process.
|
||||||
|
*
|
||||||
|
* <p>Access to this variable will be removed in a future version without deprecation.
|
||||||
|
*/
|
||||||
|
private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_O = 700;
|
||||||
|
// 20k.
|
||||||
|
private static final int MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P = 20000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant will be removed in a future version without deprecation, avoid using it.
|
||||||
|
*/
|
||||||
|
public static final int NO_MAX_FD_COUNT = -1;
|
||||||
|
|
||||||
|
private static volatile HardwareConfigState instance;
|
||||||
|
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
|
private static volatile int manualOverrideMaxFdCount = NO_MAX_FD_COUNT;
|
||||||
|
|
||||||
|
private final boolean isHardwareConfigAllowedByDeviceModel;
|
||||||
|
private final int sdkBasedMaxFdCount;
|
||||||
|
private final int minHardwareDimension;
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private int decodesSinceLastFdCheck;
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private boolean isFdSizeBelowHardwareLimit = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only mutated on the main thread. Read by any number of background threads concurrently.
|
||||||
|
*
|
||||||
|
* <p>Defaults to {@code false} because we need to wait for the GL context to be initialized and
|
||||||
|
* it defaults to not initialized (<a href="https://b.corp.google.com/issues/126573603#comment12">https://b.corp.google.com/issues/126573603#comment12</a>).
|
||||||
|
*/
|
||||||
|
private final AtomicBoolean isHardwareConfigAllowedByAppState = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public static HardwareConfigState getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (HardwareConfigState.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new HardwareConfigState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting HardwareConfigState() {
|
||||||
|
isHardwareConfigAllowedByDeviceModel = isHardwareConfigAllowedByDeviceModel();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
sdkBasedMaxFdCount = MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_P;
|
||||||
|
minHardwareDimension = MIN_HARDWARE_DIMENSION_P;
|
||||||
|
} else {
|
||||||
|
sdkBasedMaxFdCount = MAXIMUM_FDS_FOR_HARDWARE_CONFIGS_O;
|
||||||
|
minHardwareDimension = MIN_HARDWARE_DIMENSION_O;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean areHardwareBitmapsBlocked() {
|
||||||
|
Util.assertMainThread();
|
||||||
|
return !isHardwareConfigAllowedByAppState.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void blockHardwareBitmaps() {
|
||||||
|
Util.assertMainThread();
|
||||||
|
isHardwareConfigAllowedByAppState.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unblockHardwareBitmaps() {
|
||||||
|
Util.assertMainThread();
|
||||||
|
isHardwareConfigAllowedByAppState.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHardwareConfigAllowed(
|
||||||
|
int targetWidth,
|
||||||
|
int targetHeight,
|
||||||
|
boolean isHardwareConfigAllowed,
|
||||||
|
boolean isExifOrientationRequired)
|
||||||
|
{
|
||||||
|
if (!isHardwareConfigAllowed) {
|
||||||
|
if (enableVerboseLogging) {
|
||||||
|
Log.v(TAG, "Hardware config disallowed by caller");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isHardwareConfigAllowedByDeviceModel) {
|
||||||
|
if (enableVerboseLogging) {
|
||||||
|
Log.v(TAG, "Hardware config disallowed by device model");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!HARDWARE_BITMAPS_SUPPORTED) {
|
||||||
|
if (enableVerboseLogging) {
|
||||||
|
Log.v(TAG, "Hardware config disallowed by sdk");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (areHardwareBitmapsBlockedByAppState()) {
|
||||||
|
if (enableVerboseLogging) {
|
||||||
|
Log.v(TAG, "Hardware config disallowed by app state");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isExifOrientationRequired) {
|
||||||
|
if (enableVerboseLogging) {
|
||||||
|
Log.v(TAG, "Hardware config disallowed because exif orientation is required");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (targetWidth < minHardwareDimension) {
|
||||||
|
if (enableVerboseLogging) {
|
||||||
|
Log.v(TAG, "Hardware config disallowed because width is too small");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (targetHeight < minHardwareDimension) {
|
||||||
|
if (enableVerboseLogging) {
|
||||||
|
Log.v(TAG, "Hardware config disallowed because height is too small");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Make sure to call isFdSizeBelowHardwareLimit last because it has side affects.
|
||||||
|
if (!isFdSizeBelowHardwareLimit()) {
|
||||||
|
if (enableVerboseLogging) {
|
||||||
|
Log.v(TAG, "Hardware config disallowed because there are insufficient FDs");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean areHardwareBitmapsBlockedByAppState() {
|
||||||
|
return BLOCK_HARDWARE_BITMAPS_WHEN_GL_CONTEXT_MIGHT_NOT_BE_INITIALIZED
|
||||||
|
&& !isHardwareConfigAllowedByAppState.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
|
boolean setHardwareConfigIfAllowed(
|
||||||
|
int targetWidth,
|
||||||
|
int targetHeight,
|
||||||
|
BitmapFactory.Options optionsWithScaling,
|
||||||
|
boolean isHardwareConfigAllowed,
|
||||||
|
boolean isExifOrientationRequired)
|
||||||
|
{
|
||||||
|
boolean result =
|
||||||
|
isHardwareConfigAllowed(
|
||||||
|
targetWidth, targetHeight, isHardwareConfigAllowed, isExifOrientationRequired);
|
||||||
|
if (result) {
|
||||||
|
optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
|
||||||
|
optionsWithScaling.inMutable = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHardwareConfigAllowedByDeviceModel() {
|
||||||
|
return !isHardwareConfigDisallowedByB112551574() && !isHardwareConfigDisallowedByB147430447();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHardwareConfigDisallowedByB147430447() {
|
||||||
|
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// This method will only be called once, so simple iteration is reasonable.
|
||||||
|
return Arrays.asList(
|
||||||
|
"LG-M250",
|
||||||
|
"LG-M320",
|
||||||
|
"LG-Q710AL",
|
||||||
|
"LG-Q710PL",
|
||||||
|
"LGM-K121K",
|
||||||
|
"LGM-K121L",
|
||||||
|
"LGM-K121S",
|
||||||
|
"LGM-X320K",
|
||||||
|
"LGM-X320L",
|
||||||
|
"LGM-X320S",
|
||||||
|
"LGM-X401L",
|
||||||
|
"LGM-X401S",
|
||||||
|
"LM-Q610.FG",
|
||||||
|
"LM-Q610.FGN",
|
||||||
|
"LM-Q617.FG",
|
||||||
|
"LM-Q617.FGN",
|
||||||
|
"LM-Q710.FG",
|
||||||
|
"LM-Q710.FGN",
|
||||||
|
"LM-X220PM",
|
||||||
|
"LM-X220QMA",
|
||||||
|
"LM-X410PM")
|
||||||
|
.contains(Build.MODEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHardwareConfigDisallowedByB112551574() {
|
||||||
|
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// This method will only be called once, so simple iteration is reasonable.
|
||||||
|
for (String prefixOrModelName :
|
||||||
|
// This is sadly a list of prefixes, not models. We no longer have the data that shows us
|
||||||
|
// all the explicit models, so we have to live with the prefixes.
|
||||||
|
Arrays.asList(
|
||||||
|
// Samsung
|
||||||
|
"SC-04J",
|
||||||
|
"SM-N935",
|
||||||
|
"SM-J720",
|
||||||
|
"SM-G570F",
|
||||||
|
"SM-G570M",
|
||||||
|
"SM-G960",
|
||||||
|
"SM-G965",
|
||||||
|
"SM-G935",
|
||||||
|
"SM-G930",
|
||||||
|
"SM-A520",
|
||||||
|
"SM-A720F",
|
||||||
|
// Moto
|
||||||
|
"moto e5",
|
||||||
|
"moto e5 play",
|
||||||
|
"moto e5 plus",
|
||||||
|
"moto e5 cruise",
|
||||||
|
"moto g(6) forge",
|
||||||
|
"moto g(6) play")) {
|
||||||
|
if (Build.MODEL.startsWith(prefixOrModelName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMaxFdCount() {
|
||||||
|
return manualOverrideMaxFdCount != NO_MAX_FD_COUNT
|
||||||
|
? manualOverrideMaxFdCount
|
||||||
|
: sdkBasedMaxFdCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("LogTagInlined")
|
||||||
|
private synchronized boolean isFdSizeBelowHardwareLimit() {
|
||||||
|
if (++decodesSinceLastFdCheck >= MINIMUM_DECODES_BETWEEN_FD_CHECKS) {
|
||||||
|
decodesSinceLastFdCheck = 0;
|
||||||
|
int currentFds = Objects.requireNonNull(FD_SIZE_LIST.list()).length;
|
||||||
|
long maxFdCount = getMaxFdCount();
|
||||||
|
isFdSizeBelowHardwareLimit = currentFds < maxFdCount;
|
||||||
|
|
||||||
|
if (!isFdSizeBelowHardwareLimit && enableVerboseLogging) {
|
||||||
|
Log.w(
|
||||||
|
Downsampler.TAG,
|
||||||
|
"Excluding HARDWARE bitmap config because we're over the file descriptor limit"
|
||||||
|
+ ", file descriptors "
|
||||||
|
+ currentFds
|
||||||
|
+ ", limit "
|
||||||
|
+ maxFdCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isFdSizeBelowHardwareLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.glide;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.BitmapFactory.Options;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.ImageHeaderParser;
|
||||||
|
import com.bumptech.glide.load.ImageHeaderParser.ImageType;
|
||||||
|
import com.bumptech.glide.load.ImageHeaderParserUtils;
|
||||||
|
import com.bumptech.glide.load.data.DataRewinder;
|
||||||
|
import com.bumptech.glide.load.data.InputStreamRewinder;
|
||||||
|
import com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;
|
||||||
|
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;
|
||||||
|
import com.bumptech.glide.util.ByteBufferUtil;
|
||||||
|
import com.bumptech.glide.util.Preconditions;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a helper class for {@link Downsampler} that abstracts out image operations from the input
|
||||||
|
* type wrapped into a {@link DataRewinder}.
|
||||||
|
*/
|
||||||
|
interface ImageReader {
|
||||||
|
@Nullable
|
||||||
|
Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException;
|
||||||
|
|
||||||
|
ImageHeaderParser.ImageType getImageType() throws IOException;
|
||||||
|
|
||||||
|
int getImageOrientation() throws IOException;
|
||||||
|
|
||||||
|
void stopGrowingBuffers();
|
||||||
|
|
||||||
|
final class ByteArrayReader implements ImageReader {
|
||||||
|
|
||||||
|
private final byte[] bytes;
|
||||||
|
private final List<ImageHeaderParser> parsers;
|
||||||
|
private final ArrayPool byteArrayPool;
|
||||||
|
|
||||||
|
ByteArrayReader(byte[] bytes, List<ImageHeaderParser> parsers, ArrayPool byteArrayPool) {
|
||||||
|
this.bytes = bytes;
|
||||||
|
this.parsers = parsers;
|
||||||
|
this.byteArrayPool = byteArrayPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Bitmap decodeBitmap(Options options) {
|
||||||
|
return BitmapFactory.decodeByteArray(bytes, /* offset= */ 0, bytes.length, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageType getImageType() throws IOException {
|
||||||
|
return ImageHeaderParserUtils.getType(parsers, ByteBuffer.wrap(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageOrientation() throws IOException {
|
||||||
|
return ImageHeaderParserUtils.getOrientation(parsers, ByteBuffer.wrap(bytes), byteArrayPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopGrowingBuffers() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class FileReader implements ImageReader {
|
||||||
|
|
||||||
|
private final File file;
|
||||||
|
private final List<ImageHeaderParser> parsers;
|
||||||
|
private final ArrayPool byteArrayPool;
|
||||||
|
|
||||||
|
FileReader(File file, List<ImageHeaderParser> parsers, ArrayPool byteArrayPool) {
|
||||||
|
this.file = file;
|
||||||
|
this.parsers = parsers;
|
||||||
|
this.byteArrayPool = byteArrayPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Bitmap decodeBitmap(Options options) throws FileNotFoundException {
|
||||||
|
InputStream is = null;
|
||||||
|
try {
|
||||||
|
is = new RecyclableBufferedInputStream(new FileInputStream(file), byteArrayPool);
|
||||||
|
return BitmapFactory.decodeStream(is, /* outPadding= */ null, options);
|
||||||
|
} finally {
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignored.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageType getImageType() throws IOException {
|
||||||
|
InputStream is = null;
|
||||||
|
try {
|
||||||
|
is = new RecyclableBufferedInputStream(new FileInputStream(file), byteArrayPool);
|
||||||
|
return ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
|
||||||
|
} finally {
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignored.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageOrientation() throws IOException {
|
||||||
|
InputStream is = null;
|
||||||
|
try {
|
||||||
|
is = new RecyclableBufferedInputStream(new FileInputStream(file), byteArrayPool);
|
||||||
|
return ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
|
||||||
|
} finally {
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignored.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopGrowingBuffers() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ByteBufferReader implements ImageReader {
|
||||||
|
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
private final List<ImageHeaderParser> parsers;
|
||||||
|
private final ArrayPool byteArrayPool;
|
||||||
|
|
||||||
|
ByteBufferReader(ByteBuffer buffer, List<ImageHeaderParser> parsers, ArrayPool byteArrayPool) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.parsers = parsers;
|
||||||
|
this.byteArrayPool = byteArrayPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Bitmap decodeBitmap(Options options) {
|
||||||
|
return BitmapFactory.decodeStream(stream(), /* outPadding= */ null, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageType getImageType() throws IOException {
|
||||||
|
return ImageHeaderParserUtils.getType(parsers, ByteBufferUtil.rewind(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageOrientation() throws IOException {
|
||||||
|
return ImageHeaderParserUtils.getOrientation(
|
||||||
|
parsers, ByteBufferUtil.rewind(buffer), byteArrayPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopGrowingBuffers() {}
|
||||||
|
|
||||||
|
private InputStream stream() {
|
||||||
|
return ByteBufferUtil.toStream(ByteBufferUtil.rewind(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class InputStreamImageReader implements ImageReader {
|
||||||
|
private final InputStreamRewinder dataRewinder;
|
||||||
|
private final ArrayPool byteArrayPool;
|
||||||
|
private final List<ImageHeaderParser> parsers;
|
||||||
|
|
||||||
|
InputStreamImageReader(
|
||||||
|
InputStream is, List<ImageHeaderParser> parsers, ArrayPool byteArrayPool)
|
||||||
|
{
|
||||||
|
this.byteArrayPool = Preconditions.checkNotNull(byteArrayPool);
|
||||||
|
this.parsers = Preconditions.checkNotNull(parsers);
|
||||||
|
|
||||||
|
dataRewinder = new InputStreamRewinder(is, byteArrayPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {
|
||||||
|
return BitmapFactory.decodeStream(dataRewinder.rewindAndGet(), null, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageHeaderParser.ImageType getImageType() throws IOException {
|
||||||
|
return ImageHeaderParserUtils.getType(parsers, dataRewinder.rewindAndGet(), byteArrayPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageOrientation() throws IOException {
|
||||||
|
return ImageHeaderParserUtils.getOrientation(
|
||||||
|
parsers, dataRewinder.rewindAndGet(), byteArrayPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopGrowingBuffers() {
|
||||||
|
dataRewinder.fixMarkLimits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) final class ParcelFileDescriptorImageReader implements ImageReader {
|
||||||
|
private final ArrayPool byteArrayPool;
|
||||||
|
private final List<ImageHeaderParser> parsers;
|
||||||
|
private final ParcelFileDescriptorRewinder dataRewinder;
|
||||||
|
|
||||||
|
ParcelFileDescriptorImageReader(
|
||||||
|
ParcelFileDescriptor parcelFileDescriptor,
|
||||||
|
List<ImageHeaderParser> parsers,
|
||||||
|
ArrayPool byteArrayPool)
|
||||||
|
{
|
||||||
|
this.byteArrayPool = Preconditions.checkNotNull(byteArrayPool);
|
||||||
|
this.parsers = Preconditions.checkNotNull(parsers);
|
||||||
|
|
||||||
|
dataRewinder = new ParcelFileDescriptorRewinder(parcelFileDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {
|
||||||
|
return BitmapFactory.decodeFileDescriptor(
|
||||||
|
dataRewinder.rewindAndGet().getFileDescriptor(), null, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageHeaderParser.ImageType getImageType() throws IOException {
|
||||||
|
return ImageHeaderParserUtils.getType(parsers, dataRewinder, byteArrayPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageOrientation() throws IOException {
|
||||||
|
return ImageHeaderParserUtils.getOrientation(parsers, dataRewinder, byteArrayPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopGrowingBuffers() {
|
||||||
|
// Nothing to do here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user