diff --git a/Libraries/Components/View/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js index dbf500a25925fd..28fd4596338890 100644 --- a/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -143,6 +143,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = { overlayColor: colorAttributes, resizeMode: true, tintColor: colorAttributes, + objectFit: true, }; module.exports = ReactNativeStyleAttributes; diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index 1a58c2a97d8227..0e34214cb58b5e 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -21,6 +21,8 @@ import NativeImageLoaderAndroid from './NativeImageLoaderAndroid'; import TextInlineImageNativeComponent from './TextInlineImageNativeComponent'; +import {convertObjectFitToResizeMode} from './ImageUtils'; + import type {ImageProps as ImagePropsType} from './ImageProps'; import type {RootTag} from '../Types/RootTagTypes'; import {getImageSourcesFromImageProps} from './ImageSourceUtils'; @@ -187,6 +189,14 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => { }, }; + const objectFit = + style && style.objectFit + ? convertObjectFitToResizeMode(style.objectFit) + : null; + // $FlowFixMe[prop-missing] + const resizeMode = + objectFit || props.resizeMode || (style && style.resizeMode) || 'cover'; + return ( {analyticTag => { @@ -205,7 +215,7 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => { return ( { ); } - return ; + return ( + + ); }} ); diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index ad5d5e8f90b5a8..0bbd134a60b431 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -21,6 +21,8 @@ import type {ImageProps as ImagePropsType} from './ImageProps'; import type {ImageStyleProp} from '../StyleSheet/StyleSheet'; import NativeImageLoaderIOS from './NativeImageLoaderIOS'; +import {convertObjectFitToResizeMode} from './ImageUtils'; + import ImageViewNativeComponent from './ImageViewNativeComponent'; import type {RootTag} from 'react-native/Libraries/Types/RootTagTypes'; import {getImageSourcesFromImageProps} from './ImageSourceUtils'; @@ -127,8 +129,14 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => { } } - // $FlowFixMe[prop-missing] - const resizeMode = props.resizeMode || style.resizeMode || 'cover'; + const objectFit = + // $FlowFixMe[prop-missing] + style && style.objectFit + ? convertObjectFitToResizeMode(style.objectFit) + : null; + const resizeMode = + // $FlowFixMe[prop-missing] + objectFit || props.resizeMode || (style && style.resizeMode) || 'cover'; // $FlowFixMe[prop-missing] const tintColor = props.tintColor || style.tintColor; diff --git a/Libraries/Image/ImageUtils.js b/Libraries/Image/ImageUtils.js new file mode 100644 index 00000000000000..734f36c7e90bf0 --- /dev/null +++ b/Libraries/Image/ImageUtils.js @@ -0,0 +1,21 @@ +/** + * 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. + * + * @flow + * @format + */ + +type ResizeMode = 'cover' | 'contain' | 'stretch' | 'repeat' | 'center'; + +export function convertObjectFitToResizeMode(objectFit: string): ResizeMode { + const objectFitMap = { + contain: 'contain', + cover: 'cover', + fill: 'stretch', + 'scale-down': 'contain', + }; + return objectFitMap[objectFit]; +} diff --git a/Libraries/StyleSheet/StyleSheetTypes.js b/Libraries/StyleSheet/StyleSheetTypes.js index ee11fab8aa5d26..e667dddf911c4c 100644 --- a/Libraries/StyleSheet/StyleSheetTypes.js +++ b/Libraries/StyleSheet/StyleSheetTypes.js @@ -644,6 +644,7 @@ 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', tintColor?: ____ColorValue_Internal, overlayColor?: string, }>; @@ -656,6 +657,7 @@ 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', tintColor?: ____ColorValue_Internal, overlayColor?: string, }>; diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index cddfc87a83517d..dcb2dbdf11d75a 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -1061,6 +1061,55 @@ exports.examples = [ ); }, }, + { + title: 'Object Fit', + description: ('The `objectFit` style prop controls how the image is ' + + 'rendered within the frame.': string), + render: function (): React.Node { + return ( + + {[smallImage, fullImage].map((image, index) => { + return ( + + + + Contain + + + + Cover + + + + + + Fill + + + + Scale Down + + + + + ); + })} + + ); + }, + }, { title: 'Resize Mode', description: ('The `resizeMode` style prop controls how the image is ' +