|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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';
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
const generateTryOnBase64 = async () => {
|
|
|
if (!personImage || !clothingImage) {
|
|
|
Alert.alert('Error', 'Please select both person and clothing images');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
setLoading(true);
|
|
|
setResultImage(null);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const personBase64 = await FileSystem.readAsStringAsync(personImage, {
|
|
|
encoding: FileSystem.EncodingType.Base64,
|
|
|
});
|
|
|
|
|
|
const clothingBase64 = await FileSystem.readAsStringAsync(clothingImage, {
|
|
|
encoding: FileSystem.EncodingType.Base64,
|
|
|
});
|
|
|
|
|
|
|
|
|
const formData = new FormData();
|
|
|
formData.append('person_image_base64', personBase64);
|
|
|
formData.append('clothing_image_base64', clothingBase64);
|
|
|
formData.append('num_steps', '40');
|
|
|
|
|
|
|
|
|
const response = await axios.post(`${API_URL}/tryon-base64`, 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);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
const generateTryOnFiles = async () => {
|
|
|
if (!personImage || !clothingImage) {
|
|
|
Alert.alert('Error', 'Please select both person and clothing images');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
setLoading(true);
|
|
|
setResultImage(null);
|
|
|
|
|
|
try {
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
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;
|
|
|
|