V-try-on-v3 / templates /index.html
ritz26's picture
improve the model and fix girls try on issue
019b718
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Virtual Fashion Try-On</title>
<script src="https://cdn.tailwindcss.com"></script>
<!-- Cropper.js CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css" rel="stylesheet">
</head>
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center py-10">
<h1 class="text-4xl font-bold text-blue-400 mb-10">Virtual Fashion Try-On</h1>
<div class="flex flex-col md:flex-row gap-10 w-full max-w-6xl">
<!-- LEFT: Input Form -->
<form id="tryon-form" action="/" method="post" enctype="multipart/form-data" class="w-full md:w-1/2 bg-gray-800 rounded-2xl shadow-lg p-8 space-y-6">
<div class="grid grid-cols-1 gap-8">
<!-- Person Image Upload -->
<div>
<div class="flex justify-between items-center mb-2">
<h2 class="text-lg font-semibold">Upload your photo</h2>
{% if cached_person %}
<button type="button" onclick="changePerson()" class="bg-yellow-500 hover:bg-yellow-600 text-white text-sm px-3 py-1 rounded-lg transition">
Change Person
</button>
{% endif %}
</div>
{% if cached_person %}
<!-- Show cached person image -->
<div class="border-2 border-green-500 rounded-xl p-4 bg-green-900/20">
<div class="flex flex-col items-center">
<div class="flex items-center gap-2 mb-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
<p class="text-green-400 text-sm font-medium">Person image cached</p>
</div>
<img src="/uploads/person.jpg" alt="Cached Person" class="max-h-32 rounded-lg border border-gray-600">
<p class="text-gray-400 text-xs mt-2">Coordinates saved - no need to re-upload!</p>
</div>
</div>
{% else %}
<label for="person_image" class="flex flex-col items-center justify-center border-2 border-dashed border-gray-600 rounded-xl p-6 hover:bg-gray-700 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-gray-400 mb-2" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M7 16v4m0 0h10m-10 0v-4m0 0h10m-10 0V5m0 0h10m-10 0H5m14 0h-2" />
</svg>
<p class="text-gray-400">Drag & drop or click to upload</p>
<input id="person_image" type="file" name="person_image" class="hidden"
onchange="showFileName('person_image', 'person_filename', 'person_preview')">
</label>
<p id="person_filename" class="text-green-400 text-sm mt-2 text-center"></p>
<div class="mt-3 flex justify-center">
<img id="person_preview" class="hidden max-h-32 rounded-lg border border-gray-600">
</div>
{% endif %}
</div>
<!-- Gender Selection -->
<div>
<h2 class="text-lg font-semibold mb-3">Select Gender</h2>
<div class="flex gap-4">
<label class="flex items-center cursor-pointer">
<input type="radio" name="gender" value="male" checked class="sr-only">
<div class="gender-option flex items-center gap-2 px-4 py-2 rounded-lg border-2 border-blue-500 bg-blue-500/20 text-blue-400">
<!-- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg> -->
<span>Male</span>
</div>
</label>
<label class="flex items-center cursor-pointer">
<input type="radio" name="gender" value="female" class="sr-only">
<div class="gender-option flex items-center gap-2 px-4 py-2 rounded-lg border-2 border-gray-600 text-gray-400">
<!-- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg> -->
<span>Female</span>
</div>
</label>
</div>
<!-- <p class="text-xs text-gray-400 mt-2">
<span class="text-pink-400">Female mode:</span> Uses chest-to-hip detection to avoid hair on shoulders
</p> -->
</div>
<!-- Garment Image Upload with Cropper -->
<div>
<div class="flex justify-between items-center mb-2">
<h2 class="text-lg font-semibold">Upload garment image</h2>
{% if cached_person %}
<div class="flex items-center gap-1 text-xs text-green-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<span>Fast mode</span>
</div>
{% endif %}
</div>
<label for="tshirt_image" class="flex flex-col items-center justify-center border-2 border-dashed border-gray-600 rounded-xl p-6 hover:bg-gray-700 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-gray-400 mb-2" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M7 16v4m0 0h10m-10 0v-4m0 0h10m-10 0V5m0 0h10m-10 0H5m14 0h-2" />
</svg>
<p class="text-gray-400">Drag & drop or click to upload</p>
<input id="tshirt_image" type="file" name="tshirt_image" class="hidden" required>
</label>
<p id="tshirt_filename" class="text-green-400 text-sm mt-2 text-center"></p>
<!-- Cropping Container -->
<div class="mt-3 flex justify-center">
<img id="tshirt_preview" class="hidden max-h-64 rounded-lg border border-gray-600">
</div>
</div>
</div>
<!-- Submit Button -->
<div class="flex justify-center">
<button type="submit" class="bg-pink-500 hover:bg-pink-600 text-white font-semibold py-3 px-8 rounded-xl shadow-md transition">
🚀 Perform Virtual Try-On
</button>
</div>
</form>
<!-- RIGHT: Output -->
<div class="w-full md:w-1/2 bg-gray-800 rounded-2xl shadow-lg p-8 flex items-center justify-center text-center">
{% if result_img %}
<div>
<h2 class="text-2xl font-bold mb-6 text-center">🎉 Your Virtual Try-On Result</h2>
{% if processing_time %}
<div class="text-center mb-4">
<span class="bg-blue-500 text-white px-3 py-1 rounded-full text-sm">
⚡ Processed in {{ processing_time }}
</span>
</div>
{% endif %}
<div class="flex justify-center mb-6">
<img id="result-image" src="{{ result_img }}" alt="Result Image" class="rounded-xl"
onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
<div style="display:none;" class="text-red-500 p-4 border border-red-500 rounded-xl">
<p>❌ Image failed to load</p>
<p class="text-sm">Please try again or check the server logs</p>
<p class="text-xs">Image URL: {{ result_img }}</p>
</div>
</div>
<div class="flex justify-center">
<button onclick="downloadImage()" class="bg-green-500 hover:bg-green-600 text-white font-semibold py-3 px-6 rounded-xl shadow-md transition flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Download Image
</button>
</div>
</div>
{% else %}
<div id="output-container">
<div id="loading-spinner" style="display: none;" class="flex flex-col items-center">
<svg class="animate-spin h-16 w-16 text-pink-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014
12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<p class="mt-4 text-lg">Processing... Please wait.</p>
</div>
<div id="placeholder-text">
<h2 class="text-2xl font-bold text-center text-gray-500">Your result will appear here</h2>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Global Full-screen Loader Overlay -->
<div id="global-loader" style="display:none;" class="fixed inset-0 z-50 bg-black/70 flex items-center justify-center">
<div class="flex flex-col items-center">
<svg class="animate-spin h-16 w-16 text-pink-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<p class="mt-4 text-lg">Processing... Please wait.</p>
</div>
</div>
<!-- Cropper.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
<script>
let cropper;
// Global loader on every submit
document.getElementById('tryon-form').addEventListener('submit', function(e) {
const overlay = document.getElementById('global-loader');
if (overlay) overlay.style.display = 'flex';
// If garment cropper is active → replace original file with cropped version
if (cropper) {
e.preventDefault();
cropper.getCroppedCanvas().toBlob((blob) => {
const file = new File([blob], "cropped_garment.png", { type: "image/png" });
// Replace original input
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
document.getElementById('tshirt_image').files = dataTransfer.files;
// Now submit form
e.target.submit();
});
return;
}
});
// Change Person: full refresh via POST + server redirect
function changePerson() {
const form = document.createElement('form');
form.method = 'POST';
form.action = '/change_person';
document.body.appendChild(form);
form.submit();
}
// Gender selection handling
document.querySelectorAll('input[name="gender"]').forEach(radio => {
radio.addEventListener('change', function() {
// Update visual styling
document.querySelectorAll('.gender-option').forEach(option => {
option.classList.remove('border-blue-500', 'bg-blue-500/20', 'text-blue-400', 'border-pink-500', 'bg-pink-500/20', 'text-pink-400');
option.classList.add('border-gray-600', 'text-gray-400');
});
const selectedOption = this.parentElement.querySelector('.gender-option');
if (this.value === 'male') {
selectedOption.classList.remove('border-gray-600', 'text-gray-400');
selectedOption.classList.add('border-blue-500', 'bg-blue-500/20', 'text-blue-400');
} else {
selectedOption.classList.remove('border-gray-600', 'text-gray-400');
selectedOption.classList.add('border-pink-500', 'bg-pink-500/20', 'text-pink-400');
}
});
});
// Show file name + preview (person only)
function showFileName(inputId, filenameId, previewId) {
const input = document.getElementById(inputId);
const filename = document.getElementById(filenameId);
const preview = document.getElementById(previewId);
if (input.files.length > 0) {
const file = input.files[0];
filename.textContent = "✔️ " + file.name + " uploaded";
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.classList.remove("hidden");
};
reader.readAsDataURL(file);
} else {
filename.textContent = "";
preview.classList.add("hidden");
}
}
// Garment upload with Cropper
document.getElementById('tshirt_image').addEventListener('change', function() {
const input = this;
const filename = document.getElementById('tshirt_filename');
const preview = document.getElementById('tshirt_preview');
if (input.files.length > 0) {
const file = input.files[0];
filename.textContent = "✔️ " + file.name + " uploaded";
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.classList.remove("hidden");
// Destroy old cropper if exists
if (cropper) {
cropper.destroy();
}
// Initialize Cropper.js with square ratio
cropper = new Cropper(preview, {
aspectRatio: 1,
viewMode: 1,
autoCropArea: 1,
});
};
reader.readAsDataURL(file);
} else {
filename.textContent = "";
preview.classList.add("hidden");
if (cropper) {
cropper.destroy();
cropper = null;
}
}
});
// Download result
function downloadImage() {
const img = document.getElementById('result-image');
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.drawImage(img, 0, 0);
canvas.toBlob(function(blob) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'virtual-try-on-result.png';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 'image/png');
}
</script>
</body>
</html>