diff --git a/packages/react-native/Libraries/Image/Image.d.ts b/packages/react-native/Libraries/Image/Image.d.ts index 95e42447033..d50679a548b 100644 --- a/packages/react-native/Libraries/Image/Image.d.ts +++ b/packages/react-native/Libraries/Image/Image.d.ts @@ -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; diff --git a/packages/react-native/Libraries/Image/ImageProps.js b/packages/react-native/Libraries/Image/ImageProps.js index c0c39075872..80c05862e43 100644 --- a/packages/react-native/Libraries/Image/ImageProps.js +++ b/packages/react-native/Libraries/Image/ImageProps.js @@ -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 diff --git a/packages/react-native/Libraries/Image/ImageResizeMode.d.ts b/packages/react-native/Libraries/Image/ImageResizeMode.d.ts index 84174e3da96..5df952b678d 100644 --- a/packages/react-native/Libraries/Image/ImageResizeMode.d.ts +++ b/packages/react-native/Libraries/Image/ImageResizeMode.d.ts @@ -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; } diff --git a/packages/react-native/Libraries/Image/ImageResizeMode.js b/packages/react-native/Libraries/Image/ImageResizeMode.js index b63627b793b..50001ce552c 100644 --- a/packages/react-native/Libraries/Image/ImageResizeMode.js +++ b/packages/react-native/Libraries/Image/ImageResizeMode.js @@ -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'; diff --git a/packages/react-native/Libraries/Image/ImageUtils.js b/packages/react-native/Libraries/Image/ImageUtils.js index 732b5733bc6..c0e00bb534a 100644 --- a/packages/react-native/Libraries/Image/ImageUtils.js +++ b/packages/react-native/Libraries/Image/ImageUtils.js @@ -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; } diff --git a/packages/react-native/Libraries/Image/RCTImageUtils.mm b/packages/react-native/Libraries/Image/RCTImageUtils.mm index 38fbd6bad43..b7f85f83fc3 100644 --- a/packages/react-native/Libraries/Image/RCTImageUtils.mm +++ b/packages/react-native/Libraries/Image/RCTImageUtils.mm @@ -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; } diff --git a/packages/react-native/Libraries/Image/RCTResizeMode.h b/packages/react-native/Libraries/Image/RCTResizeMode.h index 148e3dd2fe9..e65f113a0dc 100644 --- a/packages/react-native/Libraries/Image/RCTResizeMode.h +++ b/packages/react-native/Libraries/Image/RCTResizeMode.h @@ -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: diff --git a/packages/react-native/Libraries/Image/RCTResizeMode.mm b/packages/react-native/Libraries/Image/RCTResizeMode.mm index c35c32e3fd1..88ea96eb036 100644 --- a/packages/react-native/Libraries/Image/RCTResizeMode.mm +++ b/packages/react-native/Libraries/Image/RCTResizeMode.mm @@ -17,6 +17,7 @@ RCT_ENUM_CONVERTER( @"stretch" : @(RCTResizeModeStretch), @"center" : @(RCTResizeModeCenter), @"repeat" : @(RCTResizeModeRepeat), + @"none" : @(RCTResizeModeNone), }), RCTResizeModeStretch, integerValue) diff --git a/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts b/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts index a4aa69d0275..05a8b6410f4 100644 --- a/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts +++ b/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts @@ -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; } diff --git a/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js b/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js index ebc1dfa98b0..0d75a942040 100644 --- a/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js +++ b/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js @@ -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, }>; diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index ceca9bbce45..ea0a2fe12ef 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -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, }>; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageResizeMode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageResizeMode.kt index 76da29958b1..61098ad9c38 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageResizeMode.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageResizeMode.kt @@ -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) { diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.kt index 1c6f98d76ec..c3c589986af 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.kt @@ -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()) diff --git a/packages/react-native/ReactCommon/react/renderer/components/image/conversions.h b/packages/react-native/ReactCommon/react/renderer/components/image/conversions.h index 0a76824f588..2803206c792 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/image/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/image/conversions.h @@ -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"; } } diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h index 88487f94d48..50194b7a754 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h @@ -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"; } } diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/primitives.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/primitives.h index 18aba9e10b5..e8d4be21db4 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/primitives.h @@ -47,6 +47,7 @@ enum class ImageResizeMode { Stretch, Center, Repeat, + None, }; class ImageErrorInfo { diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 81e42ad8adb..b3dddd83e87 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -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 = [ /> + + + + None + + + + ); })} @@ -1537,6 +1551,18 @@ exports.examples = [ /> + + + + None + + + + ); })}