2015-09-14 14:35:58 +00:00
/ * *
* Copyright ( c ) 2015 - present , Facebook , Inc .
*
2018-02-17 02:24:55 +00:00
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree .
2015-09-14 14:35:58 +00:00
*
* @ providesModule Image
* @ flow
2018-01-15 03:32:26 +00:00
* @ format
2015-09-14 14:35:58 +00:00
* /
'use strict' ;
var ImageResizeMode = require ( 'ImageResizeMode' ) ;
var ImageStylePropTypes = require ( 'ImageStylePropTypes' ) ;
2017-03-02 15:32:08 +00:00
var NativeMethodsMixin = require ( 'NativeMethodsMixin' ) ;
var NativeModules = require ( 'NativeModules' ) ;
2015-09-14 14:35:58 +00:00
var React = require ( 'React' ) ;
2017-04-12 23:09:48 +00:00
var PropTypes = require ( 'prop-types' ) ;
2015-09-14 14:35:58 +00:00
var ReactNativeViewAttributes = require ( 'ReactNativeViewAttributes' ) ;
var StyleSheet = require ( 'StyleSheet' ) ;
var StyleSheetPropType = require ( 'StyleSheetPropType' ) ;
2017-04-12 23:09:48 +00:00
var ViewPropTypes = require ( 'ViewPropTypes' ) ;
2015-09-14 14:35:58 +00:00
2017-07-07 21:24:25 +00:00
var createReactClass = require ( 'create-react-class' ) ;
2015-09-14 14:35:58 +00:00
var flattenStyle = require ( 'flattenStyle' ) ;
var merge = require ( 'merge' ) ;
2015-11-18 16:24:26 +00:00
var requireNativeComponent = require ( 'requireNativeComponent' ) ;
2015-09-14 14:35:58 +00:00
var resolveAssetSource = require ( 'resolveAssetSource' ) ;
2018-01-15 03:32:33 +00:00
const { ViewContextTypes } = require ( 'ViewContext' ) ;
2018-01-15 03:32:26 +00:00
var { ImageLoader } = NativeModules ;
2016-04-13 14:29:10 +00:00
2016-07-18 19:07:29 +00:00
let _requestId = 1 ;
function generateRequestId ( ) {
return _requestId ++ ;
}
2015-09-14 14:35:58 +00:00
/ * *
2018-01-30 00:10:49 +00:00
* A React component for displaying different types of images ,
2015-09-14 14:35:58 +00:00
* including network images , static resources , temporary local images , and
2018-01-30 00:10:49 +00:00
* images from local disk , such as the camera roll .
2015-09-14 14:35:58 +00:00
*
2018-01-30 00:10:49 +00:00
* See https : //facebook.github.io/react-native/docs/image.html
2015-09-14 14:35:58 +00:00
* /
2017-07-07 21:24:25 +00:00
var Image = createReactClass ( {
displayName : 'Image' ,
2015-09-14 14:35:58 +00:00
propTypes : {
2017-03-24 07:22:57 +00:00
... ViewPropTypes ,
2016-02-10 03:15:52 +00:00
style : StyleSheetPropType ( ImageStylePropTypes ) ,
2018-01-15 03:32:26 +00:00
/ * *
2018-01-30 00:10:49 +00:00
* See https : //facebook.github.io/react-native/docs/image.html#source
2015-10-09 16:31:51 +00:00
* /
source : PropTypes . oneOfType ( [
PropTypes . shape ( {
uri : PropTypes . string ,
2017-02-18 12:33:59 +00:00
headers : PropTypes . objectOf ( PropTypes . string ) ,
2015-10-09 16:31:51 +00:00
} ) ,
// Opaque type returned by require('./image.jpg')
PropTypes . number ,
2016-06-13 21:04:19 +00:00
// Multiple sources
PropTypes . arrayOf (
PropTypes . shape ( {
uri : PropTypes . string ,
width : PropTypes . number ,
height : PropTypes . number ,
2017-10-03 06:19:31 +00:00
headers : PropTypes . objectOf ( PropTypes . string ) ,
2018-01-15 03:32:26 +00:00
} ) ,
) ,
2016-02-10 03:15:52 +00:00
] ) ,
2017-03-02 15:32:08 +00:00
/ * *
2018-01-15 03:32:26 +00:00
* blurRadius : the blur radius of the blur filter added to the image
2018-01-30 00:10:49 +00:00
*
* See https : //facebook.github.io/react-native/docs/image.html#blurradius
2018-01-15 03:32:26 +00:00
* /
2017-03-02 15:32:08 +00:00
blurRadius : PropTypes . number ,
2015-12-06 23:44:47 +00:00
/ * *
2018-01-30 00:10:49 +00:00
* See https : //facebook.github.io/react-native/docs/image.html#loadingindicatorsource
2015-12-06 23:44:47 +00:00
* /
loadingIndicatorSource : PropTypes . oneOfType ( [
PropTypes . shape ( {
uri : PropTypes . string ,
} ) ,
// Opaque type returned by require('./image.jpg')
PropTypes . number ,
] ) ,
2015-11-16 19:39:19 +00:00
progressiveRenderingEnabled : PropTypes . bool ,
fadeDuration : PropTypes . number ,
2015-11-26 01:06:59 +00:00
/ * *
* Invoked on load start
* /
onLoadStart : PropTypes . func ,
2016-11-15 03:42:34 +00:00
/ * *
* Invoked on load error
* /
onError : PropTypes . func ,
2015-11-26 01:06:59 +00:00
/ * *
* Invoked when load completes successfully
* /
onLoad : PropTypes . func ,
/ * *
* Invoked when load either succeeds or fails
* /
onLoadEnd : PropTypes . func ,
2015-09-14 14:35:58 +00:00
/ * *
* Used to locate this view in end - to - end tests .
* /
testID : PropTypes . string ,
2016-09-09 12:00:52 +00:00
/ * *
* The mechanism that should be used to resize the image when the image ' s dimensions
* differ from the image view ' s dimensions . Defaults to ` auto ` .
*
2018-01-30 00:10:49 +00:00
* See https : //facebook.github.io/react-native/docs/image.html#resizemethod
2016-09-09 12:00:52 +00:00
* /
resizeMethod : PropTypes . oneOf ( [ 'auto' , 'resize' , 'scale' ] ) ,
2016-08-01 07:49:14 +00:00
/ * *
* Determines how to resize the image when the frame doesn ' t match the raw
* image dimensions .
*
2018-01-30 00:10:49 +00:00
* See https : //facebook.github.io/react-native/docs/image.html#resizemode
2016-08-01 07:49:14 +00:00
* /
Support Image resizeMode=repeat on Android
Summary:
`<Image resizeMode="repeat" />` for Android, matching the iOS implementation (#7968). (Non-goal: changing the component's API for finer-grained control / feature parity with CSS - this would be nice in the future)
As requested in e.g. #14158.
Given https://github.com/facebook/fresco/issues/1575, and lacking the context to follow the specific recommendations in https://github.com/facebook/fresco/issues/1575#issuecomment-267004303, I've opted for a minimal change within RN itself.
It's likely that performance can be improved by offloading this work to Fresco in some clever way; but I'm assuming that the present naive approach is still an improvement over a userland implementation with `onLayout` and multiple `<Image>` instances.
- Picking up on a TODO note in the existing code, I implemented `MultiPostprocessor` to allow arbitrary chaining of Fresco-compatible postprocessors inside `ReactImageView`.
- Rather than extensively refactor `ImageResizeMode`, `ReactImageManager` and `ReactImageView`, I mostly preserved the existing API that maps `resizeMode` values to [`ScaleType`](http://frescolib.org/javadoc/reference/com/facebook/drawee/drawable/ScalingUtils.ScaleType.html) instances, and simply added a second mapping, to [`TileMode`](https://developer.android.com/reference/android/graphics/Shader.TileMode.html).
- To match the iOS rendering exactly for oversized images, I found that scaling with a custom `ScaleType` was required - a kind of combination of `CENTER_INSIDE` and `FIT_START` which Fresco doesn't provide - so I implemented that as `ScaleTypeStartInside`. (This is, frankly, questionable as the default behaviour on iOS to begin with - but I am aiming for parity here)
- `resizeMode="repeat"` is therefore unpacked by the view manager to the effect of:
```js
view.setScaleType(ScaleTypeStartInside.INSTANCE);
view.setTileMode(Shader.TileMode.REPEAT);
```
And the added postprocessing in the view (in case of a non-`CLAMP` tile mode) consists of waiting for layout, allocating a destination bitmap and painting the source bitmap with the requested tile mode and scale type.
Note that as in https://github.com/facebook/react-native/pull/17398#issue-285235247, I have neither updated nor tested the "Flat" UI implementation - everything compiles but I've taken [this comment](https://github.com/facebook/react-native/issues/12770#issuecomment-294052694) to mean there's no point in trying to wade through it on my own right now; I'm happy to tackle it if given some pointers.
Also, I'm happy to address any code style issues or other feedback; I'm new to this codebase and a very infrequent Android/Java coder.
Tested by enabling the relevant case in RNTester on Android.
| iOS | Android |
|-|-|
| <img src=https://user-images.githubusercontent.com/2246565/34461897-4e12008e-ee2f-11e7-8581-1dc0cc8f2779.png width=300>| <img src=https://user-images.githubusercontent.com/2246565/34461894-40b2c8ec-ee2f-11e7-8a8f-96704f3c8caa.png width=300> |
Docs update: https://github.com/facebook/react-native-website/pull/106
[ANDROID] [FEATURE] [Image] - Implement resizeMode=repeat
Closes https://github.com/facebook/react-native/pull/17404
Reviewed By: achen1
Differential Revision: D7070329
Pulled By: mdvacca
fbshipit-source-id: 6a72fcbdcc7c7c2daf293dc1d8b6728f54ad0249
2018-03-12 23:05:40 +00:00
resizeMode : PropTypes . oneOf ( [ 'cover' , 'contain' , 'stretch' , 'repeat' , 'center' ] ) ,
2015-09-14 14:35:58 +00:00
} ,
statics : {
resizeMode : ImageResizeMode ,
2016-05-21 01:40:59 +00:00
getSize (
url : string ,
success : ( width : number , height : number ) => void ,
2017-04-05 17:16:30 +00:00
failure ? : ( error : any ) => void ,
2016-05-21 01:40:59 +00:00
) {
return ImageLoader . getSize ( url )
. then ( function ( sizes ) {
success ( sizes . width , sizes . height ) ;
} )
2018-01-15 03:32:26 +00:00
. catch (
failure ||
function ( ) {
console . warn ( 'Failed to get size for image: ' + url ) ;
} ,
) ;
2016-05-21 01:40:59 +00:00
} ,
2016-04-13 14:29:10 +00:00
/ * *
* Prefetches a remote image for later use by downloading it to the disk
* cache
2018-01-30 00:10:49 +00:00
*
* See https : //facebook.github.io/react-native/docs/image.html#prefetch
2016-04-13 14:29:10 +00:00
* /
2016-07-18 19:07:29 +00:00
prefetch ( url : string , callback : ? Function ) {
const requestId = generateRequestId ( ) ;
callback && callback ( requestId ) ;
return ImageLoader . prefetchImage ( url , requestId ) ;
} ,
/ * *
2018-01-30 00:10:49 +00:00
* Abort prefetch request .
*
* See https : //facebook.github.io/react-native/docs/image.html#abortprefetch
2016-07-18 19:07:29 +00:00
* /
abortPrefetch ( requestId : number ) {
2016-07-23 07:16:32 +00:00
ImageLoader . abortRequest ( requestId ) ;
2016-04-13 14:29:10 +00:00
} ,
2016-08-31 12:06:10 +00:00
/ * *
* Perform cache interrogation .
*
2018-01-30 00:10:49 +00:00
* See https : //facebook.github.io/react-native/docs/image.html#querycache
2016-08-31 12:06:10 +00:00
* /
2018-01-15 03:32:26 +00:00
async queryCache (
urls : Array < string > ,
) : Promise < Map < string , 'memory' | 'disk' >> {
2016-08-31 12:06:10 +00:00
return await ImageLoader . queryCache ( urls ) ;
2016-11-15 05:01:02 +00:00
} ,
/ * *
2018-01-30 00:10:49 +00:00
* Resolves an asset reference into an object .
*
* See https : //facebook.github.io/react-native/docs/image.html#resolveassetsource
2016-11-15 05:01:02 +00:00
* /
resolveAssetSource : resolveAssetSource ,
2015-09-14 14:35:58 +00:00
} ,
mixins : [ NativeMethodsMixin ] ,
/ * *
* ` NativeMethodsMixin ` will look for this when invoking ` setNativeProps ` . We
2017-09-18 04:26:17 +00:00
* make ` this ` look like an actual native component class .
2015-09-14 14:35:58 +00:00
* /
viewConfig : {
uiViewClassName : 'RCTView' ,
2016-03-01 02:02:11 +00:00
validAttributes : ReactNativeViewAttributes . RCTView ,
2015-09-14 14:35:58 +00:00
} ,
2018-01-15 03:32:33 +00:00
contextTypes : ViewContextTypes ,
2015-11-13 18:51:09 +00:00
2015-09-14 14:35:58 +00:00
render : function ( ) {
2016-06-13 21:04:19 +00:00
const source = resolveAssetSource ( this . props . source ) ;
2018-01-15 03:32:26 +00:00
const loadingIndicatorSource = resolveAssetSource (
this . props . loadingIndicatorSource ,
) ;
2015-10-05 10:59:02 +00:00
2016-06-13 21:04:19 +00:00
// As opposed to the ios version, here we render `null` when there is no source, source.uri
// or source array.
2015-10-05 10:59:02 +00:00
if ( source && source . uri === '' ) {
console . warn ( 'source.uri should not be an empty string' ) ;
}
2016-03-04 15:31:38 +00:00
if ( this . props . src ) {
2018-01-15 03:32:26 +00:00
console . warn (
'The <Image> component requires a `source` property rather than `src`.' ,
) ;
2016-03-04 15:31:38 +00:00
}
2017-09-26 04:55:56 +00:00
if ( this . props . children ) {
2018-01-15 03:32:26 +00:00
throw new Error (
'The <Image> component cannot contain children. If you want to render content on top of the image, consider using the <ImageBackground> component or absolute positioning.' ,
) ;
2017-09-26 04:55:56 +00:00
}
2016-06-13 21:04:19 +00:00
if ( source && ( source . uri || Array . isArray ( source ) ) ) {
let style ;
let sources ;
if ( source . uri ) {
const { width , height } = source ;
style = flattenStyle ( [ { width , height } , styles . base , this . props . style ] ) ;
sources = [ { uri : source . uri } ] ;
} else {
style = flattenStyle ( [ styles . base , this . props . style ] ) ;
sources = source ;
}
2016-11-15 03:42:34 +00:00
const { onLoadStart , onLoad , onLoadEnd , onError } = this . props ;
2016-06-13 21:04:19 +00:00
const nativeProps = merge ( this . props , {
2015-09-14 14:35:58 +00:00
style ,
2018-01-15 03:32:26 +00:00
shouldNotifyLoadEvents : ! ! (
onLoadStart ||
onLoad ||
onLoadEnd ||
onError
) ,
2016-06-13 21:04:19 +00:00
src : sources ,
2017-02-18 12:33:59 +00:00
headers : source . headers ,
2018-01-15 03:32:26 +00:00
loadingIndicatorSrc : loadingIndicatorSource
? loadingIndicatorSource . uri
: null ,
2015-09-14 14:35:58 +00:00
} ) ;
2017-09-18 04:26:17 +00:00
if ( this . context . isInAParentText ) {
2018-01-15 03:32:26 +00:00
return < RCTTextInlineImage { ... nativeProps } / > ;
2015-09-14 14:35:58 +00:00
} else {
2018-01-15 03:32:26 +00:00
return < RKImage { ... nativeProps } / > ;
2015-09-14 14:35:58 +00:00
}
}
return null ;
2018-01-15 03:32:26 +00:00
} ,
2015-09-14 14:35:58 +00:00
} ) ;
var styles = StyleSheet . create ( {
base : {
overflow : 'hidden' ,
} ,
} ) ;
2015-11-18 16:24:26 +00:00
var cfg = {
nativeOnly : {
src : true ,
2017-02-18 12:33:59 +00:00
headers : true ,
2015-12-06 23:44:47 +00:00
loadingIndicatorSrc : true ,
2015-11-26 01:06:59 +00:00
shouldNotifyLoadEvents : true ,
2015-11-18 16:24:26 +00:00
} ,
} ;
var RKImage = requireNativeComponent ( 'RCTImageView' , Image , cfg ) ;
2018-01-15 03:32:26 +00:00
var RCTTextInlineImage = requireNativeComponent (
'RCTTextInlineImage' ,
Image ,
cfg ,
) ;
2015-09-14 14:35:58 +00:00
module . exports = Image ;