Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for overriding downsample mode per image request #2787

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.imagepipeline.core

enum class DownsampleMode {
ALWAYS,
AUTO,
NEVER
}
Original file line number Diff line number Diff line change
Expand Up @@ -574,9 +574,3 @@ class ImagePipelineConfig private constructor(builder: Builder) : ImagePipelineC
}
}
}

enum class DownsampleMode {
ALWAYS,
AUTO,
NEVER
}
Original file line number Diff line number Diff line change
Expand Up @@ -396,25 +396,27 @@ class DecodeProducer(
protected abstract val qualityInfo: QualityInfo

init {

val job = JobRunnable { encodedImage, status ->
if (encodedImage != null) {
val request = producerContext.imageRequest
producerContext.putExtra(HasExtraData.KEY_IMAGE_FORMAT, encodedImage.imageFormat.name)
encodedImage.source = request.sourceUri?.toString()

val requestDownsampleMode = request.downsampleOverride ?: downsampleMode
val isResizingDone = statusHasFlag(status, IS_RESIZING_DONE)
if (downsampleMode == DownsampleMode.ALWAYS ||
(downsampleMode == DownsampleMode.AUTO && !isResizingDone)) {
if (downsampleEnabledForNetwork || !UriUtil.isNetworkUri(request.sourceUri)) {
encodedImage.sampleSize =
DownsampleUtil.determineSampleSize(
request.rotationOptions,
request.resizeOptions,
encodedImage,
maxBitmapDimension)
}
val shouldAdjustSampleSize =
(requestDownsampleMode == DownsampleMode.ALWAYS ||
(requestDownsampleMode == DownsampleMode.AUTO && !isResizingDone)) &&
(downsampleEnabledForNetwork || !UriUtil.isNetworkUri(request.sourceUri))
if (shouldAdjustSampleSize) {
encodedImage.sampleSize =
DownsampleUtil.determineSampleSize(
request.rotationOptions,
request.resizeOptions,
encodedImage,
maxBitmapDimension)
}

if (producerContext.imagePipelineConfig.experiments.downsampleIfLargeBitmap) {
maybeIncreaseSampleSize(encodedImage)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.common.RotationOptions;
import com.facebook.imagepipeline.common.SourceUriType;
import com.facebook.imagepipeline.core.DownsampleMode;
import com.facebook.imagepipeline.listener.RequestListener;
import com.facebook.imageutils.BitmapUtil;
import com.facebook.infer.annotation.Nullsafe;
Expand Down Expand Up @@ -120,6 +121,9 @@ public class ImageRequest {
*/
private final @Nullable Boolean mResizingAllowedOverride;

/** Custom downsample override for this request. null -> use default pipeline's setting. */
private final @Nullable DownsampleMode mDownsampleOverride;

private final @Nullable String mDiskCacheId;

private final int mDelayMs;
Expand Down Expand Up @@ -175,6 +179,8 @@ protected ImageRequest(ImageRequestBuilder builder) {

mResizingAllowedOverride = builder.getResizingAllowedOverride();

mDownsampleOverride = builder.getDownsampleOverride();

mDelayMs = builder.getDelayMs();

mDiskCacheId = builder.getDiskCacheId();
Expand Down Expand Up @@ -270,6 +276,10 @@ public boolean isMemoryCacheEnabled() {
return mResizingAllowedOverride;
}

public @Nullable DownsampleMode getDownsampleOverride() {
return mDownsampleOverride;
}

public int getDelayMs() {
return mDelayMs;
}
Expand Down Expand Up @@ -322,6 +332,7 @@ public boolean equals(@Nullable Object o) {
|| !Objects.equal(mCachesDisabled, request.mCachesDisabled)
|| !Objects.equal(mDecodePrefetches, request.mDecodePrefetches)
|| !Objects.equal(mResizingAllowedOverride, request.mResizingAllowedOverride)
|| !Objects.equal(mDownsampleOverride, request.mDownsampleOverride)
|| !Objects.equal(mRotationOptions, request.mRotationOptions)
|| mLoadThumbnailOnly != request.mLoadThumbnailOnly) {
return false;
Expand Down Expand Up @@ -359,6 +370,7 @@ public int hashCode() {
result = HashCode.extend(result, mRotationOptions);
result = HashCode.extend(result, postprocessorCacheKey);
result = HashCode.extend(result, mResizingAllowedOverride);
result = HashCode.extend(result, mDownsampleOverride);
result = HashCode.extend(result, mDelayMs);
result = HashCode.extend(result, mLoadThumbnailOnly);
// ^ I *think* this is safe despite autoboxing...?
Expand Down Expand Up @@ -393,6 +405,7 @@ public void recordHashCode(HashMap<String, Integer> hashCodeLog) {
hashCodeLog.put("ImageRequest.postprocessorCacheKey", getHashCodeHelper(postprocessorCacheKey));
hashCodeLog.put(
"ImageRequest.mResizingAllowedOverride", getHashCodeHelper(mResizingAllowedOverride));
hashCodeLog.put("ImageRequest.mDownsampleOverride", getHashCodeHelper(mDownsampleOverride));
hashCodeLog.put("ImageRequest.mDelayMs", getHashCodeHelper(mDelayMs));
hashCodeLog.put("ImageRequest.mLoadThumbnailOnly", getHashCodeHelper(mLoadThumbnailOnly));
}
Expand All @@ -417,6 +430,7 @@ public String toString() {
.add("rotationOptions", mRotationOptions)
.add("bytesRange", mBytesRange)
.add("resizingAllowedOverride", mResizingAllowedOverride)
.add("downsampleOverride", mDownsampleOverride)
.add("progressiveRenderingEnabled", mProgressiveRenderingEnabled)
.add("localThumbnailPreviewsEnabled", mLocalThumbnailPreviewsEnabled)
.add("loadThumbnailOnly", mLoadThumbnailOnly)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.facebook.imagepipeline.common.Priority;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.common.RotationOptions;
import com.facebook.imagepipeline.core.DownsampleMode;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.facebook.imagepipeline.core.ImagePipelineExperiments;
import com.facebook.imagepipeline.listener.RequestListener;
Expand Down Expand Up @@ -47,6 +48,7 @@ public class ImageRequestBuilder {
private @Nullable RequestListener mRequestListener;
private @Nullable BytesRange mBytesRange = null;
private @Nullable Boolean mResizingAllowedOverride = null;
private @Nullable DownsampleMode mDownsampleOverride = null;
private int mDelayMs;
private @Nullable String mDiskCacheId = null;

Expand Down Expand Up @@ -104,7 +106,8 @@ public static ImageRequestBuilder fromRequest(ImageRequest imageRequest) {
.setRotationOptions(imageRequest.getRotationOptions())
.setShouldDecodePrefetches(imageRequest.shouldDecodePrefetches())
.setDelayMs(imageRequest.getDelayMs())
.setDiskCacheId(imageRequest.getDiskCacheId());
.setDiskCacheId(imageRequest.getDiskCacheId())
.setDownsampleOverride(imageRequest.getDownsampleOverride());
}

public static void addCustomUriNetworkScheme(String scheme) {
Expand Down Expand Up @@ -453,6 +456,15 @@ public ImageRequestBuilder setResizingAllowedOverride(@Nullable Boolean resizing
return mResizingAllowedOverride;
}

public ImageRequestBuilder setDownsampleOverride(@Nullable DownsampleMode downsampleOverride) {
this.mDownsampleOverride = downsampleOverride;
return this;
}

public @Nullable DownsampleMode getDownsampleOverride() {
return mDownsampleOverride;
}

public int getDelayMs() {
return mDelayMs;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,22 @@ public void testDecode_WhenSmartResizingEnabledAndLocalUri_ThenPerformDownsampli
assertNotEquals(mEncodedImage.getSampleSize(), EncodedImage.DEFAULT_SAMPLE_SIZE);
}

@Test
public void testDecode_WhenDownsampleOverrideProvidedAndLocalUri_ThenPerformNoDownsampling()
throws Exception {
int resizedWidth = 10;
int resizedHeight = 10;
setupLocalUri(ResizeOptions.forDimensions(resizedWidth, resizedHeight), DownsampleMode.NEVER);

produceResults();
JobScheduler.JobRunnable jobRunnable = getJobRunnable();

jobRunnable.run(mEncodedImage, Consumer.IS_LAST);

// The sample size was not modified, which means Downsampling has not been performed
assertEquals(mEncodedImage.getSampleSize(), EncodedImage.DEFAULT_SAMPLE_SIZE);
}

@Test
public void testDecode_WhenSmartResizingEnabledAndNetworkUri_ThenPerformNoDownsampling()
throws Exception {
Expand All @@ -429,6 +445,23 @@ public void testDecode_WhenSmartResizingEnabledAndNetworkUri_ThenPerformNoDownsa
assertEquals(mEncodedImage.getSampleSize(), EncodedImage.DEFAULT_SAMPLE_SIZE);
}

@Test
public void testDecode_WhenDownsampleOverrideProvidedAndNetworkUri_ThenPerformNoDownsampling()
throws Exception {
int resizedWidth = 10;
int resizedHeight = 10;
setupNetworkUri(
ResizeOptions.forDimensions(resizedWidth, resizedHeight), DownsampleMode.ALWAYS);

produceResults();
JobScheduler.JobRunnable jobRunnable = getJobRunnable();

jobRunnable.run(mEncodedImage, Consumer.IS_LAST);

// The sample size was not modified, which means Downsampling has not been performed
assertEquals(mEncodedImage.getSampleSize(), EncodedImage.DEFAULT_SAMPLE_SIZE);
}

private void setupImageRequest(String requestId, ImageRequest imageRequest) {
mImageRequest = imageRequest;
mRequestId = requestId;
Expand All @@ -446,30 +479,42 @@ private void setupImageRequest(String requestId, ImageRequest imageRequest) {
}

private void setupNetworkUri() {
setupNetworkUri(null);
setupNetworkUri(null, null);
}

private void setupNetworkUri(@Nullable ResizeOptions resizeOptions) {
setupNetworkUri(resizeOptions, null);
}

private void setupNetworkUri(
@Nullable ResizeOptions resizeOptions, @Nullable DownsampleMode downsampleOverride) {
setupImageRequest(
"networkRequest1",
ImageRequestBuilder.newBuilderWithSource(Uri.parse("http://www.fb.com/image"))
.setProgressiveRenderingEnabled(true)
.setImageDecodeOptions(IMAGE_DECODE_OPTIONS)
.setResizeOptions(resizeOptions)
.setDownsampleOverride(downsampleOverride)
.build());
}

private void setupLocalUri() {
setupLocalUri(null);
setupLocalUri(null, null);
}

private void setupLocalUri(@Nullable ResizeOptions resizeOptions) {
setupLocalUri(resizeOptions, null);
}

private void setupLocalUri(
@Nullable ResizeOptions resizeOptions, @Nullable DownsampleMode downsampleOverride) {
setupImageRequest(
"localRequest1",
ImageRequestBuilder.newBuilderWithSource(Uri.parse("file://path/image"))
.setProgressiveRenderingEnabled(true) // this should be ignored
.setImageDecodeOptions(IMAGE_DECODE_OPTIONS)
.setResizeOptions(resizeOptions)
.setDownsampleOverride(downsampleOverride)
.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class ImagePipelineUtilsImpl(private val imageDecodeOptionsProvider: ImageDecode
): ImageRequestBuilder? =
imageRequestBuilder?.apply {
imageOptions.resizeOptions?.let { resizeOptions = it }
imageOptions.downsampleOverride?.let { downsampleOverride = it }
imageOptions.rotationOptions?.let { rotationOptions = it }
imageDecodeOptionsProvider.create(imageRequestBuilder, imageOptions)?.let {
imageDecodeOptions = it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.facebook.fresco.vito.options.RoundingOptions
import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.facebook.imagepipeline.common.ResizeOptions
import com.facebook.imagepipeline.common.RotationOptions
import com.facebook.imagepipeline.core.DownsampleMode
import com.facebook.imagepipeline.testing.TestNativeLoader
import org.assertj.core.api.Java6Assertions
import org.assertj.core.api.Java6Assertions.fail
Expand Down Expand Up @@ -142,6 +143,22 @@ class ImagePipelineUtilsImplTest {
Java6Assertions.assertThat(imageRequest.resizeOptions).isEqualTo(resizeOptions)
}

@Test
fun testBuildImageRequest_whenResizingOverrideDisabled_thenSetOverrideOption() {
val resizeOptions = ResizeOptions.forDimensions(123, 234)
val imageOptions =
ImageOptions.create().resize(resizeOptions).downsampleOverride(DownsampleMode.NEVER).build()
val imageRequest = imagePipelineUtils.buildImageRequest(URI, imageOptions)
if (imageRequest == null) {
fail("not null value expected")
return
}

Java6Assertions.assertThat(imageRequest.sourceUri).isEqualTo(URI)
Java6Assertions.assertThat(imageRequest.resizeOptions).isEqualTo(resizeOptions)
Java6Assertions.assertThat(imageRequest.downsampleOverride).isEqualTo(DownsampleMode.NEVER)
}

@Test
fun testBuildImageRequest_whenRotatingEnabled_thenSetRotateOptions() {
val rotationOptions = RotationOptions.forceRotation(RotationOptions.ROTATE_270)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.facebook.imagepipeline.common.ResizeOptions
import com.facebook.imagepipeline.common.RotationOptions
import com.facebook.imagepipeline.core.DownsampleMode
import com.facebook.imagepipeline.request.Postprocessor

open class DecodedImageOptions(builder: Builder<*>) : EncodedImageOptions(builder) {
val resizeOptions: ResizeOptions? = builder.resizeOptions
val downsampleOverride: DownsampleMode? = builder.downsampleOverride
val rotationOptions: RotationOptions? = builder.rotationOptions
val postprocessor: Postprocessor? = builder.postprocessor
val imageDecodeOptions: ImageDecodeOptions? = builder.imageDecodeOptions
Expand All @@ -42,6 +44,7 @@ open class DecodedImageOptions(builder: Builder<*>) : EncodedImageOptions(builde

protected fun equalDecodedOptions(other: DecodedImageOptions): Boolean {
return if (!Objects.equal(resizeOptions, other.resizeOptions) ||
!Objects.equal(downsampleOverride, other.downsampleOverride) ||
!Objects.equal(rotationOptions, other.rotationOptions) ||
!Objects.equal(postprocessor, other.postprocessor) ||
!Objects.equal(imageDecodeOptions, other.imageDecodeOptions) ||
Expand All @@ -60,6 +63,7 @@ open class DecodedImageOptions(builder: Builder<*>) : EncodedImageOptions(builde
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + (resizeOptions?.hashCode() ?: 0)
result = 31 * result + (downsampleOverride?.hashCode() ?: 0)
result = 31 * result + (rotationOptions?.hashCode() ?: 0)
result = 31 * result + (postprocessor?.hashCode() ?: 0)
result = 31 * result + (imageDecodeOptions?.hashCode() ?: 0)
Expand All @@ -79,6 +83,7 @@ open class DecodedImageOptions(builder: Builder<*>) : EncodedImageOptions(builde
override fun toStringHelper(): Objects.ToStringHelper =
super.toStringHelper()
.add("resizeOptions", resizeOptions)
.add("downsampleOverride", downsampleOverride)
.add("rotationOptions", resizeOptions)
.add("postprocessor", postprocessor)
.add("imageDecodeOptions", imageDecodeOptions)
Expand All @@ -93,6 +98,7 @@ open class DecodedImageOptions(builder: Builder<*>) : EncodedImageOptions(builde

open class Builder<T : Builder<T>> : EncodedImageOptions.Builder<T> {
internal var resizeOptions: ResizeOptions? = null
internal var downsampleOverride: DownsampleMode? = null
internal var rotationOptions: RotationOptions? = null
internal var postprocessor: Postprocessor? = null
internal var imageDecodeOptions: ImageDecodeOptions? = null
Expand All @@ -109,6 +115,7 @@ open class DecodedImageOptions(builder: Builder<*>) : EncodedImageOptions(builde

constructor(decodedImageOptions: DecodedImageOptions) : super(decodedImageOptions) {
resizeOptions = decodedImageOptions.resizeOptions
downsampleOverride = decodedImageOptions.downsampleOverride
rotationOptions = decodedImageOptions.rotationOptions
postprocessor = decodedImageOptions.postprocessor
imageDecodeOptions = decodedImageOptions.imageDecodeOptions
Expand All @@ -126,6 +133,16 @@ open class DecodedImageOptions(builder: Builder<*>) : EncodedImageOptions(builde

fun resize(resizeOptions: ResizeOptions?): T = modify { this.resizeOptions = resizeOptions }

/**
* Custom downsample override for this request. null -> use default pipeline's setting.
*
* @param downsampleOverride
* @return the builder
*/
fun downsampleOverride(downsampleOverride: DownsampleMode?): T = modify {
this.downsampleOverride = downsampleOverride
}

fun rotate(rotationOptions: RotationOptions?): T = modify {
this.rotationOptions = rotationOptions
}
Expand Down