A Photo

Understanding the Photo type

A Photo is a RAW- or processed in-memory buffer, captured by a CameraPhotoOutput.

See "The Photo Output" for more information about capturing Photos.

Displaying a Photo

A Photo holds an in-memory buffer of its pixels, and can be converted to an Image (from react-native-nitro-image) entirely in-memory by using toImageAsync():

const photo = ...
const image = await photo.toImageAsync()
photo.dispose()

The image can then be displayed in a <NitroImage /> view:

import { NitroImage } from 'react-native-nitro-image'

function App() {
  const image = ...
  return (
    <NitroImage
      style={{ flex: 1 }}
      image={image}
    />
  )
}

Dispose when no longer used

As with all in-memory objects holding large native memory (Photo, Frame, Image or more) make sure to dispose() the Photo once you are no longer using it, as otherwise the JS Runtime might not immediately delete it, possibly exhausting system resources or even stalling the Camera preventing further captures.

const photo = ...
photo.dispose()

Saving to a File

To save a Photo to an image file, use saveToTemporaryFileAsync() or saveToFileAsync(...):

const photo = ...
const path = await photo.saveToTemporaryFileAsync()

Saving to the Camera Roll

After a Photo has been saved to a file, you can save it to the user's Camera Roll using a library like @react-native-camera-roll/camera-roll or expo-media-library.

Container Formats

Photos can be captured in different container formats (see PhotoContainerFormat), which adjusts the capture pipeline's processing steps accordingly - for example, 'dcm' is a RAW format and applies almost no processing in a capture pipeline, whereas 'jpeg' or 'heic' are processed formats and apply color-grading, compression, multi-capture-fusion, HDR, and other processing steps in the capture pipeline.

You can inspect a Photo's PhotoContainerFormat via the containerFormat property:

console.log(photo.containerFormat) // 'jpeg'

Accessing a Photo's Pixel Buffer

Some Photos allow accessing their pixel buffer directly in-memory, without decoding. On iOS, this is often only available on RAW photos (containerFormat == 'dcm'), while on Android this is available on all Photos.

if (photo.hasPixelBuffer) {
  const pixelBuffer = photo.getPixelBuffer()
  const rgba = new Uint8Array(pixelBuffer)
  // process pixels
}

A Photo's Pixel Buffer does not contain any container metadata (JPEG/HEIC headers), or EXIF flags - it's purely pixels.

Accessing the Pixel Buffer via NitroImage

Since a Photo can be converted to an Image (from react-native-nitro-image), it is also a common pattern to access pixels via the react-native-nitro-image APIs;

const photo = ...
const image = await photo.toImageAsync()
const pixelBuffer = await image.toRawPixelData()

Note

It is worth noting that converting to an Image performs an encoding pass, and reading the Pixel Buffer requires a decoding pass.

Accessing a Photo's Pixel Buffer directly does not require any encoding/decoding passes.

Accessing a Photo's Encoded Image Data

The Photo also provides access to its Encoded Image Data - which is what would be written to a File if it was saved - including container metadata and EXIF flags.

You can get the Encoded Image Data via getFileDataAsync(), for example to write this to a network stream entirely in-memory;

const photo = ...
const encodedData = await photo.getFileDataAsync()
// Upload to backend:
await fetch('https://my-backend.com/upload', {
  method: 'POST',
  headers: { 'Content-Type': 'image/jpeg' },
  body: Buffer.from(encodedData)
})

Skia Images

A Photo can also be imported into @shopify/react-native-skia using the Encoded Image Data APIs, which allows you to apply shaders or custom drawing/rendering commands to it:

const photo = ...
const encodedData = await photo.getFileDataAsync()
// Convert Photo to SkImage:
const bytes = new Uint8Array(buffer)
const data = Skia.Data.fromBytes(bytes)
const image = Skia.Image.MakeImageFromEncoded(data)
// Render SkImage in SkCanvas now