Skip to main content

Overview

The useStyleTransfer hook manages a style transfer model instance for applying artistic styles to images. It transforms content images to match the artistic style of a reference style.

Import

import { useStyleTransfer } from 'react-native-executorch';

Hook Signature

const styleTransfer = useStyleTransfer({ model, preventLoad }: StyleTransferProps): StyleTransferType

Parameters

model
object
required
Object containing model source
preventLoad
boolean
default:"false"
If true, prevents automatic model loading and downloading when the hook mounts

Return Value

Returns an object with the following properties and methods:

State Properties

isReady
boolean
Indicates whether the style transfer model is loaded and ready to process images.
isGenerating
boolean
Indicates whether the model is currently processing an image.
downloadProgress
number
Download progress as a value between 0 and 1.
error
RnExecutorchError | null
Contains error details if the model fails to load or encounters an error during style transfer.

Methods

forward
function
Executes the model’s forward pass to apply artistic style to the provided image.
forward(imageSource: string): Promise<string>
Returns a promise that resolves to a string containing the stylized image (typically as a base64 string or file URI).

Usage Examples

Basic Style Transfer

import { useStyleTransfer } from 'react-native-executorch';
import { useState } from 'react';
import { launchImageLibrary } from 'react-native-image-picker';

function StyleTransferApp() {
  const [originalUri, setOriginalUri] = useState<string | null>(null);
  const [styledUri, setStyledUri] = useState<string | null>(null);
  
  const styleTransfer = useStyleTransfer({
    model: {
      modelSource: 'https://huggingface.co/.../style-transfer.pte',
    },
  });
  
  const applyStyle = async (uri: string) => {
    if (!styleTransfer.isReady) return;
    
    try {
      const result = await styleTransfer.forward(uri);
      setStyledUri(result);
      console.log('Style applied successfully!');
    } catch (error) {
      console.error('Style transfer failed:', error);
    }
  };
  
  const pickAndStyle = async () => {
    const result = await launchImageLibrary({ mediaType: 'photo' });
    if (result.assets?.[0]?.uri) {
      const uri = result.assets[0].uri;
      setOriginalUri(uri);
      await applyStyle(uri);
    }
  };
  
  return (
    <View>
      <Text>Status: {styleTransfer.isReady ? 'Ready' : 'Loading...'}</Text>
      <Text>Progress: {(styleTransfer.downloadProgress * 100).toFixed(0)}%</Text>
      
      <Button
        title="Pick Image & Apply Style"
        onPress={pickAndStyle}
        disabled={!styleTransfer.isReady}
      />
      
      {styleTransfer.isGenerating && <ActivityIndicator />}
      
      <View style={{ flexDirection: 'row' }}>
        {originalUri && (
          <View style={{ flex: 1 }}>
            <Text>Original:</Text>
            <Image
              source={{ uri: originalUri }}
              style={{ width: 200, height: 200 }}
            />
          </View>
        )}
        
        {styledUri && (
          <View style={{ flex: 1 }}>
            <Text>Styled:</Text>
            <Image
              source={{ uri: styledUri }}
              style={{ width: 200, height: 200 }}
            />
          </View>
        )}
      </View>
    </View>
  );
}

Before/After Comparison

import { useStyleTransfer } from 'react-native-executorch';
import { useState } from 'react';
import Slider from '@react-native-community/slider';

function BeforeAfterComparison() {
  const [originalUri, setOriginalUri] = useState<string | null>(null);
  const [styledUri, setStyledUri] = useState<string | null>(null);
  const [sliderValue, setSliderValue] = useState(0.5);
  
  const styleTransfer = useStyleTransfer({
    model: {
      modelSource: require('./models/mosaic-style.pte'),
    },
  });
  
  return (
    <View>
      <View style={{ position: 'relative', width: 400, height: 400 }}>
        {styledUri && (
          <Image
            source={{ uri: styledUri }}
            style={{
              position: 'absolute',
              width: 400,
              height: 400,
            }}
          />
        )}
        
        {originalUri && (
          <Image
            source={{ uri: originalUri }}
            style={{
              position: 'absolute',
              width: 400,
              height: 400,
              clipPath: `inset(0 ${(1 - sliderValue) * 100}% 0 0)`,
            }}
          />
        )}
      </View>
      
      <Slider
        value={sliderValue}
        onValueChange={setSliderValue}
        minimumValue={0}
        maximumValue={1}
      />
      <Text>Slide to compare</Text>
    </View>
  );
}

Camera with Live Style Preview

import { useStyleTransfer } from 'react-native-executorch';
import { Camera } from 'react-native-vision-camera';
import { useState, useRef } from 'react';

function LiveStyleCamera() {
  const cameraRef = useRef<Camera>(null);
  const [styledImage, setStyledImage] = useState<string | null>(null);
  const [isProcessing, setIsProcessing] = useState(false);
  
  const styleTransfer = useStyleTransfer({
    model: {
      modelSource: 'https://example.com/style.pte',
    },
  });
  
  const captureAndStyle = async () => {
    if (!cameraRef.current || !styleTransfer.isReady || isProcessing) return;
    
    setIsProcessing(true);
    
    try {
      const photo = await cameraRef.current.takePhoto();
      const styled = await styleTransfer.forward(photo.path);
      setStyledImage(styled);
    } catch (error) {
      console.error('Style transfer failed:', error);
    } finally {
      setIsProcessing(false);
    }
  };
  
  return (
    <View style={{ flex: 1 }}>
      <Camera
        ref={cameraRef}
        style={{ flex: 1 }}
        device={/* camera device */}
        isActive={!isProcessing}
      />
      
      {styledImage && (
        <Image
          source={{ uri: styledImage }}
          style={{
            position: 'absolute',
            top: 20,
            right: 20,
            width: 100,
            height: 100,
            borderWidth: 2,
            borderColor: 'white',
          }}
        />
      )}
      
      <Button
        title={isProcessing ? 'Processing...' : 'Capture & Style'}
        onPress={captureAndStyle}
        disabled={!styleTransfer.isReady || isProcessing}
      />
    </View>
  );
}

Batch Style Transfer

import { useStyleTransfer } from 'react-native-executorch';
import { useState } from 'react';

function BatchStyleTransfer() {
  const [images, setImages] = useState<string[]>([]);
  const [processedImages, setProcessedImages] = useState<string[]>([]);
  const [progress, setProgress] = useState(0);
  
  const styleTransfer = useStyleTransfer({
    model: {
      modelSource: require('./models/style.pte'),
    },
  });
  
  const processBatch = async () => {
    if (!styleTransfer.isReady) return;
    
    const results: string[] = [];
    
    for (let i = 0; i < images.length; i++) {
      try {
        const styled = await styleTransfer.forward(images[i]);
        results.push(styled);
        setProgress((i + 1) / images.length);
      } catch (error) {
        console.error(`Failed to process image ${i}:`, error);
      }
    }
    
    setProcessedImages(results);
    setProgress(1);
  };
  
  return (
    <View>
      <Text>Images: {images.length}</Text>
      <Text>Progress: {(progress * 100).toFixed(0)}%</Text>
      
      <Button
        title="Process All"
        onPress={processBatch}
        disabled={!styleTransfer.isReady || images.length === 0}
      />
      
      <ScrollView horizontal>
        {processedImages.map((uri, idx) => (
          <Image
            key={idx}
            source={{ uri }}
            style={{ width: 150, height: 150, margin: 5 }}
          />
        ))}
      </ScrollView>
    </View>
  );
}

Save Styled Image

import { useStyleTransfer } from 'react-native-executorch';
import { useState } from 'react';
import RNFS from 'react-native-fs';
import Share from 'react-native-share';

function StyleTransferWithSave() {
  const [styledUri, setStyledUri] = useState<string | null>(null);
  
  const styleTransfer = useStyleTransfer({
    model: {
      modelSource: 'https://example.com/style.pte',
    },
  });
  
  const applyStyleAndSave = async (imageUri: string) => {
    if (!styleTransfer.isReady) return;
    
    try {
      const result = await styleTransfer.forward(imageUri);
      setStyledUri(result);
      
      // Save to device
      const filename = `styled_${Date.now()}.jpg`;
      const destPath = `${RNFS.DocumentDirectoryPath}/${filename}`;
      
      if (result.startsWith('data:')) {
        // Base64 image
        const base64Data = result.split(',')[1];
        await RNFS.writeFile(destPath, base64Data, 'base64');
      } else {
        // File URI
        await RNFS.copyFile(result, destPath);
      }
      
      console.log('Saved to:', destPath);
      return destPath;
    } catch (error) {
      console.error('Style transfer or save failed:', error);
    }
  };
  
  const shareStyled = async () => {
    if (!styledUri) return;
    
    try {
      await Share.open({
        url: styledUri,
        type: 'image/jpeg',
      });
    } catch (error) {
      console.error('Share failed:', error);
    }
  };
  
  return (
    <View>
      {styledUri && (
        <>
          <Image source={{ uri: styledUri }} style={{ width: 300, height: 300 }} />
          <Button title="Share" onPress={shareStyled} />
        </>
      )}
    </View>
  );
}

Performance Monitoring

import { useStyleTransfer } from 'react-native-executorch';
import { useState } from 'react';

function StyleTransferWithMetrics() {
  const [processingTime, setProcessingTime] = useState<number | null>(null);
  const [styledUri, setStyledUri] = useState<string | null>(null);
  
  const styleTransfer = useStyleTransfer({
    model: {
      modelSource: require('./models/style.pte'),
    },
  });
  
  const measureStyleTransfer = async (imageUri: string) => {
    if (!styleTransfer.isReady) return;
    
    const startTime = Date.now();
    
    try {
      const result = await styleTransfer.forward(imageUri);
      const endTime = Date.now();
      
      setStyledUri(result);
      setProcessingTime(endTime - startTime);
      
      console.log(`Style transfer completed in ${endTime - startTime}ms`);
    } catch (error) {
      console.error('Style transfer failed:', error);
    }
  };
  
  return (
    <View>
      {processingTime && (
        <Text>Processing time: {processingTime}ms</Text>
      )}
      
      {styledUri && (
        <Image source={{ uri: styledUri }} style={{ width: 300, height: 300 }} />
      )}
    </View>
  );
}

Notes

The model automatically loads when the hook mounts unless preventLoad is set to true.
Style transfer can be computationally intensive and may take several seconds depending on image size and device capabilities.
For best results, consider resizing large images before applying style transfer to improve processing speed.

Common Use Cases

  1. Photo Filters: Apply artistic filters to user photos
  2. Creative Apps: Build photo editing apps with style effects
  3. Art Generation: Create artistic variations of images
  4. Social Media: Add unique visual effects to shared content
  5. Content Creation: Generate stylized content for marketing

Performance Considerations

  • Image Size: Larger images take longer to process
  • Model Complexity: Different style models have varying performance
  • Device Capability: Performance varies by device hardware
  • Memory Usage: Monitor memory during batch processing

See Also

Build docs developers (and LLMs) love