Skip to content

Commit

Permalink
Add support for overriding downsample mode per image request
Browse files Browse the repository at this point in the history
Reviewed By: yungsters, defHLT

Differential Revision: D62393210

fbshipit-source-id: ca070fd9e01c09fef242eb0ab79ce6f9da7aff60
  • Loading branch information
Abbondanzo authored and facebook-github-bot committed Sep 17, 2024
1 parent b99d3a8 commit 9c57738
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 20 deletions.
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

0 comments on commit 9c57738

Please sign in to comment.