Skip to content

Export formats ​

MethodOutputTrim default
toBase64(options?)PNG or JPEG, raw base64 stringfalse
toFile(options?)PNG or JPEG file, file:// URIfalse
toSvg()SVG document stringalways cropped
copyToClipboard()PNG on the system clipboardalways trimmed
saveToPhotoLibrary(options?)PNG or JPEG in Photos or the gallerytrue
getStrokeData()The raw strokes as JSON-friendly data

Image options ​

toBase64, toFile and saveToPhotoLibrary take the same options:

OptionTypeDefaultNotes
format'png' | 'jpeg''png'PNG is lossless with alpha. JPEG is smaller and has no alpha.
qualitynumber10 to 1, JPEG only. Ignored for PNG.
trimbooleanfalse (true for saveToPhotoLibrary)Crop to the signature instead of the whole canvas.

Images are rendered at the screen's pixel density: a 300×150 pt view on a 3× iPhone gives a 900×450 px image, and on Android the size is the view's size in physical pixels.

trim crops to the bounding box of the strokes plus a small margin (2 pt on iOS; on Android 0.6 × penMaxWidth dp plus 2 px, kept inside the view). On an empty canvas you get the full canvas.

Backgrounds ​

What ends up behind the ink depends on the format, the destination and the platform:

ExportiOSAndroid
PNG from toBase64 / toFileAlways transparentbackgroundColor if set, otherwise transparent
JPEG (any method)backgroundColor, or white if transparentbackgroundColor, or white if transparent
copyToClipboard()TransparentbackgroundColor if set, otherwise transparent
PNG from saveToPhotoLibraryAlways opaque: backgroundColor, or whitebackgroundColor if set, otherwise transparent

iOS always saves an opaque image to Photos because the Photos viewer shows transparent images on black, which makes dark ink unreadable. If you want the same opaque result on Android, set backgroundColor or use format: 'jpeg'.

The baseline and the toolbar are never part of an export.

Base64 ​

tsx
const base64 = await ref.current?.toBase64({ format: 'png', trim: true });
const dataUri = `data:image/png;base64,${base64}`;

The string has no data: prefix and no line breaks. Base64 is about a third larger than the binary image and crosses to JavaScript as one string, so prefer toFile() for full-resolution images you are going to upload anyway.

File ​

tsx
const uri = await ref.current?.toFile({ format: 'jpeg', quality: 0.85 });
// file:///.../signature-1728100000000.jpg

The file is written to the app's temporary directory on iOS and to the cache directory on Android, named signature-<timestamp>.png or .jpg. The library never deletes these files. Move them somewhere permanent or delete them when you are done.

SVG ​

tsx
const svg = await ref.current?.toSvg();

The SVG contains one <path> per stroke, built from the stroke's points as straight segments, with the stroke color as a hex value and round caps and joins. The viewBox is cropped to the strokes. Keep in mind:

  • Each path has a single width. On iOS it is the average width of the stroke's points; on Android it is the midpoint of penMinWidth and penMaxWidth. The thick and thin parts of the on-screen ink are not reproduced.
  • Units differ. iOS uses points; Android uses physical pixels, so the numbers in an Android SVG are larger on high-density screens. Scale the SVG through its viewBox rather than relying on its width and height.
  • On Android a single tap (a dot) becomes a <circle>.
  • Colors are written without alpha.

If you need exact geometry for your own rendering, use getStrokeData(), which uses points or dp on both platforms.

Clipboard ​

tsx
ref.current?.copyToClipboard();

Copies a trimmed PNG. It returns nothing and does not report errors. On an empty canvas it copies a blank image, so check isEmpty() first if that matters.

  • iOS sets UIPasteboard.general.image.
  • Android writes signature-clipboard.png to the cache directory and puts a content:// URI from the bundled FileProvider on the clipboard. It also grants the system UI read access so the Android 13+ clipboard preview can show a thumbnail.

The built-in toolbar's copy button calls the same code.

Photo library ​

tsx
const result = await ref.current?.saveToPhotoLibrary({ format: 'png' });
if (result && !result.granted) {
  // iOS: the user denied access
}

The Promise resolves with { granted, uri? }:

iOSAndroid
PermissionAsks for "Add to Photos" access the first time. Needs NSPhotoLibraryAddUsageDescription.None on API 29+. API 28 and older need WRITE_EXTERNAL_STORAGE.
WhereThe Photos library, keeping the PNG or JPEG formatPictures/Signatures/ through MediaStore (on API 29+)
granted: falseThe user denied or restricted accessThe image could not be inserted or written
uriNot setThe content:// URI of the new image
Rejects whenPhotos fails to save the imageAn exception is thrown, for example a missing permission on API 28 and older

See Installation for the permission setup.

Released under the MIT License.