ImageDefinition

ImageDefinition is a specialized CoValue designed specifically for managing images in Jazz. It extends beyond basic file storage by supporting multiple resolutions of the same image, optimized for mobile devices.

Note: This guide applies to both Expo and framework-less React Native implementations. The functionality described here is identical regardless of which implementation you're using, though you'll need to import from the appropriate package (jazz-expo or jazz-react-native).

Jazz offers several tools to work with images in React Native:

  • createImage() - function to create an ImageDefinition from a base64 image data URI
  • ProgressiveImg - React component to display an image with progressive loading
  • useProgressiveImg - React hook to load an image in your own component

For examples of use, see our example apps:

Creating Images

The easiest way to create and use images in your Jazz application is with the createImage() function:

import { createImage } from "jazz-react-native-media-images";
import { launchImageLibrary } from 'react-native-image-picker';

async function handleImagePicker() {
  try {
    // Launch the image picker
    const result = await launchImageLibrary({
      mediaType: 'photo',
      includeBase64: true,
      quality: 1,
    });

    if (!result.canceled) {
      const base64Uri = `data:image/jpeg;base64,${result.assets[0].base64}`;

      // Creates ImageDefinition with multiple resolutions automatically
      const image = await createImage(base64Uri, {
        owner: me.profile._owner,
        maxSize: 2048, // Optional: limit maximum resolution
      });

      // Store the image
      me.profile.image = image;
    }
  } catch (error) {
    console.error("Error creating image:", error);
  }
}

The createImage() function:

  • Creates an ImageDefinition with the right properties
  • Generates a small placeholder for immediate display
  • Creates multiple resolution variants of your image
  • Returns the created ImageDefinition

Configuration Options

You can configure createImage() with additional options:

// Configuration options
const options = {
  owner: me,                // Owner for access control
  maxSize: 1024             // Maximum resolution to generate
};

// Setting maxSize controls which resolutions are generated:
// 256: Only creates the smallest resolution (256px on longest side)
// 1024: Creates 256px and 1024px resolutions
// 2048: Creates 256px, 1024px, and 2048px resolutions
// undefined: Creates all resolutions including the original size

const image = await createImage(base64Uri, options);

Displaying Images with ProgressiveImg

For a complete progressive loading experience, use the ProgressiveImg component:

import { ProgressiveImg } from "jazz-react-native";
import { Image, StyleSheet } from "react-native";

function GalleryView({ image }) {
  return (
    <ProgressiveImg
      image={image}  // The image definition to load
      targetWidth={800} //  Looks for the best available resolution for a 800px image
    >
      {({ src }) => (
        <Image
          source={{ uri: src }}
          style={styles.galleryImage}
          resizeMode="cover"
        />
      )}
    </ProgressiveImg>
  );
}

const styles = StyleSheet.create({
  galleryImage: {
    width: '100%',
    height: 200,
    borderRadius: 8,
  }
});

The ProgressiveImg component handles:

  • Showing a placeholder while loading
  • Automatically selecting the appropriate resolution
  • Progressive enhancement as higher resolutions become available
  • Cleaning up resources when unmounted

Using useProgressiveImg Hook

For more control over image loading, you can implement your own progressive image component:

import { useProgressiveImg } from "jazz-react-native";
import { Image, View, Text, ActivityIndicator } from "react-native";

function CustomImageComponent({ image }) {
  const {
    src,         // Data URI containing the image data as a base64 string,
                 // or a placeholder image URI
    res,         // The current resolution
    originalSize // The original size of the image
  } = useProgressiveImg({
    image: image,  // The image definition to load
    targetWidth: 800  // Limit to resolutions up to 800px wide
  });

  // When image is not available yet
  if (!src) {
    return (
      <View style={{ height: 200, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f0f0f0' }}>
        <ActivityIndicator size="small" color="#0000ff" />
        <Text style={{ marginTop: 10 }}>Loading image...</Text>
      </View>
    );
  }

  // When using placeholder
  if (res === "placeholder") {
    return (
      <View style={{ position: 'relative' }}>
        <Image
          source={{ uri: src }}
          style={{ width: '100%', height: 200, opacity: 0.7 }}
          resizeMode="cover"
        />
        <ActivityIndicator
          size="large"
          color="#ffffff"
          style={{ position: 'absolute', top: '50%', left: '50%', marginLeft: -20, marginTop: -20 }}
        />
      </View>
    );
  }

  // Full image display with custom overlay
  return (
    <View style={{ position: 'relative', width: '100%', height: 200 }}>
      <Image
        source={{ uri: src }}
        style={{ width: '100%', height: '100%' }}
        resizeMode="cover"
      />
      <View style={{ position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: 'rgba(0,0,0,0.5)', padding: 8 }}>
        <Text style={{ color: 'white' }}>Resolution: {res}</Text>
      </View>
    </View>
  );
}

Understanding ImageDefinition

Behind the scenes, ImageDefinition is a specialized CoValue that stores:

  • The original image dimensions (originalSize)
  • An optional placeholder (placeholderDataURL) for immediate display
  • Multiple resolution variants of the same image as FileStreams

Each resolution is stored with a key in the format "widthxheight" (e.g., "1920x1080", "800x450").

// Structure of an ImageDefinition
const image = ImageDefinition.create({
  originalSize: [1920, 1080],
  placeholderDataURL: "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
});

// Accessing the highest available resolution
const highestRes = image.highestResAvailable();
if (highestRes) {
  console.log(`Found resolution: ${highestRes.res}`);
  console.log(`Stream: ${highestRes.stream}`);
}

For more details on using ImageDefinition directly, see the VanillaJS docs.

Fallback Behavior

highestResAvailable returns the largest resolution that fits your constraints. If a resolution has incomplete data, it falls back to the next available lower resolution.

const image = ImageDefinition.create({
  originalSize: [1920, 1080],
});

image["1920x1080"] = FileStream.create(); // Empty image upload
image["800x450"] = await FileStream.createFromBlob(mediumSizeBlob);

const highestRes = image.highestResAvailable();
console.log(highestRes.res); // 800x450