|
@@ -1,4 +1,3 @@
|
1
|
|
-
|
2
|
1
|
# react-native-view-shot ![](https://img.shields.io/npm/v/react-native-view-shot.svg) ![](https://img.shields.io/badge/react--native-%2040+-05F561.svg)
|
3
|
2
|
|
4
|
3
|
Capture a React Native view to an image.
|
|
@@ -9,11 +8,22 @@ Capture a React Native view to an image.
|
9
|
8
|
|
10
|
9
|
```bash
|
11
|
10
|
yarn add react-native-view-shot
|
12
|
|
-react-native link react-native-view-shot
|
13
|
11
|
```
|
14
|
12
|
|
15
|
13
|
Make sure react-native-view-shot is correctly linked in XCode (might require a manual installation, refer to [React Native doc](https://facebook.github.io/react-native/docs/linking-libraries-ios.html)).
|
16
|
14
|
|
|
15
|
+**Before React Native 0.60.x you would have to:**
|
|
16
|
+
|
|
17
|
+```bash
|
|
18
|
+react-native link react-native-view-shot
|
|
19
|
+```
|
|
20
|
+
|
|
21
|
+**Since 0.60.x, [autolink](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md) should just work**, On iOS, you might have to:
|
|
22
|
+
|
|
23
|
+```bash
|
|
24
|
+cd ios && pod install && cd ..
|
|
25
|
+```
|
|
26
|
+
|
17
|
27
|
## Recommended High Level API
|
18
|
28
|
|
19
|
29
|
```js
|
|
@@ -81,6 +91,7 @@ class ExampleCaptureScrollViewContent extends Component {
|
81
|
91
|
}
|
82
|
92
|
}
|
83
|
93
|
```
|
|
94
|
+
|
84
|
95
|
**Props:**
|
85
|
96
|
|
86
|
97
|
- **`children`**: the actual content to rasterize.
|
|
@@ -101,8 +112,7 @@ import { captureRef } from "react-native-view-shot";
|
101
|
112
|
captureRef(viewRef, {
|
102
|
113
|
format: "jpg",
|
103
|
114
|
quality: 0.8
|
104
|
|
-})
|
105
|
|
-.then(
|
|
115
|
+}).then(
|
106
|
116
|
uri => console.log("Image saved to", uri),
|
107
|
117
|
error => console.error("Oops, snapshot failed", error)
|
108
|
118
|
);
|
|
@@ -112,14 +122,14 @@ Returns a Promise of the image URI.
|
112
|
122
|
|
113
|
123
|
- **`view`** is a reference to a React Native component.
|
114
|
124
|
- **`options`** may include:
|
115
|
|
- - **`width`** / **`height`** *(number)*: the width and height of the final image (resized from the View bound. don't provide it if you want the original pixel size).
|
116
|
|
- - **`format`** *(string)*: either `png` or `jpg` or `webm` (Android). Defaults to `png`.
|
117
|
|
- - **`quality`** *(number)*: the quality. 0.0 - 1.0 (default). (only available on lossy formats like jpg)
|
118
|
|
- - **`result`** *(string)*, the method you want to use to save the snapshot, one of:
|
119
|
|
- - `"tmpfile"` (default): save to a temporary file *(that will only exist for as long as the app is running)*.
|
120
|
|
- - `"base64"`: encode as base64 and returns the raw string. Use only with small images as this may result of lags (the string is sent over the bridge). *N.B. This is not a data uri, use `data-uri` instead*.
|
|
125
|
+ - **`width`** / **`height`** _(number)_: the width and height of the final image (resized from the View bound. don't provide it if you want the original pixel size).
|
|
126
|
+ - **`format`** _(string)_: either `png` or `jpg` or `webm` (Android). Defaults to `png`.
|
|
127
|
+ - **`quality`** _(number)_: the quality. 0.0 - 1.0 (default). (only available on lossy formats like jpg)
|
|
128
|
+ - **`result`** _(string)_, the method you want to use to save the snapshot, one of:
|
|
129
|
+ - `"tmpfile"` (default): save to a temporary file _(that will only exist for as long as the app is running)_.
|
|
130
|
+ - `"base64"`: encode as base64 and returns the raw string. Use only with small images as this may result of lags (the string is sent over the bridge). _N.B. This is not a data uri, use `data-uri` instead_.
|
121
|
131
|
- `"data-uri"`: same as `base64` but also includes the [Data URI scheme](https://en.wikipedia.org/wiki/Data_URI_scheme) header.
|
122
|
|
- - **`snapshotContentContainer`** *(bool)*: if true and when view is a ScrollView, the "content container" height will be evaluated instead of the container height.
|
|
132
|
+ - **`snapshotContentContainer`** _(bool)_: if true and when view is a ScrollView, the "content container" height will be evaluated instead of the container height.
|
123
|
133
|
|
124
|
134
|
## `releaseCapture(uri)`
|
125
|
135
|
|
|
@@ -135,14 +145,13 @@ import { captureScreen } from "react-native-view-shot";
|
135
|
145
|
captureScreen({
|
136
|
146
|
format: "jpg",
|
137
|
147
|
quality: 0.8
|
138
|
|
-})
|
139
|
|
-.then(
|
|
148
|
+}).then(
|
140
|
149
|
uri => console.log("Image saved to", uri),
|
141
|
150
|
error => console.error("Oops, snapshot failed", error)
|
142
|
151
|
);
|
143
|
152
|
```
|
144
|
153
|
|
145
|
|
-This method will capture the contents of the currently displayed screen as a native hardware screenshot. It does not require a ref input, as it does not work at the view level. This means that ScrollViews will not be captured in their entirety - only the portions currently visible to the user.
|
|
154
|
+This method will capture the contents of the currently displayed screen as a native hardware screenshot. It does not require a ref input, as it does not work at the view level. This means that ScrollViews will not be captured in their entirety - only the portions currently visible to the user.
|
146
|
155
|
|
147
|
156
|
Returns a Promise of the image URI.
|
148
|
157
|
|
|
@@ -158,17 +167,18 @@ Returns a Promise of the image URI.
|
158
|
167
|
|
159
|
168
|
Model tested: iPhone 6 (iOS), Nexus 5 (Android).
|
160
|
169
|
|
161
|
|
-| System | iOS | Android | Windows |
|
162
|
|
-|--------------------|--------------------|-------------------|-------------------|
|
163
|
|
-| View,Text,Image,.. | YES | YES | YES |
|
164
|
|
-| WebView | YES | YES<sup>1</sup> | YES |
|
165
|
|
-| gl-react v2 | YES | NO<sup>2</sup> | NO<sup>3</sup> |
|
166
|
|
-| react-native-video | NO | NO | NO |
|
167
|
|
-| react-native-maps | YES | NO<sup>4</sup> | NO<sup>3</sup> |
|
168
|
|
-| react-native-svg | YES | YES | maybe? |
|
169
|
|
-| react-native-camera | NO | YES | NO <sup>3</sup> |
|
|
170
|
+| System | iOS | Android | Windows |
|
|
171
|
+| --------------------- | ---------------- | ----------------- | ---------------------- |
|
|
172
|
+| View,Text,Image,.. | YES | YES | YES |
|
|
173
|
+| WebView | YES | YES<sup>1</sup> | YES |
|
|
174
|
+| gl-react v2 | YES | NO<sup>2</sup> | NO<sup>3</sup> |
|
|
175
|
+| react-native-video | NO | NO | NO |
|
|
176
|
+| react-native-maps | YES | NO<sup>4</sup> | NO<sup>3</sup> |
|
|
177
|
+| react-native-svg | YES | YES | maybe? |
|
|
178
|
+| react-native-camera | NO | YES | NO <sup>3</sup> |
|
170
|
179
|
|
171
|
180
|
>
|
|
181
|
+
|
172
|
182
|
1. Only supported by wrapping a `<View collapsable={false}>` parent and snapshotting it.
|
173
|
183
|
2. It returns an empty image (not a failure Promise).
|
174
|
184
|
3. Component itself lacks platform support.
|
|
@@ -177,11 +187,13 @@ Model tested: iPhone 6 (iOS), Nexus 5 (Android).
|
177
|
187
|
## Performance Optimization
|
178
|
188
|
|
179
|
189
|
During profiling captured several things that influence on performance:
|
180
|
|
-1) (de-)allocation of memory for bitmap
|
181
|
|
-2) (de-)allocation of memory for Base64 output buffer
|
182
|
|
-3) compression of bitmap to different image formats: PNG, JPG
|
|
190
|
+
|
|
191
|
+1. (de-)allocation of memory for bitmap
|
|
192
|
+2. (de-)allocation of memory for Base64 output buffer
|
|
193
|
+3. compression of bitmap to different image formats: PNG, JPG
|
183
|
194
|
|
184
|
195
|
To solve that in code introduced several new approaches:
|
|
196
|
+
|
185
|
197
|
- reusable images, that reduce load on GC;
|
186
|
198
|
- reusable arrays/buffers that also reduce load on GC;
|
187
|
199
|
- RAW image format for avoiding expensive compression;
|
|
@@ -194,6 +206,7 @@ more details and code snippet are below.
|
194
|
206
|
Introduced a new image format RAW. it correspond a ARGB array of pixels.
|
195
|
207
|
|
196
|
208
|
Advantages:
|
|
209
|
+
|
197
|
210
|
- no compression, so its supper quick. Screenshot taking is less than 16ms;
|
198
|
211
|
|
199
|
212
|
RAW format supported for `zip-base64`, `base64` and `tmpfile` result types.
|
|
@@ -209,32 +222,32 @@ approach for capturing screen views and deliver them to the react side.
|
209
|
222
|
### How to work with zip-base64 and RAW format?
|
210
|
223
|
|
211
|
224
|
```js
|
212
|
|
-const fs = require('fs')
|
213
|
|
-const zlib = require('zlib')
|
214
|
|
-const PNG = require('pngjs').PNG
|
215
|
|
-const Buffer = require('buffer').Buffer
|
|
225
|
+const fs = require("fs");
|
|
226
|
+const zlib = require("zlib");
|
|
227
|
+const PNG = require("pngjs").PNG;
|
|
228
|
+const Buffer = require("buffer").Buffer;
|
216
|
229
|
|
217
|
|
-const format = Platform.OS === 'android' ? 'raw' : 'png'
|
218
|
|
-const result = Platform.OS === 'android' ? 'zip-base64' : 'base64'
|
|
230
|
+const format = Platform.OS === "android" ? "raw" : "png";
|
|
231
|
+const result = Platform.OS === "android" ? "zip-base64" : "base64";
|
219
|
232
|
|
220
|
233
|
captureRef(this.ref, { result, format }).then(data => {
|
221
|
|
- // expected pattern 'width:height|', example: '1080:1731|'
|
222
|
|
- const resolution = /^(\d+):(\d+)\|/g.exec(data)
|
223
|
|
- const width = (resolution || ['', 0, 0])[1]
|
224
|
|
- const height = (resolution || ['', 0, 0])[2]
|
225
|
|
- const base64 = data.substr((resolution || [''])[0].length || 0)
|
226
|
|
-
|
227
|
|
- // convert from base64 to Buffer
|
228
|
|
- const buffer = Buffer.from(base64, 'base64')
|
229
|
|
- // un-compress data
|
230
|
|
- const inflated = zlib.inflateSync(buffer)
|
231
|
|
- // compose PNG
|
232
|
|
- const png = new PNG({ width, height })
|
233
|
|
- png.data = inflated
|
234
|
|
- const pngData = PNG.sync.write(png)
|
235
|
|
- // save composed PNG
|
236
|
|
- fs.writeFileSync(output, pngData)
|
237
|
|
-})
|
|
234
|
+ // expected pattern 'width:height|', example: '1080:1731|'
|
|
235
|
+ const resolution = /^(\d+):(\d+)\|/g.exec(data);
|
|
236
|
+ const width = (resolution || ["", 0, 0])[1];
|
|
237
|
+ const height = (resolution || ["", 0, 0])[2];
|
|
238
|
+ const base64 = data.substr((resolution || [""])[0].length || 0);
|
|
239
|
+
|
|
240
|
+ // convert from base64 to Buffer
|
|
241
|
+ const buffer = Buffer.from(base64, "base64");
|
|
242
|
+ // un-compress data
|
|
243
|
+ const inflated = zlib.inflateSync(buffer);
|
|
244
|
+ // compose PNG
|
|
245
|
+ const png = new PNG({ width, height });
|
|
246
|
+ png.data = inflated;
|
|
247
|
+ const pngData = PNG.sync.write(png);
|
|
248
|
+ // save composed PNG
|
|
249
|
+ fs.writeFileSync(output, pngData);
|
|
250
|
+});
|
238
|
251
|
```
|
239
|
252
|
|
240
|
253
|
Keep in mind that packaging PNG data is a CPU consuming operation as a `zlib.inflate`.
|
|
@@ -244,6 +257,7 @@ Hint: use `process.fork()` approach for converting raw data into PNGs.
|
244
|
257
|
> Note: code is tested in large commercial project.
|
245
|
258
|
|
246
|
259
|
> Note #2: Don't forget to add packages into your project:
|
|
260
|
+>
|
247
|
261
|
> ```js
|
248
|
262
|
> yarn add pngjs
|
249
|
263
|
> yarn add zlib
|
|
@@ -285,7 +299,6 @@ Alternatively, you can use the `ViewShot` component that will wait the first `on
|
285
|
299
|
|
286
|
300
|
This is because the snapshot image result is in real pixel size where the width/height defined in a React Native style are defined in "point" unit. You might want to set width and height option to force a resize. (might affect image quality)
|
287
|
301
|
|
288
|
|
-
|
289
|
302
|
---
|
290
|
303
|
|
291
|
304
|
## Thanks
|