mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
feat(image): support for resizeMode and objectFit value of 'none' (#47110)
Summary: As part of https://github.com/facebook/react-native/issues/34425, `objectFit` value of `'none'` needs to be supported for the Image component. In order to support this, a new value must also be added to support the equivalent in `resizeMode`. With this new value, the image will not be resized at all and keeping it in the initial position within a container (see in the screenshots). In this PR the support is added for both Fabric and Paper. ## Changelog: [GENERAL] [ADDED] - image `resizeMode` and `objectFit` support for `'none'`. Pull Request resolved: https://github.com/facebook/react-native/pull/47110 Test Plan: Using the `rn-tester`, there is a new image example for both `resizeMode` and `objectFit`. See below the results for both Android and iOS: <details> <summary>Fabric screenshots</summary> **Android:** | Resize Mode | Object Fit | | --------- | ---------- | |  |  | **iOS:** | Resize Mode | Object Fit | | --------- | ---------- | |  |  | </details> <details> <summary>Paper screenshots</summary> **Android:** | Resize Mode | Object Fit | | --------- | ---------- | |  |  | **iOS:** | Resize Mode | Object Fit | | --------- | ---------- | |  |  | </details> Reviewed By: fabriziocucci Differential Revision: D65420002 Pulled By: javache fbshipit-source-id: df3bc8fc931b88cde5fe51d89685bf327e30ed9f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
d7d5de9f96
commit
d8cfd98070
@@ -200,6 +200,8 @@ export interface ImagePropsBase
|
||||
* 'center': Scale the image down so that it is completely visible,
|
||||
* if bigger than the area of the view.
|
||||
* The image will not be scaled up.
|
||||
*
|
||||
* 'none': Do not resize the image. The image will be displayed at its intrinsic size.
|
||||
*/
|
||||
resizeMode?: ImageResizeMode | undefined;
|
||||
|
||||
|
||||
+2
-1
@@ -19,6 +19,7 @@ import type {
|
||||
} from '../StyleSheet/StyleSheet';
|
||||
import type {LayoutEvent, SyntheticEvent} from '../Types/CoreEventTypes';
|
||||
import typeof Image from './Image';
|
||||
import type {ImageResizeMode} from './ImageResizeMode';
|
||||
import type {ImageSource} from './ImageSource';
|
||||
import type {ElementRef, Node, RefSetter} from 'react';
|
||||
|
||||
@@ -234,7 +235,7 @@ export type ImageProps = $ReadOnly<{|
|
||||
*
|
||||
* See https://reactnative.dev/docs/image#resizemode
|
||||
*/
|
||||
resizeMode?: ?('cover' | 'contain' | 'stretch' | 'repeat' | 'center'),
|
||||
resizeMode?: ?ImageResizeMode,
|
||||
|
||||
/**
|
||||
* A unique identifier for this element to be used in UI Automation
|
||||
|
||||
@@ -12,7 +12,8 @@ export type ImageResizeMode =
|
||||
| 'contain'
|
||||
| 'stretch'
|
||||
| 'repeat'
|
||||
| 'center';
|
||||
| 'center'
|
||||
| 'none';
|
||||
|
||||
/**
|
||||
* @see ImageResizeMode.js
|
||||
@@ -46,4 +47,10 @@ export interface ImageResizeModeStatic {
|
||||
* image will keep it's size and aspect ratio.
|
||||
*/
|
||||
repeat: ImageResizeMode;
|
||||
|
||||
/**
|
||||
* none - The image will be displayed at its intrinsic size, which means the
|
||||
* image will not be scaled up or down.
|
||||
*/
|
||||
none: ImageResizeMode;
|
||||
}
|
||||
|
||||
@@ -33,4 +33,7 @@ export type ImageResizeMode =
|
||||
|
||||
// Resize by stretching it to fill the entire frame of the view without
|
||||
// clipping. This may change the aspect ratio of the image, distorting it.
|
||||
| 'stretch';
|
||||
| 'stretch'
|
||||
|
||||
// The image will not be resized at all.
|
||||
| 'none';
|
||||
|
||||
+6
-3
@@ -8,15 +8,18 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
type ResizeMode = 'cover' | 'contain' | 'stretch' | 'repeat' | 'center';
|
||||
import type {ImageResizeMode} from './ImageResizeMode';
|
||||
|
||||
const objectFitMap: {[string]: ResizeMode} = {
|
||||
const objectFitMap: {[string]: ImageResizeMode} = {
|
||||
contain: 'contain',
|
||||
cover: 'cover',
|
||||
fill: 'stretch',
|
||||
'scale-down': 'contain',
|
||||
none: 'none',
|
||||
};
|
||||
|
||||
export function convertObjectFitToResizeMode(objectFit: ?string): ?ResizeMode {
|
||||
export function convertObjectFitToResizeMode(
|
||||
objectFit: ?string,
|
||||
): ?ImageResizeMode {
|
||||
return objectFit != null ? objectFitMap[objectFit] : undefined;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize, CGFloat destScale, RCTR
|
||||
switch (resizeMode) {
|
||||
case RCTResizeModeStretch:
|
||||
case RCTResizeModeRepeat:
|
||||
case RCTResizeModeNone:
|
||||
|
||||
return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
|
||||
|
||||
@@ -249,6 +250,7 @@ BOOL RCTUpscalingRequired(
|
||||
|
||||
case RCTResizeModeRepeat:
|
||||
case RCTResizeModeCenter:
|
||||
case RCTResizeModeNone:
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ typedef NS_ENUM(NSInteger, RCTResizeMode) {
|
||||
RCTResizeModeStretch = UIViewContentModeScaleToFill,
|
||||
RCTResizeModeCenter = UIViewContentModeCenter,
|
||||
RCTResizeModeRepeat = -1, // Use negative values to avoid conflicts with iOS enum values.
|
||||
RCTResizeModeNone = UIViewContentModeTopLeft,
|
||||
};
|
||||
|
||||
static inline RCTResizeMode RCTResizeModeFromUIViewContentMode(UIViewContentMode mode)
|
||||
@@ -30,12 +31,14 @@ static inline RCTResizeMode RCTResizeModeFromUIViewContentMode(UIViewContentMode
|
||||
case UIViewContentModeCenter:
|
||||
return RCTResizeModeCenter;
|
||||
break;
|
||||
case UIViewContentModeTopLeft:
|
||||
return RCTResizeModeNone;
|
||||
break;
|
||||
case UIViewContentModeRedraw:
|
||||
case UIViewContentModeTop:
|
||||
case UIViewContentModeBottom:
|
||||
case UIViewContentModeLeft:
|
||||
case UIViewContentModeRight:
|
||||
case UIViewContentModeTopLeft:
|
||||
case UIViewContentModeTopRight:
|
||||
case UIViewContentModeBottomLeft:
|
||||
case UIViewContentModeBottomRight:
|
||||
|
||||
@@ -17,6 +17,7 @@ RCT_ENUM_CONVERTER(
|
||||
@"stretch" : @(RCTResizeModeStretch),
|
||||
@"center" : @(RCTResizeModeCenter),
|
||||
@"repeat" : @(RCTResizeModeRepeat),
|
||||
@"none" : @(RCTResizeModeNone),
|
||||
}),
|
||||
RCTResizeModeStretch,
|
||||
integerValue)
|
||||
|
||||
@@ -568,6 +568,6 @@ export interface ImageStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle {
|
||||
overlayColor?: ColorValue | undefined;
|
||||
tintColor?: ColorValue | undefined;
|
||||
opacity?: AnimatableNumericValue | undefined;
|
||||
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | undefined;
|
||||
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none' | undefined;
|
||||
cursor?: CursorValue | undefined;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
'use strict';
|
||||
|
||||
import type AnimatedNode from '../Animated/nodes/AnimatedNode';
|
||||
import type {ImageResizeMode} from './../Image/ImageResizeMode';
|
||||
import type {
|
||||
____DangerouslyImpreciseStyle_InternalOverrides,
|
||||
____ImageStyle_InternalOverrides,
|
||||
@@ -941,8 +942,8 @@ export type ____TextStyle_Internal = $ReadOnly<{
|
||||
|
||||
export type ____ImageStyle_InternalCore = $ReadOnly<{
|
||||
...$Exact<____ViewStyle_Internal>,
|
||||
resizeMode?: 'contain' | 'cover' | 'stretch' | 'center' | 'repeat',
|
||||
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down',
|
||||
resizeMode?: ImageResizeMode,
|
||||
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none',
|
||||
tintColor?: ____ColorValue_Internal,
|
||||
overlayColor?: string,
|
||||
}>;
|
||||
@@ -954,8 +955,8 @@ export type ____ImageStyle_Internal = $ReadOnly<{
|
||||
|
||||
export type ____DangerouslyImpreciseStyle_InternalCore = $ReadOnly<{
|
||||
...$Exact<____TextStyle_Internal>,
|
||||
resizeMode?: 'contain' | 'cover' | 'stretch' | 'center' | 'repeat',
|
||||
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down',
|
||||
resizeMode?: ImageResizeMode,
|
||||
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none',
|
||||
tintColor?: ____ColorValue_Internal,
|
||||
overlayColor?: string,
|
||||
}>;
|
||||
|
||||
@@ -4869,7 +4869,7 @@ export type ImageProps = $ReadOnly<{|
|
||||
| \\"strict-origin-when-cross-origin\\"
|
||||
| \\"unsafe-url\\"
|
||||
),
|
||||
resizeMode?: ?(\\"cover\\" | \\"contain\\" | \\"stretch\\" | \\"repeat\\" | \\"center\\"),
|
||||
resizeMode?: ?ImageResizeMode,
|
||||
testID?: ?string,
|
||||
tintColor?: ColorValue,
|
||||
src?: ?string,
|
||||
@@ -4892,7 +4892,8 @@ exports[`public API should not change unintentionally Libraries/Image/ImageResiz
|
||||
| \\"contain\\"
|
||||
| \\"cover\\"
|
||||
| \\"repeat\\"
|
||||
| \\"stretch\\";
|
||||
| \\"stretch\\"
|
||||
| \\"none\\";
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -4989,10 +4990,9 @@ export type { ImageProps } from \\"./ImageProps\\";
|
||||
`;
|
||||
|
||||
exports[`public API should not change unintentionally Libraries/Image/ImageUtils.js 1`] = `
|
||||
"type ResizeMode = \\"cover\\" | \\"contain\\" | \\"stretch\\" | \\"repeat\\" | \\"center\\";
|
||||
declare export function convertObjectFitToResizeMode(
|
||||
"declare export function convertObjectFitToResizeMode(
|
||||
objectFit: ?string
|
||||
): ?ResizeMode;
|
||||
): ?ImageResizeMode;
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -8261,8 +8261,8 @@ export type ____TextStyle_Internal = $ReadOnly<{
|
||||
}>;
|
||||
export type ____ImageStyle_InternalCore = $ReadOnly<{
|
||||
...$Exact<____ViewStyle_Internal>,
|
||||
resizeMode?: \\"contain\\" | \\"cover\\" | \\"stretch\\" | \\"center\\" | \\"repeat\\",
|
||||
objectFit?: \\"cover\\" | \\"contain\\" | \\"fill\\" | \\"scale-down\\",
|
||||
resizeMode?: ImageResizeMode,
|
||||
objectFit?: \\"cover\\" | \\"contain\\" | \\"fill\\" | \\"scale-down\\" | \\"none\\",
|
||||
tintColor?: ____ColorValue_Internal,
|
||||
overlayColor?: string,
|
||||
}>;
|
||||
@@ -8272,8 +8272,8 @@ export type ____ImageStyle_Internal = $ReadOnly<{
|
||||
}>;
|
||||
export type ____DangerouslyImpreciseStyle_InternalCore = $ReadOnly<{
|
||||
...$Exact<____TextStyle_Internal>,
|
||||
resizeMode?: \\"contain\\" | \\"cover\\" | \\"stretch\\" | \\"center\\" | \\"repeat\\",
|
||||
objectFit?: \\"cover\\" | \\"contain\\" | \\"fill\\" | \\"scale-down\\",
|
||||
resizeMode?: ImageResizeMode,
|
||||
objectFit?: \\"cover\\" | \\"contain\\" | \\"fill\\" | \\"scale-down\\" | \\"none\\",
|
||||
tintColor?: ____ColorValue_Internal,
|
||||
overlayColor?: string,
|
||||
}>;
|
||||
|
||||
+4
-1
@@ -19,6 +19,7 @@ public object ImageResizeMode {
|
||||
private const val RESIZE_MODE_STRETCH = "stretch"
|
||||
private const val RESIZE_MODE_CENTER = "center"
|
||||
private const val RESIZE_MODE_REPEAT = "repeat"
|
||||
private const val RESIZE_MODE_NONE = "none"
|
||||
|
||||
/** Converts JS resize modes into `ScalingUtils.ScaleType`. See `ImageResizeMode.js`. */
|
||||
@JvmStatic
|
||||
@@ -30,6 +31,7 @@ public object ImageResizeMode {
|
||||
RESIZE_MODE_CENTER -> return ScalingUtils.ScaleType.CENTER_INSIDE
|
||||
// Handled via a combination of ScaleType and TileMode
|
||||
RESIZE_MODE_REPEAT -> return ScaleTypeStartInside.INSTANCE
|
||||
RESIZE_MODE_NONE -> return ScaleTypeStartInside.INSTANCE
|
||||
}
|
||||
|
||||
if (resizeModeValue != null) {
|
||||
@@ -45,7 +47,8 @@ public object ImageResizeMode {
|
||||
if (RESIZE_MODE_CONTAIN == resizeModeValue ||
|
||||
RESIZE_MODE_COVER == resizeModeValue ||
|
||||
RESIZE_MODE_STRETCH == resizeModeValue ||
|
||||
RESIZE_MODE_CENTER == resizeModeValue) {
|
||||
RESIZE_MODE_CENTER == resizeModeValue ||
|
||||
RESIZE_MODE_NONE == resizeModeValue) {
|
||||
return TileMode.CLAMP
|
||||
}
|
||||
if (RESIZE_MODE_REPEAT == resizeModeValue) {
|
||||
|
||||
+2
@@ -28,6 +28,8 @@ class ImageResizeModeTest {
|
||||
.isEqualTo(ScalingUtils.ScaleType.FIT_XY)
|
||||
Assertions.assertThat(ImageResizeMode.toScaleType("center"))
|
||||
.isEqualTo(ScalingUtils.ScaleType.CENTER_INSIDE)
|
||||
Assertions.assertThat(ImageResizeMode.toScaleType("none"))
|
||||
.isEqualTo(ScaleTypeStartInside.INSTANCE)
|
||||
|
||||
// No resizeMode set
|
||||
Assertions.assertThat(ImageResizeMode.defaultValue())
|
||||
|
||||
@@ -148,6 +148,8 @@ inline void fromRawValue(
|
||||
result = ImageResizeMode::Center;
|
||||
} else if (stringValue == "repeat") {
|
||||
result = ImageResizeMode::Repeat;
|
||||
} else if (stringValue == "none") {
|
||||
result = ImageResizeMode::None;
|
||||
} else {
|
||||
LOG(ERROR) << "Unsupported ImageResizeMode value: " << stringValue;
|
||||
react_native_expect(false);
|
||||
@@ -168,6 +170,8 @@ inline std::string toString(const ImageResizeMode& value) {
|
||||
return "center";
|
||||
case ImageResizeMode::Repeat:
|
||||
return "repeat";
|
||||
case ImageResizeMode::None:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -26,6 +26,8 @@ inline static UIViewContentMode RCTContentModeFromImageResizeMode(facebook::reac
|
||||
// Repeat resize mode is handled by the UIImage. Use scale to fill
|
||||
// so the repeated image fills the UIImageView.
|
||||
return UIViewContentModeScaleToFill;
|
||||
case facebook::react::ImageResizeMode::None:
|
||||
return UIViewContentModeTopLeft;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +44,8 @@ inline std::string toString(const facebook::react::ImageResizeMode &value)
|
||||
return "center";
|
||||
case facebook::react::ImageResizeMode::Repeat:
|
||||
return "repeat";
|
||||
case facebook::react::ImageResizeMode::None:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ enum class ImageResizeMode {
|
||||
Stretch,
|
||||
Center,
|
||||
Repeat,
|
||||
None,
|
||||
};
|
||||
|
||||
class ImageErrorInfo {
|
||||
|
||||
@@ -825,6 +825,9 @@ const styles = StyleSheet.create({
|
||||
objectFitScaleDown: {
|
||||
objectFit: 'scale-down',
|
||||
},
|
||||
objectFitNone: {
|
||||
objectFit: 'none',
|
||||
},
|
||||
imageInBundle: {
|
||||
borderColor: 'yellow',
|
||||
borderWidth: 4,
|
||||
@@ -1466,6 +1469,17 @@ exports.examples = [
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.horizontal}>
|
||||
<View>
|
||||
<RNTesterText style={styles.resizeModeText}>
|
||||
None
|
||||
</RNTesterText>
|
||||
<Image
|
||||
style={[styles.resizeMode, styles.objectFitNone]}
|
||||
source={image}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
@@ -1537,6 +1551,18 @@ exports.examples = [
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.horizontal}>
|
||||
<View>
|
||||
<RNTesterText style={styles.resizeModeText}>
|
||||
None
|
||||
</RNTesterText>
|
||||
<Image
|
||||
style={styles.resizeMode}
|
||||
resizeMode="none"
|
||||
source={image}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user