Spaces:
Running
on
Zero
Running
on
Zero
| /** | |
| * React Native Example for Virtual Try-On API | |
| * | |
| * Install required packages: | |
| * npm install expo-image-picker expo-file-system axios | |
| */ | |
| import React, { useState } from 'react'; | |
| import { | |
| View, | |
| Text, | |
| Image, | |
| TouchableOpacity, | |
| ActivityIndicator, | |
| StyleSheet, | |
| Alert, | |
| } from 'react-native'; | |
| import * as ImagePicker from 'expo-image-picker'; | |
| import * as FileSystem from 'expo-file-system'; | |
| import axios from 'axios'; | |
| // Replace with your Hugging Face Space URL after deployment | |
| const API_URL = 'https://your-username-virtual-tryon-api.hf.space'; | |
| const VirtualTryOnApp = () => { | |
| const [personImage, setPersonImage] = useState(null); | |
| const [clothingImage, setClothingImage] = useState(null); | |
| const [resultImage, setResultImage] = useState(null); | |
| const [loading, setLoading] = useState(false); | |
| const [processingTime, setProcessingTime] = useState(null); | |
| // Pick person image | |
| const pickPersonImage = async () => { | |
| const result = await ImagePicker.launchImageLibraryAsync({ | |
| mediaTypes: ImagePicker.MediaTypeOptions.Images, | |
| allowsEditing: true, | |
| aspect: [1, 1], | |
| quality: 1, | |
| }); | |
| if (!result.canceled) { | |
| setPersonImage(result.assets[0].uri); | |
| } | |
| }; | |
| // Pick clothing image | |
| const pickClothingImage = async () => { | |
| const result = await ImagePicker.launchImageLibraryAsync({ | |
| mediaTypes: ImagePicker.MediaTypeOptions.Images, | |
| allowsEditing: true, | |
| aspect: [1, 1], | |
| quality: 1, | |
| }); | |
| if (!result.canceled) { | |
| setClothingImage(result.assets[0].uri); | |
| } | |
| }; | |
| // Method 1: Using Base64 (Recommended for React Native) | |
| const generateTryOnBase64 = async () => { | |
| if (!personImage || !clothingImage) { | |
| Alert.alert('Error', 'Please select both person and clothing images'); | |
| return; | |
| } | |
| setLoading(true); | |
| setResultImage(null); | |
| try { | |
| // Convert images to base64 | |
| const personBase64 = await FileSystem.readAsStringAsync(personImage, { | |
| encoding: FileSystem.EncodingType.Base64, | |
| }); | |
| const clothingBase64 = await FileSystem.readAsStringAsync(clothingImage, { | |
| encoding: FileSystem.EncodingType.Base64, | |
| }); | |
| // Create form data | |
| const formData = new FormData(); | |
| formData.append('person_image_base64', personBase64); | |
| formData.append('clothing_image_base64', clothingBase64); | |
| formData.append('num_steps', '40'); // Adjust for speed/quality trade-off | |
| // Make API request | |
| const response = await axios.post(`${API_URL}/tryon-base64`, formData, { | |
| headers: { | |
| 'Content-Type': 'multipart/form-data', | |
| }, | |
| timeout: 120000, // 2 minutes timeout | |
| }); | |
| if (response.data.success) { | |
| const imageUri = `data:image/png;base64,${response.data.image}`; | |
| setResultImage(imageUri); | |
| setProcessingTime(response.data.processing_time); | |
| Alert.alert('Success', `Generated in ${response.data.processing_time.toFixed(1)}s`); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error); | |
| Alert.alert('Error', error.message || 'Failed to generate try-on'); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| // Method 2: Using File Upload | |
| const generateTryOnFiles = async () => { | |
| if (!personImage || !clothingImage) { | |
| Alert.alert('Error', 'Please select both person and clothing images'); | |
| return; | |
| } | |
| setLoading(true); | |
| setResultImage(null); | |
| try { | |
| // Create form data | |
| const formData = new FormData(); | |
| formData.append('person_image', { | |
| uri: personImage, | |
| type: 'image/jpeg', | |
| name: 'person.jpg', | |
| }); | |
| formData.append('clothing_image', { | |
| uri: clothingImage, | |
| type: 'image/jpeg', | |
| name: 'clothing.jpg', | |
| }); | |
| formData.append('return_format', 'base64'); | |
| formData.append('num_steps', '40'); | |
| // Make API request | |
| const response = await axios.post(`${API_URL}/tryon`, formData, { | |
| headers: { | |
| 'Content-Type': 'multipart/form-data', | |
| }, | |
| timeout: 120000, | |
| }); | |
| if (response.data.success) { | |
| const imageUri = `data:image/png;base64,${response.data.image}`; | |
| setResultImage(imageUri); | |
| setProcessingTime(response.data.processing_time); | |
| Alert.alert('Success', `Generated in ${response.data.processing_time.toFixed(1)}s`); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error); | |
| Alert.alert('Error', error.message || 'Failed to generate try-on'); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| return ( | |
| <View style={styles.container}> | |
| <Text style={styles.title}>Virtual Try-On</Text> | |
| <View style={styles.imageRow}> | |
| <TouchableOpacity style={styles.imageContainer} onPress={pickPersonImage}> | |
| {personImage ? ( | |
| <Image source={{ uri: personImage }} style={styles.image} /> | |
| ) : ( | |
| <View style={styles.placeholder}> | |
| <Text>Select Person Image</Text> | |
| </View> | |
| )} | |
| </TouchableOpacity> | |
| <TouchableOpacity style={styles.imageContainer} onPress={pickClothingImage}> | |
| {clothingImage ? ( | |
| <Image source={{ uri: clothingImage }} style={styles.image} /> | |
| ) : ( | |
| <View style={styles.placeholder}> | |
| <Text>Select Clothing Image</Text> | |
| </View> | |
| )} | |
| </TouchableOpacity> | |
| </View> | |
| <TouchableOpacity | |
| style={[styles.button, loading && styles.buttonDisabled]} | |
| onPress={generateTryOnBase64} | |
| disabled={loading} | |
| > | |
| {loading ? ( | |
| <ActivityIndicator color="#fff" /> | |
| ) : ( | |
| <Text style={styles.buttonText}>Generate Try-On</Text> | |
| )} | |
| </TouchableOpacity> | |
| {processingTime && ( | |
| <Text style={styles.info}>Processing time: {processingTime.toFixed(1)}s</Text> | |
| )} | |
| {resultImage && ( | |
| <View style={styles.resultContainer}> | |
| <Text style={styles.subtitle}>Result:</Text> | |
| <Image source={{ uri: resultImage }} style={styles.resultImage} /> | |
| </View> | |
| )} | |
| </View> | |
| ); | |
| }; | |
| const styles = StyleSheet.create({ | |
| container: { | |
| flex: 1, | |
| padding: 20, | |
| backgroundColor: '#f5f5f5', | |
| }, | |
| title: { | |
| fontSize: 24, | |
| fontWeight: 'bold', | |
| textAlign: 'center', | |
| marginVertical: 20, | |
| }, | |
| imageRow: { | |
| flexDirection: 'row', | |
| justifyContent: 'space-between', | |
| marginBottom: 20, | |
| }, | |
| imageContainer: { | |
| width: '48%', | |
| aspectRatio: 1, | |
| }, | |
| image: { | |
| width: '100%', | |
| height: '100%', | |
| borderRadius: 10, | |
| }, | |
| placeholder: { | |
| width: '100%', | |
| height: '100%', | |
| backgroundColor: '#ddd', | |
| borderRadius: 10, | |
| justifyContent: 'center', | |
| alignItems: 'center', | |
| }, | |
| button: { | |
| backgroundColor: '#007AFF', | |
| padding: 15, | |
| borderRadius: 10, | |
| alignItems: 'center', | |
| marginVertical: 10, | |
| }, | |
| buttonDisabled: { | |
| backgroundColor: '#999', | |
| }, | |
| buttonText: { | |
| color: '#fff', | |
| fontSize: 16, | |
| fontWeight: 'bold', | |
| }, | |
| info: { | |
| textAlign: 'center', | |
| color: '#666', | |
| marginVertical: 10, | |
| }, | |
| resultContainer: { | |
| marginTop: 20, | |
| alignItems: 'center', | |
| }, | |
| subtitle: { | |
| fontSize: 18, | |
| fontWeight: 'bold', | |
| marginBottom: 10, | |
| }, | |
| resultImage: { | |
| width: 300, | |
| height: 300, | |
| borderRadius: 10, | |
| }, | |
| }); | |
| export default VirtualTryOnApp; | |