react-native-watermark-view
v0.1.1
Published
Burn an arbitrary React Native view (text, images, layout) into an image file as a watermark.
Downloads
24
Readme
react-native-watermark-view
Burn an arbitrary React Native view — text, images, flexbox layout, anything you can render — into an image file as a watermark.
Unlike watermarking libraries that only accept a text string or a single image
with fixed placement, the overlay here is your own JSX. The result is a new file
on disk that you render with a plain <Image> or upload.
const ref = useRef<WatermarkCanvasHandle>(null);
<WatermarkCanvas
ref={ref}
source={{ uri: photoUri }}
baseWidth={1000}
onReady={() => setReady(true)}
>
<View style={{ position: 'absolute', bottom: 24, left: 24 }}>
<Image source={logo} style={{ width: 64, height: 64 }} />
<Text style={{ color: '#fff', fontSize: 28 }}>{timestamp}</Text>
<Text style={{ color: '#fff', fontSize: 20 }}>{address}</Text>
</View>
</WatermarkCanvas>;
const { uri } = await ref.current.save({ maxWidth: 1600, quality: 0.9 });Why the output stays sharp
A React Native view is laid out at screen density, around 360dp wide. Capturing it and scaling the bitmap up to a 4000px photo produces blurry text.
This library never upscales a bitmap. The overlay is snapshotted at the output
scale, so the platform re-renders the text and vector content at the final
resolution. baseWidth defines the coordinate space you write the overlay in;
native scales that space to the output width. One overlay looks the same on an
800px image and a 4000px one.
Requirements
- React Native New Architecture (TurboModules). No legacy bridge support.
- iOS and Android.
- No third-party dependencies — capture and compositing are implemented natively.
Installation
npm install react-native-watermark-view
cd ios && pod installAPI
<WatermarkCanvas>
Renders off screen by default. Its children are the watermark.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| source | { uri: string } | number | — | Remote url, file://, content://, a bare path, or a require()d asset. |
| baseWidth | number | 1000 | Coordinate space the overlay is written in. |
| preview | boolean | false | Render on screen instead of off it. |
| onReady | (size) => void | — | Fires once the source dimensions are known and children are laid out. |
| onError | (error: WatermarkError) => void | — | Reports a failure to read the source. |
ref.save(options?)
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| maxWidth | number | source width | Output width. Never upscales past the source, and is capped at 4096. |
| quality | number | 0.9 | JPEG quality within (0, 1]. Ignored for PNG. |
| format | 'jpeg' \| 'png' | 'jpeg' | Output format. |
| filename | string | a uuid | File name inside the cache directory. |
Resolves to { uri, path, width, height }. Call it only after onReady.
saveToGallery(path, options?)
Copies a finished file into the device gallery.
const { path } = await ref.current.save({ maxWidth: 1600 });
const { uri } = await saveToGallery(path, { album: 'Absensi' });Resolves to { uri } — content://… on Android, ph://… on iOS. The album
name is reduced to a single directory segment, so it cannot escape the
platform pictures directory.
iOS: this step is not optional. Add
NSPhotoLibraryAddUsageDescriptionto your app'sInfo.plist. Without it iOS terminates your app when saving — it is not an error this library can catch and report.
On Android 9 and below the library requests WRITE_EXTERNAL_STORAGE for you
and rejects with PERMISSION_DENIED if the user declines. Android 10+ needs no
permission at all.
clearCache()
Deletes every file this library has written to the cache directory.
Error codes
Failures reject with a WatermarkError carrying a code:
| Code | Meaning |
| --- | --- |
| SOURCE_NOT_FOUND | The local path does not exist or cannot be read. |
| SOURCE_DOWNLOAD_FAILED | A remote fetch failed or returned a non-2xx status. |
| DECODE_FAILED | The file is not a recognisable image. |
| VIEW_NOT_READY | save() ran before onReady, or the view is not mounted. |
| OUT_OF_MEMORY | Bitmap allocation failed. Lower maxWidth. |
| WRITE_FAILED | The disk is full or the cache directory is not writable. |
Limitations
These are real constraints of the approach, not oversights:
- Android does not capture hardware-layer effects.
elevationshadows and blur are drawn by the GPU compositor, whichView.draw(Canvas)does not invoke. They will be missing from the output. UsetextShadowor a transparent PNG instead. - iOS and Android text rendering differ. Results are visually equivalent, not pixel-identical. Do not assert pixel equality across platforms.
- Video, animated GIF, and multi-frame HEIC are out of scope. Single-frame HEIC works wherever the platform can decode it.
- Output files land in the app cache directory, which the OS may clear. Move or upload the file if you need it to persist.
Contributing
See CONTRIBUTING.md.
License
MIT
