Skip to content

Commit b6c3433

Browse files
Abbondanzofacebook-github-bot
authored andcommitted
Hybrid image aliasing (#44803)
Summary: Pull Request resolved: #44803 This change introduces a new prop to the Android `Image` component: `resizeMultiplier`. This prop can be used when the `resizeMethod` is set to `resize`, and it directly modifies the resultant bitmap generated in memory from Fresco to be larger (or smaller) depending on the multiplier. A default of 1.0 means the bitmap size is designed to fit the destination dimensions. A multiplier greater than 1.0 will set the `ResizeOptions` provided to Fresco to be larger that the destination dimensions, and the resulting bitmap will be scaled from the hardware size. This new prop is most useful in cases where the destination dimensions are quite small and the source image is significantly larger. The `resize` resize method performs downsampling and significant image quality is lost between the source and destination image sizes, often resulting in a blurry image. By using a multiplier, the decoded image is slightly larger than the target size but smaller than the source image (if the source image is large enough). It's important to note that Fresco still chooses the closest power of 2 and will not scale the image larger than its source dimensions. If the multiplier yields `ResizeOptions` greater than the source dimensions, no downsampling occurs. Here's an example: If you have a source image with dimensions 200x200 and destination dimensions of 24x24, a `resizeMultiplier` of `2.0` will tell Fresco to downsample the image to 48x48. Fresco picks the closest power of 2 (so, 50x50) and decodes the image into a bitmap of that size. Without the multiplier, the closest power of 2 would be 25x25, which is half the quality. ## Changelog [Android][Added] - Adds a new `Image` prop `resizeMultiplier` to help increase quality of small images on low DPI devices Reviewed By: javache Differential Revision: D58120352 fbshipit-source-id: e0ebf4bd899170134825a29f72a68621447106c0
1 parent deb4819 commit b6c3433

File tree

7 files changed

+51
-9
lines changed

7 files changed

+51
-9
lines changed

packages/react-native/Libraries/Image/ImageProps.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,22 @@ type AndroidImageProps = $ReadOnly<{|
5959
loadingIndicatorSource?: ?(number | $ReadOnly<{|uri: string|}>),
6060
progressiveRenderingEnabled?: ?boolean,
6161
fadeDuration?: ?number,
62+
63+
/**
64+
* The mechanism that should be used to resize the image when the image's
65+
* dimensions differ from the image view's dimensions. Defaults to `'auto'`.
66+
* See https://reactnative.dev/docs/image#resizemethod-android
67+
*/
68+
resizeMethod?: ?('auto' | 'resize' | 'scale'),
69+
70+
/**
71+
* When the `resizeMethod` is set to `resize`, the destination dimensions are
72+
* multiplied by this value. The `scale` method is used to perform the
73+
* remainder of the resize.
74+
* This is used to produce higher quality images when resizing to small dimensions.
75+
* Defaults to 1.0.
76+
*/
77+
resizeMultiplier?: ?number,
6278
|}>;
6379

6480
export type ImageProps = {|
@@ -183,11 +199,6 @@ export type ImageProps = {|
183199
*/
184200
onLoadStart?: ?() => void,
185201

186-
/**
187-
* See https://reactnative.dev/docs/image#resizemethod
188-
*/
189-
resizeMethod?: ?('auto' | 'resize' | 'scale'),
190-
191202
/**
192203
* The image source (either a remote URL or a local file resource).
193204
*

packages/react-native/Libraries/Image/ImageViewNativeComponent.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,14 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
8282
validAttributes: {
8383
blurRadius: true,
8484
internal_analyticTag: true,
85+
resizeMethod: true,
8586
resizeMode: true,
87+
resizeMultiplier: true,
8688
tintColor: {
8789
process: require('../StyleSheet/processColor').default,
8890
},
8991
borderBottomLeftRadius: true,
9092
borderTopLeftRadius: true,
91-
resizeMethod: true,
9293
src: true,
9394
// NOTE: New Architecture expects this to be called `source`,
9495
// regardless of the platform, therefore propagate it as well.

packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4445,6 +4445,8 @@ type AndroidImageProps = $ReadOnly<{|
44454445
loadingIndicatorSource?: ?(number | $ReadOnly<{| uri: string |}>),
44464446
progressiveRenderingEnabled?: ?boolean,
44474447
fadeDuration?: ?number,
4448+
resizeMethod?: ?(\\"auto\\" | \\"resize\\" | \\"scale\\"),
4449+
resizeMultiplier?: ?number,
44484450
|}>;
44494451
export type ImageProps = {|
44504452
...$Diff<ViewProps, $ReadOnly<{| style: ?ViewStyleProp |}>>,
@@ -4472,7 +4474,6 @@ export type ImageProps = {|
44724474
onLoad?: ?(event: ImageLoadEvent) => void,
44734475
onLoadEnd?: ?() => void,
44744476
onLoadStart?: ?() => void,
4475-
resizeMethod?: ?(\\"auto\\" | \\"resize\\" | \\"scale\\"),
44764477
source?: ?ImageSource,
44774478
style?: ?ImageStyleProp,
44784479
referrerPolicy?: ?(

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6390,6 +6390,7 @@ public class com/facebook/react/views/image/ReactImageManager : com/facebook/rea
63906390
public fun setProgressiveRenderingEnabled (Lcom/facebook/react/views/image/ReactImageView;Z)V
63916391
public fun setResizeMethod (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V
63926392
public fun setResizeMode (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V
6393+
public fun setResizeMultiplier (Lcom/facebook/react/views/image/ReactImageView;F)V
63936394
public fun setSource (Lcom/facebook/react/views/image/ReactImageView;Lcom/facebook/react/bridge/ReadableArray;)V
63946395
public fun setSrc (Lcom/facebook/react/views/image/ReactImageView;Lcom/facebook/react/bridge/ReadableArray;)V
63956396
public fun setTintColor (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/Integer;)V
@@ -6423,6 +6424,7 @@ public class com/facebook/react/views/image/ReactImageView : com/facebook/drawee
64236424
public fun setOverlayColor (I)V
64246425
public fun setProgressiveRenderingEnabled (Z)V
64256426
public fun setResizeMethod (Lcom/facebook/react/views/image/ImageResizeMethod;)V
6427+
public fun setResizeMultiplier (F)V
64266428
public fun setScaleType (Lcom/facebook/drawee/drawable/ScalingUtils$ScaleType;)V
64276429
public fun setShouldNotifyLoadEvents (Z)V
64286430
public fun setSource (Lcom/facebook/react/bridge/ReadableArray;)V

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageResizeMethod.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ package com.facebook.react.views.image
1010
public enum class ImageResizeMethod {
1111
AUTO,
1212
RESIZE,
13-
SCALE
13+
SCALE,
1414
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,14 @@ public void setResizeMethod(ReactImageView view, @Nullable String resizeMethod)
223223
}
224224
}
225225

226+
@ReactProp(name = "resizeMultiplier")
227+
public void setResizeMultiplier(ReactImageView view, float resizeMultiplier) {
228+
if (resizeMultiplier < 0.01f) {
229+
FLog.w(ReactConstants.TAG, "Invalid resize multiplier: '" + resizeMultiplier + "'");
230+
}
231+
view.setResizeMultiplier(resizeMultiplier);
232+
}
233+
226234
@ReactProp(name = "tintColor", customType = "Color")
227235
public void setTintColor(ReactImageView view, @Nullable Integer tintColor) {
228236
if (tintColor == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public CloseableReference<Bitmap> process(Bitmap source, PlatformBitmapFactory b
134134
private int mFadeDurationMs = -1;
135135
private boolean mProgressiveRenderingEnabled;
136136
private ReadableMap mHeaders;
137+
private float mResizeMultiplier = 1.0f;
137138

138139
// We can't specify rounding in XML, so have to do so here
139140
private static GenericDraweeHierarchy buildHierarchy(Context context) {
@@ -307,6 +308,14 @@ public void setResizeMethod(ImageResizeMethod resizeMethod) {
307308
}
308309
}
309310

311+
public void setResizeMultiplier(float multiplier) {
312+
boolean isNewMultiplier = Math.abs(mResizeMultiplier - multiplier) > 0.0001f;
313+
if (isNewMultiplier) {
314+
mResizeMultiplier = multiplier;
315+
mIsDirty = true;
316+
}
317+
}
318+
310319
public void setSource(@Nullable ReadableArray sources) {
311320
List<ImageSource> tmpSources = new LinkedList<>();
312321

@@ -478,7 +487,7 @@ public void maybeUpdateView() {
478487
}
479488
Postprocessor postprocessor = MultiPostprocessor.from(postprocessors);
480489

481-
ResizeOptions resizeOptions = doResize ? new ResizeOptions(getWidth(), getHeight()) : null;
490+
ResizeOptions resizeOptions = doResize ? getResizeOptions() : null;
482491

483492
ImageRequestBuilder imageRequestBuilder =
484493
ImageRequestBuilder.newBuilderWithSource(mImageSource.getUri())
@@ -601,6 +610,16 @@ private boolean shouldResize(ImageSource imageSource) {
601610
}
602611
}
603612

613+
@Nullable
614+
private ResizeOptions getResizeOptions() {
615+
int width = Math.round((float) getWidth() * mResizeMultiplier);
616+
int height = Math.round((float) getHeight() * mResizeMultiplier);
617+
if (width <= 0 || height <= 0) {
618+
return null;
619+
}
620+
return new ResizeOptions(width, height);
621+
}
622+
604623
private void warnImageSource(String uri) {
605624
// TODO(T189014077): This code-path produces an infinite loop of js calls with logbox.
606625
// This is an issue with Fabric view preallocation, react, and LogBox. Fix.

0 commit comments

Comments
 (0)