Files
react-native/packages/rn-tester/js/examples/Image/ImageExample.js
T
Tomek Zawadzki f00e348ca7 Fix support for blobs larger than 64 KB on Android (#31789)
Summary:
Fixes https://github.com/facebook/react-native/issues/31774.

This pull request resolves a problem related to accessing blobs greater than 64 KB on Android. When an object URL for such blob is passed as source of `<Image />` component, the image does not load.

This issue was related to the fact that pipe buffer has a limited capacity of 65536 bytes (https://man7.org/linux/man-pages/man7/pipe.7.html, section "Pipe capacity"). If there is more bytes to be written than free space in the buffer left, the write operation blocks and waits until the content is read from the pipe.

The current implementation of `BlobProvider.openFile` first creates a pipe, then writes the blob data to the pipe and finally returns the read side descriptor of the pipe. For blobs larger than 64 KB, the write operation will block forever, because there are no readers to empty the buffer.

https://github.com/facebook/react-native/blob/41ecccefcf16ac8bcf858dd955af709eb20f7e4a/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.java#L86-L95

This pull request moves the write operation to a separate thread. The read side descriptor is returned immediately so that both writer and reader can work simultaneously. Reading from the pipe empties the buffer and allows the next chunks to be written.

## Changelog

<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->

[Android] [Fixed] - Fix support for blobs larger than 64 KB

Pull Request resolved: https://github.com/facebook/react-native/pull/31789

Test Plan:
A new example has been added to RN Tester app to verify if the new implementation properly loads the image of size 455 KB from a blob via object URL passed as image source.

<img src="https://user-images.githubusercontent.com/20516055/123859163-9eba6d80-d924-11eb-8a09-2b1f353bb968.png" alt="Screenshot_1624996413" width="300" />

Reviewed By: ShikaSD

Differential Revision: D29674273

Pulled By: yungsters

fbshipit-source-id: e0ac3ec0a23690b05ab843061803f95f7666c0db
2021-07-14 11:11:31 -07:00

1277 lines
36 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
const React = require('react');
const {
Image,
StyleSheet,
Text,
View,
ImageBackground,
} = require('react-native');
const base64Icon =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
const ImageCapInsetsExample = require('./ImageCapInsetsExample');
const IMAGE_PREFETCH_URL =
'https://www.facebook.com/favicon.ico?r=1&t=' + Date.now();
const prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
type ImageSource = $ReadOnly<{|
uri: string,
|}>;
type BlobImageState = {|
objectURL: ?string,
|};
type BlobImageProps = $ReadOnly<{|
url: string,
|}>;
class BlobImage extends React.Component<BlobImageProps, BlobImageState> {
state = {
objectURL: null,
};
UNSAFE_componentWillMount() {
(async () => {
const result = await fetch(this.props.url);
const blob = await result.blob();
const objectURL = URL.createObjectURL(blob);
this.setState({objectURL});
})();
}
render() {
return this.state.objectURL !== null ? (
<Image source={{uri: this.state.objectURL}} style={styles.base} />
) : (
<Text>Object URL not created yet</Text>
);
}
}
type BlobImageExampleState = {||};
type BlobImageExampleProps = $ReadOnly<{|
urls: string[],
|}>;
class BlobImageExample extends React.Component<
BlobImageExampleProps,
BlobImageExampleState,
> {
render() {
return (
<View style={styles.horizontal}>
{this.props.urls.map(url => (
<BlobImage key={url} url={url} />
))}
</View>
);
}
}
type NetworkImageCallbackExampleState = {|
events: Array<string>,
startLoadPrefetched: boolean,
mountTime: number,
imageHash: number,
|};
type NetworkImageCallbackExampleProps = $ReadOnly<{|
source: ImageSource,
prefetchedSource: ImageSource,
|}>;
class NetworkImageCallbackExample extends React.Component<
NetworkImageCallbackExampleProps,
NetworkImageCallbackExampleState,
> {
state = {
events: [],
startLoadPrefetched: false,
mountTime: Date.now(),
imageHash: Date.now(),
};
UNSAFE_componentWillMount() {
this.setState({mountTime: Date.now()});
}
_loadEventFired = (event: string) => {
this.setState(state => ({
events: [...state.events, event],
}));
};
updateLoadingImageHash = () => {
this.setState({imageHash: Date.now()});
};
render() {
const {mountTime} = this.state;
return (
<View>
<Image
source={this.props.source}
style={[styles.base, {overflow: 'visible'}]}
onLoadStart={() =>
this._loadEventFired(`✔ onLoadStart (+${Date.now() - mountTime}ms)`)
}
onProgress={event => {
const {loaded, total} = event.nativeEvent;
const percent = Math.round((loaded / total) * 100);
this._loadEventFired(
`✔ onProgress ${percent}% (+${Date.now() - mountTime}ms)`,
);
}}
onLoad={event => {
if (event.nativeEvent.source) {
const url = event.nativeEvent.source.uri;
this._loadEventFired(
`✔ onLoad (+${Date.now() - mountTime}ms) for URL ${url}`,
);
} else {
this._loadEventFired(`✔ onLoad (+${Date.now() - mountTime}ms)`);
}
}}
onLoadEnd={() => {
this._loadEventFired(`✔ onLoadEnd (+${Date.now() - mountTime}ms)`);
this.setState({startLoadPrefetched: true}, () => {
prefetchTask.then(
() => {
this._loadEventFired(
`✔ Prefetch OK (+${Date.now() - mountTime}ms)`,
);
Image.queryCache([IMAGE_PREFETCH_URL]).then(map => {
const result = map[IMAGE_PREFETCH_URL];
if (result) {
this._loadEventFired(
`✔ queryCache "${result}" (+${Date.now() -
mountTime}ms)`,
);
} else {
this._loadEventFired(
`✘ queryCache (+${Date.now() - mountTime}ms)`,
);
}
});
},
error => {
this._loadEventFired(
`✘ Prefetch failed (+${Date.now() - mountTime}ms)`,
);
},
);
});
}}
/>
{this.state.startLoadPrefetched ? (
<Image
source={this.props.prefetchedSource}
style={[styles.base, {overflow: 'visible'}]}
onLoadStart={() =>
this._loadEventFired(
`✔ (prefetched) onLoadStart (+${Date.now() - mountTime}ms)`,
)
}
onLoad={event => {
// Currently this image source feature is only available on iOS.
if (event.nativeEvent.source) {
const url = event.nativeEvent.source.uri;
this._loadEventFired(
`✔ (prefetched) onLoad (+${Date.now() -
mountTime}ms) for URL ${url}`,
);
} else {
this._loadEventFired(
`✔ (prefetched) onLoad (+${Date.now() - mountTime}ms)`,
);
}
}}
onLoadEnd={() =>
this._loadEventFired(
`✔ (prefetched) onLoadEnd (+${Date.now() - mountTime}ms)`,
)
}
/>
) : null}
<Text style={{marginTop: 20}}>{this.state.events.join('\n')}</Text>
</View>
);
}
}
type NetworkImageExampleState = {|
error: ?string,
loading: boolean,
progress: $ReadOnlyArray<number>,
|};
type NetworkImageExampleProps = $ReadOnly<{|
source: ImageSource,
|}>;
class NetworkImageExample extends React.Component<
NetworkImageExampleProps,
NetworkImageExampleState,
> {
state = {
error: null,
loading: false,
progress: [],
};
render() {
return this.state.error != null ? (
<Text>{this.state.error}</Text>
) : (
<>
<Image
source={this.props.source}
style={[styles.base, {overflow: 'visible'}]}
onLoadStart={e => this.setState({loading: true})}
onError={e =>
this.setState({error: e.nativeEvent.error, loading: false})
}
onProgress={e => {
const {loaded, total} = e.nativeEvent;
this.setState(prevState => ({
progress: [
...prevState.progress,
Math.round((100 * loaded) / total),
],
}));
}}
onLoad={() => this.setState({loading: false, error: null})}
/>
<Text>
{this.state.progress.map(progress => `${progress}%`).join('\n')}
</Text>
</>
);
}
}
type ImageSizeExampleState = {|
width: number,
height: number,
|};
type ImageSizeExampleProps = $ReadOnly<{|
source: ImageSource,
|}>;
class ImageSizeExample extends React.Component<
ImageSizeExampleProps,
ImageSizeExampleState,
> {
state = {
width: 0,
height: 0,
};
componentDidMount() {
Image.getSize(this.props.source.uri, (width, height) => {
this.setState({width, height});
});
}
render() {
return (
<View style={{flexDirection: 'row'}}>
<Image
style={{
width: 60,
height: 60,
backgroundColor: 'transparent',
marginRight: 10,
}}
source={this.props.source}
/>
<Text>
Actual dimensions:{'\n'}
Width: {this.state.width}, Height: {this.state.height}
</Text>
</View>
);
}
}
type MultipleSourcesExampleState = {|
width: number,
height: number,
|};
type MultipleSourcesExampleProps = $ReadOnly<{||}>;
class MultipleSourcesExample extends React.Component<
MultipleSourcesExampleProps,
MultipleSourcesExampleState,
> {
state = {
width: 30,
height: 30,
};
increaseImageSize = () => {
if (this.state.width >= 100) {
return;
}
this.setState({
width: this.state.width + 10,
height: this.state.height + 10,
});
};
increaseImageSize = () => {
if (this.state.width >= 100) {
return;
}
this.setState({
width: this.state.width + 10,
height: this.state.height + 10,
});
};
decreaseImageSize = () => {
if (this.state.width <= 10) {
return;
}
this.setState({
width: this.state.width - 10,
height: this.state.height - 10,
});
};
render() {
return (
<View>
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<Text style={styles.touchableText} onPress={this.decreaseImageSize}>
Decrease image size
</Text>
<Text style={styles.touchableText} onPress={this.increaseImageSize}>
Increase image size
</Text>
</View>
<Text>
Container image size: {this.state.width}x{this.state.height}{' '}
</Text>
<View style={{height: this.state.height, width: this.state.width}}>
<Image
style={{flex: 1}}
source={[
{
uri: 'https://www.facebook.com/favicon.ico',
width: 38,
height: 38,
},
{
uri: 'https://www.facebook.com/favicon.ico',
width: 76,
height: 76,
},
{
uri: 'https://www.facebook.com/ads/pics/successstories.png',
width: 400,
height: 400,
},
]}
/>
</View>
</View>
);
}
}
type LoadingIndicatorSourceExampleState = {|
imageHash: number,
|};
type LoadingIndicatorSourceExampleProps = $ReadOnly<{||}>;
class LoadingIndicatorSourceExample extends React.Component<
LoadingIndicatorSourceExampleProps,
LoadingIndicatorSourceExampleState,
> {
state = {
imageHash: Date.now(),
};
reloadImage = () => {
this.setState({
imageHash: Date.now(),
});
};
loaderGif = {
uri: 'https://media1.giphy.com/media/3oEjI6SIIHBdRxXI40/200.gif',
};
render() {
const loadingImage = {
uri: `https://www.facebook.com/ads/pics/successstories.png?hash=${this.state.imageHash}`,
};
return (
<View>
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<Text style={styles.touchableText} onPress={this.reloadImage}>
Refresh Image
</Text>
</View>
<Image
loadingIndicatorSource={this.loaderGif}
source={loadingImage}
style={styles.base}
/>
<Text>Image Hash: {this.state.imageHash}</Text>
<Text>Image URI: {loadingImage.uri}</Text>
</View>
);
}
}
type OnLayoutExampleState = {|
width: number,
height: number,
layoutHandlerMessage: string,
|};
type OnLayoutExampleProps = $ReadOnly<{||}>;
class OnLayoutExample extends React.Component<
OnLayoutExampleProps,
OnLayoutExampleState,
> {
state = {
width: 30,
height: 30,
layoutHandlerMessage: 'No Message',
};
onLayoutHandler = event => {
this.setState({
width: this.state.width,
height: this.state.height,
layoutHandlerMessage: JSON.stringify(event.nativeEvent),
});
console.log(event.nativeEvent);
};
increaseImageSize = () => {
if (this.state.width >= 100) {
return;
}
this.setState({
width: this.state.width + 10,
height: this.state.height + 10,
});
};
increaseImageSize = () => {
if (this.state.width >= 100) {
return;
}
this.setState({
width: this.state.width + 10,
height: this.state.height + 10,
});
};
decreaseImageSize = () => {
if (this.state.width <= 10) {
return;
}
this.setState({
width: this.state.width - 10,
height: this.state.height - 10,
});
};
render() {
return (
<View>
<Text>Adjust the image size to trigger the OnLayout handler.</Text>
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<Text style={styles.touchableText} onPress={this.decreaseImageSize}>
Decrease image size
</Text>
<Text style={styles.touchableText} onPress={this.increaseImageSize}>
Increase image size
</Text>
</View>
<Text>
Container image size: {this.state.width}x{this.state.height}{' '}
</Text>
<View style={{height: this.state.height, width: this.state.width}}>
<Image
onLayout={this.onLayoutHandler}
style={{flex: 1}}
source={[
{
uri: 'https://www.facebook.com/favicon.ico',
width: 38,
height: 38,
},
{
uri: 'https://www.facebook.com/favicon.ico',
width: 76,
height: 76,
},
{
uri: 'https://www.facebook.com/ads/pics/successstories.png',
width: 400,
height: 400,
},
]}
/>
</View>
<Text>Layout Handler Message: {this.state.layoutHandlerMessage}</Text>
</View>
);
}
}
type OnPartialLoadExampleState = {|
hasLoaded: boolean,
|};
type OnPartialLoadExampleProps = $ReadOnly<{||}>;
class OnPartialLoadExample extends React.Component<
OnPartialLoadExampleProps,
OnPartialLoadExampleState,
> {
state = {
hasLoaded: false,
};
partialLoadHandler = () => {
this.setState({
hasLoaded: true,
});
};
render() {
return (
<View>
<Text>
Partial Load Function Executed: {JSON.stringify(this.state.hasLoaded)}
</Text>
<Image
source={{
uri: `https://images.pexels.com/photos/671557/pexels-photo-671557.jpeg?&buster=${Math.random()}`,
}}
onPartialLoad={this.partialLoadHandler}
style={styles.base}
/>
</View>
);
}
}
const fullImage = {
uri: 'https://www.facebook.com/ads/pics/successstories.png',
};
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
const styles = StyleSheet.create({
base: {
width: 38,
height: 38,
},
leftMargin: {
marginLeft: 10,
},
background: {
backgroundColor: '#222222',
},
sectionText: {
marginVertical: 6,
},
nestedText: {
marginLeft: 12,
marginTop: 20,
backgroundColor: 'transparent',
color: 'white',
},
resizeMode: {
width: 90,
height: 60,
borderWidth: 0.5,
borderColor: 'black',
},
resizeModeText: {
fontSize: 11,
marginBottom: 3,
},
icon: {
width: 15,
height: 15,
},
horizontal: {
flexDirection: 'row',
},
gif: {
flex: 1,
height: 200,
},
base64: {
flex: 1,
height: 50,
resizeMode: 'contain',
},
touchableText: {
fontWeight: '500',
color: 'blue',
},
});
exports.displayName = (undefined: ?string);
exports.framework = 'React';
exports.title = 'Image';
exports.category = 'Basic';
exports.description =
'Base component for displaying different types of images.';
exports.examples = [
{
title: 'Plain Network Image',
description: ('If the `source` prop `uri` property is prefixed with ' +
'"http", then it will be downloaded from the network.': string),
render: function(): React.Node {
return <Image source={fullImage} style={styles.base} />;
},
},
{
title: 'Plain Blob Image',
description: ('If the `source` prop `uri` property is an object URL, ' +
'then it will be resolved using `BlobProvider` (Android) or `RCTBlobManager` (iOS).': string),
render: function(): React.Node {
return (
<BlobImageExample
urls={[
'https://www.facebook.com/favicon.ico',
'https://www.facebook.com/ads/pics/successstories.png',
]}
/>
);
},
},
{
title: 'Plain Static Image',
description: ('Static assets should be placed in the source code tree, and ' +
'required in the same way as JavaScript modules.': string),
render: function(): React.Node {
return (
<View style={styles.horizontal}>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={styles.icon}
/>
<Image
source={require('../../assets/uie_thumb_selected.png')}
style={styles.icon}
/>
<Image
source={require('../../assets/uie_comment_normal.png')}
style={styles.icon}
/>
<Image
source={require('../../assets/uie_comment_highlighted.png')}
style={styles.icon}
/>
</View>
);
},
},
{
title: 'Image Loading Events',
render: function(): React.Node {
return (
<NetworkImageCallbackExample
source={{
uri: 'https://www.facebook.com/favicon.ico?r=1&t=' + Date.now(),
}}
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}
/>
);
},
},
{
title: 'Error Handler',
render: function(): React.Node {
return (
<NetworkImageExample
source={{
uri: 'https://www.facebook.com/favicon_TYPO.ico',
}}
/>
);
},
},
{
title: 'Image Download Progress',
render: function(): React.Node {
return (
<NetworkImageExample
source={{
uri: 'https://www.facebook.com/favicon.ico?r=1',
}}
/>
);
},
},
{
title: 'defaultSource',
description: 'Show a placeholder image when a network image is loading',
render: function(): React.Node {
return (
<Image
defaultSource={require('../../assets/bunny.png')}
source={{
uri: 'https://origami.design/public/images/bird-logo.png',
}}
style={styles.base}
/>
);
},
platform: 'ios',
},
{
title: 'Cache Policy',
description: ('First image has never been loaded before and is instructed not to load unless in cache.' +
'Placeholder image from above will stay. Second image is the same but forced to load regardless of' +
' local cache state.': string),
render: function(): React.Node {
return (
<View style={styles.horizontal}>
<Image
defaultSource={require('../../assets/bunny.png')}
source={{
uri: smallImage.uri + '?cacheBust=notinCache' + Date.now(),
cache: 'only-if-cached',
}}
style={styles.base}
/>
<Image
defaultSource={require('../../assets/bunny.png')}
source={{
uri: smallImage.uri + '?cacheBust=notinCache' + Date.now(),
cache: 'reload',
}}
style={styles.base}
/>
</View>
);
},
platform: 'ios',
},
{
title: 'Border Color',
render: function(): React.Node {
return (
<View style={styles.horizontal}>
<Image
source={smallImage}
style={[
styles.base,
styles.background,
{borderWidth: 3, borderColor: '#f099f0'},
]}
/>
</View>
);
},
},
{
title: 'Border Width',
render: function(): React.Node {
return (
<View style={styles.horizontal}>
<Image
source={smallImage}
style={[
styles.base,
styles.background,
{borderWidth: 5, borderColor: '#f099f0'},
]}
/>
</View>
);
},
},
{
title: 'Border Radius',
render: function(): React.Node {
return (
<View style={styles.horizontal}>
<Image style={[styles.base, {borderRadius: 5}]} source={fullImage} />
<Image
style={[styles.base, styles.leftMargin, {borderRadius: 19}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {borderTopLeftRadius: 20}]}
source={fullImage}
/>
<Image
style={[
styles.base,
styles.leftMargin,
{
borderWidth: 10,
borderTopLeftRadius: 10,
borderBottomRightRadius: 20,
borderColor: 'green',
},
]}
source={fullImage}
/>
<Image
style={[
styles.base,
styles.leftMargin,
{
borderWidth: 5,
borderTopLeftRadius: 10,
borderTopRightRadius: 20,
borderBottomRightRadius: 30,
borderBottomLeftRadius: 40,
borderColor: 'red',
},
]}
source={fullImage}
/>
</View>
);
},
},
{
title: 'Background Color',
render: function(): React.Node {
return (
<View style={styles.horizontal}>
<Image source={smallImage} style={styles.base} />
<Image
style={[
styles.base,
styles.leftMargin,
{backgroundColor: 'rgba(0, 0, 100, 0.25)'},
]}
source={smallImage}
/>
<Image
style={[styles.base, styles.leftMargin, {backgroundColor: 'red'}]}
source={smallImage}
/>
<Image
style={[styles.base, styles.leftMargin, {backgroundColor: 'black'}]}
source={smallImage}
/>
</View>
);
},
},
{
title: 'Opacity',
render: function(): React.Node {
return (
<View style={styles.horizontal}>
<Image style={[styles.base, {opacity: 1}]} source={fullImage} />
<Image
style={[styles.base, styles.leftMargin, {opacity: 0.8}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {opacity: 0.6}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {opacity: 0.4}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {opacity: 0.2}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {opacity: 0}]}
source={fullImage}
/>
</View>
);
},
},
{
title: 'Nesting content inside <Image> component',
render: function(): React.Node {
return (
<View style={{width: 60, height: 60}}>
<Image
style={{...StyleSheet.absoluteFillObject}}
source={fullImage}
/>
<Text style={styles.nestedText}>React</Text>
</View>
);
},
},
{
title: 'Nesting content inside <ImageBackground> component',
render: function(): React.Node {
return (
<ImageBackground
style={{width: 60, height: 60, backgroundColor: 'transparent'}}
source={fullImage}>
<Text style={styles.nestedText}>React</Text>
</ImageBackground>
);
},
},
{
title: 'Tint Color',
description: ('The `tintColor` style prop changes all the non-alpha ' +
'pixels to the tint color.': string),
render: function(): React.Node {
return (
<View>
<View style={styles.horizontal}>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[styles.icon, {borderRadius: 5, tintColor: '#5ac8fa'}]}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.leftMargin,
{borderRadius: 5, tintColor: '#4cd964'},
]}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.leftMargin,
{borderRadius: 5, tintColor: '#ff2d55'},
]}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.leftMargin,
{borderRadius: 5, tintColor: '#8e8e93'},
]}
/>
</View>
<Text style={styles.sectionText}>
It also works with downloaded images:
</Text>
<View style={styles.horizontal}>
<Image
source={smallImage}
style={[styles.base, {borderRadius: 5, tintColor: '#5ac8fa'}]}
/>
<Image
source={smallImage}
style={[
styles.base,
styles.leftMargin,
{borderRadius: 5, tintColor: '#4cd964'},
]}
/>
<Image
source={smallImage}
style={[
styles.base,
styles.leftMargin,
{borderRadius: 5, tintColor: '#ff2d55'},
]}
/>
<Image
source={smallImage}
style={[
styles.base,
styles.leftMargin,
{borderRadius: 5, tintColor: '#8e8e93'},
]}
/>
</View>
</View>
);
},
},
{
title: 'Resize Mode',
description: ('The `resizeMode` style prop controls how the image is ' +
'rendered within the frame.': string),
render: function(): React.Node {
return (
<View>
{[smallImage, fullImage].map((image, index) => {
return (
<View key={index}>
<View style={styles.horizontal}>
<View>
<Text style={[styles.resizeModeText]}>Contain</Text>
<Image
style={styles.resizeMode}
resizeMode="contain"
source={image}
/>
</View>
{/* $FlowFixMe[incompatible-type] (>=0.115.0 site=react_
* native_fb) This comment suppresses an error found when
* Flow v0.115 was deployed. To see the error, delete this
* comment and run Flow. */}
<View style={styles.leftMargin}>
<Text style={[styles.resizeModeText]}>Cover</Text>
<Image
style={styles.resizeMode}
resizeMode="cover"
source={image}
/>
</View>
</View>
<View style={styles.horizontal}>
<View>
<Text style={[styles.resizeModeText]}>Stretch</Text>
<Image
style={styles.resizeMode}
resizeMode="stretch"
source={image}
/>
</View>
{/* $FlowFixMe[incompatible-type] (>=0.115.0 site=react_
* native_fb) This comment suppresses an error found when
* Flow v0.115 was deployed. To see the error, delete this
* comment and run Flow. */}
<View style={styles.leftMargin}>
<Text style={[styles.resizeModeText]}>Repeat</Text>
<Image
style={styles.resizeMode}
resizeMode="repeat"
source={image}
/>
</View>
{/* $FlowFixMe[incompatible-type] (>=0.115.0 site=react_
* native_fb) This comment suppresses an error found when
* Flow v0.115 was deployed. To see the error, delete this
* comment and run Flow. */}
<View style={styles.leftMargin}>
<Text style={[styles.resizeModeText]}>Center</Text>
<Image
style={styles.resizeMode}
resizeMode="center"
source={image}
/>
</View>
</View>
</View>
);
})}
</View>
);
},
},
{
title: 'Animated GIF',
render: function(): React.Node {
return (
<Image
style={styles.gif}
source={require('../../assets/tumblr_mfqekpMktw1rn90umo1_500.gif')}
/>
);
},
platform: 'ios',
},
{
title: 'Base64 image',
render: function(): React.Node {
return (
<Image style={styles.base64} source={{uri: base64Icon, scale: 3}} />
);
},
platform: 'ios',
},
{
title: 'Cap Insets',
description: ('When the image is resized, the corners of the size specified ' +
'by capInsets will stay a fixed size, but the center content and ' +
'borders of the image will be stretched. This is useful for creating ' +
'resizable rounded buttons, shadows, and other resizable assets.': string),
render: function(): React.Node {
return <ImageCapInsetsExample />;
},
platform: 'ios',
},
{
title: 'Image Size',
render: function(): React.Node {
/* $FlowFixMe[prop-missing] (>=0.115.0 site=react_native_fb) This comment
* suppresses an error found when Flow v0.115 was deployed. To see the
* error, delete this comment and run Flow. */
/* $FlowFixMe[incompatible-type] (>=0.115.0 site=react_native_fb) This
* comment suppresses an error found when Flow v0.115 was deployed. To
* see the error, delete this comment and run Flow. */
return <ImageSizeExample source={fullImage} />;
},
},
{
title: 'MultipleSourcesExample',
description: ('The `source` prop allows passing in an array of uris, so that native to choose which image ' +
'to diplay based on the size of the of the target image': string),
render: function(): React.Node {
return <MultipleSourcesExample />;
},
},
{
title: 'Legacy local image',
description: ('Images shipped with the native bundle, but not managed ' +
'by the JS packager': string),
render: function(): React.Node {
return <Image source={{uri: 'legacy_image', width: 120, height: 120}} />;
},
},
{
title: 'Bundled images',
description: 'Images shipped in a separate native bundle',
render: function(): React.Node {
return (
<View style={{flexDirection: 'row'}}>
<Image
source={{
uri: 'ImageInBundle',
bundle: 'RNTesterBundle',
width: 100,
height: 100,
}}
style={{borderColor: 'yellow', borderWidth: 4}}
/>
<Image
source={{
uri: 'ImageInAssetCatalog',
bundle: 'RNTesterBundle',
width: 100,
height: 100,
}}
style={{marginLeft: 10, borderColor: 'blue', borderWidth: 4}}
/>
</View>
);
},
platform: 'ios',
},
{
title: 'Blur Radius',
render: function(): React.Node {
return (
<View style={styles.horizontal}>
<Image style={[styles.base]} source={fullImage} blurRadius={0} />
<Image
style={[styles.base, styles.leftMargin]}
source={fullImage}
blurRadius={5}
/>
<Image
style={[styles.base, styles.leftMargin]}
source={fullImage}
blurRadius={10}
/>
<Image
style={[styles.base, styles.leftMargin]}
source={fullImage}
blurRadius={15}
/>
<Image
style={[styles.base, styles.leftMargin]}
source={fullImage}
blurRadius={20}
/>
<Image
style={[styles.base, styles.leftMargin]}
source={fullImage}
blurRadius={25}
/>
</View>
);
},
},
{
title: 'Accessibility',
description: ('If the `accessible` (boolean) prop is set to True, the image will be indicated as an accessbility element.': string),
render: function(): React.Node {
return <Image accessible source={fullImage} style={styles.base} />;
},
},
{
title: 'Accessibility Label',
description: ('When an element is marked as accessibile (using the accessibility prop), it is good practice to set an accessibilityLabel on the image to provide a description of the element to people who use VoiceOver. VoiceOver will read this string when people select this element.': string),
render: function(): React.Node {
return (
<Image
accessible
accessibilityLabel="Picture of people standing around a table"
source={fullImage}
style={styles.base}
/>
);
},
},
{
title: 'Fade Duration',
description: ('The time (in miliseconds) that an image will fade in for when it appears (default = 300).': string),
render: function(): React.Node {
return (
<>
<Image fadeDuration={1500} source={fullImage} style={styles.base} />
<Text>This image will fade in over the time of 1.5s.</Text>
</>
);
},
platform: 'android',
},
{
title: 'Loading Indicator Source',
description: ('This prop is used to set the resource that will be used as the loading indicator for the image (displayed until the image is ready to be displayed).': string),
render: function(): React.Node {
return <LoadingIndicatorSourceExample />;
},
},
{
title: 'On Layout',
description: ('This prop is used to set the handler function to be called when the image is mounted or its layout changes. The function receives an event with `{nativeEvent: {layout: {x, y, width, height}}}`': string),
render: function(): React.Node {
return <OnLayoutExample />;
},
},
{
title: 'On Partial Load',
description: ('This prop is used to set the handler function to be called when the partial load of the image is complete. This is meant for progressive JPEG loads.': string),
render: function(): React.Node {
return <OnPartialLoadExample />;
},
platform: 'ios',
},
];