Images

Loading, drawing, sampling and deriving images.

App.tsx
1import { Canvas, TgfxImage, useImage } from 'react-native-tgfx'
2
3export function Sea() {
4 const photo = useImage(require('./sea.jpg'))
5 const crop = { x: , y: 340,
6 width: 600, height: 300 }
7 return (
8 <Canvas style={{ flex: 1 }}>
9 <TgfxImage image={photo} fit="cover"
10 x={0} y={0} width={402} height={200}
11 />
12 <TgfxImage image={photo} rect={crop}
13 x={0} y={212} width={402} height={200}
14 />
15 </Canvas>
16 )
17}
9:41

Image takes a require()d asset, URL, file path, SVG markup or base64:

import { Canvas, Image } from 'react-native-tgfx'

<Canvas style={{ width: 300, height: 400 }}>
  <Image source={require('./cat.png')} x={0} y={0} width={300} height={200} fit="cover" />
  <Image source="https://picsum.photos/300/200" x={0} y={200} width={300} height={200} />
</Canvas>

For hundreds of animated nodes, TgfxImage is faster. It takes a loaded handle:

import { Canvas, TgfxImage, useImage } from 'react-native-tgfx'

const photo = useImage(require('./cat.png'))

photo != null && (
  <TgfxImage image={photo} x={0} y={0} width={300} height={200} fit="cover" />
)

useImage returns TgfxImage or null. useImageState returns ImageState with loading and error. loadImage is the promise form.

ImageProps · Fit · Sampling

In Android release builds, asset names can't differ only by extension (logo.png, logo.webp).

SVG documents

A whole SVG document draws with ImageSVG:

import { Canvas, ImageSVG, useImage } from 'react-native-tgfx'

const icon = useImage(require('./logo.svg'))
const dot = useImage('<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="#0a84ff"/></svg>')

<ImageSVG image={icon} x={0} y={0} width={96} height={96} />
<ImageSVG image={dot} x={104} y={0} width={96} height={96} />

<ImageSVG> is <TgfxImage> under a Skia-compatible name. It is redrawn at any size. Use <Path> for a single d string.

ImageSVGProps

Colour profiles

Embedded ICC profiles are honoured: images are converted into the canvas's colour space. An sRGB canvas clips out-of-gamut colour. A colorSpace="display-p3" canvas keeps it.

Images with no readable profile are read as sRGB.

On iOS, PNG and JPEG decode to sRGB, so a P3 canvas cannot recover wide-gamut colour.

Sub-rects

<TgfxImage
  x={0} y={0} width={60} height={60}
  image={sheet}
  rect={{ x: 32, y: 0, width: 32, height: 32 }}
  sampling="nearest"
/>

As a shader

ImageShader paints any shape with an image.

<Circle cx={60} cy={60} r={50}>
  <ImageShader image={photo} rect={{ x: 10, y: 10, width: 100, height: 100 }} fit="cover" />
</Circle>

Derived images

These reuse the decoded source.

const thumb = photo.scaled(200, 200)
const sprite = sheet.subset(0, 0, 64, 64)
const upright = photo.oriented('rightTop')
const frosted = photo.blurred(20, 20)

oriented takes an EXIF Orientation.

useDerivedImage is the hook form (Derivation).

const thumb = useDerivedImage(photo, { type: 'scaled', width: 200, height: 200 })

Encode and save

Any image handle encodes to bytes or saves to disk:

const bytes = await image.encode('webp', 90)   // zero-copy ArrayBuffer, for upload
await image.save(`${dir}/thumb.jpg`, 'jpeg', 85) // written natively; bytes never enter JS

format is 'png' (default), 'jpeg', 'webp' or 'raw' (straight-alpha RGBA). Disposed handles and live camera or video frames reject.

Native buffers

imageFromNativeBuffer wraps a camera or decoder buffer without a copy (PixelFormat). Release the producer's buffer after wrapping, or use useCameraFrame.

let frameImage: TgfxImage
const buffer = frame.getNativeBuffer()
try {
  frameImage = imageFromNativeBuffer(buffer.pointer, 'bt601-limited')
} finally {
  buffer.release()
  frame.dispose()
}

// Draw with frameImage, then dispose it when the stream ends.

Decoded pixels

imageFromPixels wraps premultiplied RGBA, such as an ML model's output. rowBytes (a multiple of 4) defaults to tightly packed.

const image = imageFromPixels(pixels, width, height)

A buffer from another native module is borrowed, so don't write to it while the handle uses it. A JS ArrayBuffer is copied.

subset, scaled, oriented, blurred, encode and save throw on these handles.

For a stream, reuse one handle with setPixels:

if (image != null) {
  image.setPixels(pixels, width, height)
} else {
  image = imageFromPixels(pixels, width, height)
}

Cache

setImageCacheLimit(32 * 1024 * 1024)

The default is 64 MB, applied to both the decoded-pixel and GPU caches.

getCacheInfo reports usage. purgeCaches frees it all, e.g. on memory pressure:

const { imageBytes, imageLimit, gpuBytes, offscreenGpuBytes } = getCacheInfo()

purgeCaches()

Handles stay valid. Read gpuBytes a frame after purging.

Video

useVideo returns a Video whose currentFrame goes straight to <TgfxImage>. It needs Reanimated and Worklets. loadVideo returns the raw native player instead, so call updateFrame() and release() yourself.

Its options are a VideoOptions:

useVideo(require('./clip.mp4'), {
  seek: 1500,     // jump to 1.5s; seeks again whenever this changes
  paused: false,  // plays as soon as it's open (the default)
  looping: true,
  volume: 0.5,    // 0 to 1, defaults to 0 - a canvas video is usually decoration
})

On this page