CIHIRIS's picture
Add 3 files
2c33eda verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trellis3D - 2D to 3D Conversion</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/rembg.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
#preview-container {
width: 100%;
height: 400px;
background-color: #f0f0f0;
border-radius: 0.5rem;
overflow: hidden;
position: relative;
}
.dark #preview-container {
background-color: #2d3748;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
z-index: 10;
}
.progress-bar {
width: 80%;
height: 8px;
background-color: #4a5568;
border-radius: 4px;
margin-top: 1rem;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: #4299e1;
width: 0%;
transition: width 0.3s ease;
}
.dropzone {
border: 2px dashed #cbd5e0;
border-radius: 0.5rem;
padding: 2rem;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.dark .dropzone {
border-color: #4a5568;
}
.dropzone:hover {
border-color: #4299e1;
background-color: rgba(66, 153, 225, 0.05);
}
.dark .dropzone:hover {
background-color: rgba(66, 153, 225, 0.1);
}
.dropzone.active {
border-color: #4299e1;
background-color: rgba(66, 153, 225, 0.1);
}
.dark .dropzone.active {
background-color: rgba(66, 153, 225, 0.2);
}
.model-view-toggle {
display: flex;
justify-content: center;
margin-top: 1rem;
}
.view-btn {
padding: 0.5rem 1rem;
background-color: #e2e8f0;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.dark .view-btn {
background-color: #4a5568;
color: white;
}
.view-btn:first-child {
border-radius: 0.25rem 0 0 0.25rem;
}
.view-btn:last-child {
border-radius: 0 0.25rem 0.25rem 0;
}
.view-btn.active {
background-color: #4299e1;
color: white;
}
.dark .view-btn.active {
background-color: #3182ce;
}
#image-preview {
max-width: 100%;
max-height: 200px;
border-radius: 0.25rem;
margin-bottom: 1rem;
}
#depth-map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
display: none;
}
</style>
</head>
<body class="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200">
<div class="min-h-screen">
<header class="bg-white dark:bg-gray-800 shadow-md">
<div class="container mx-auto px-4 py-6 flex justify-between items-center">
<div class="flex items-center space-x-2">
<div class="w-8 h-8 bg-blue-500 rounded-md flex items-center justify-center">
<i class="fas fa-cube text-white"></i>
</div>
<h1 class="text-xl font-bold">Trellis3D</h1>
</div>
<div class="flex items-center space-x-4">
<button id="theme-toggle" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
<i class="fas fa-moon dark:hidden"></i>
<i class="fas fa-sun hidden dark:block"></i>
</button>
<button class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md transition-colors">
<i class="fas fa-user mr-2"></i>
Sign In
</button>
</div>
</div>
</header>
<main class="container mx-auto px-4 py-8">
<div class="max-w-4xl mx-auto">
<div class="text-center mb-8">
<h2 class="text-3xl font-bold mb-2">2D to 3D Conversion</h2>
<p class="text-gray-600 dark:text-gray-400">Transform your 2D images into stunning 3D models with our AI-powered technology</p>
</div>
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-8">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-semibold mb-4">Upload Image</h3>
<div id="dropzone" class="dropzone">
<div class="flex flex-col items-center justify-center">
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-4"></i>
<p class="mb-2">Drag & drop your image here</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">or</p>
<label for="file-upload" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md cursor-pointer transition-colors">
<i class="fas fa-folder-open mr-2"></i>
Browse Files
</label>
<input id="file-upload" type="file" accept="image/*" class="hidden">
</div>
</div>
<div class="mt-4">
<div class="flex items-center mb-2">
<input id="remove-bg" type="checkbox" class="rounded text-blue-500 focus:ring-blue-500" checked>
<label for="remove-bg" class="ml-2">Remove background</label>
</div>
<div class="mb-4">
<label class="block mb-2">Model Quality</label>
<div class="flex space-x-2">
<button class="quality-btn px-3 py-1 rounded-md border border-gray-300 dark:border-gray-600" data-quality="low">Low</button>
<button class="quality-btn px-3 py-1 rounded-md border border-gray-300 dark:border-gray-600 active bg-blue-500 text-white border-blue-500" data-quality="medium">Medium</button>
<button class="quality-btn px-3 py-1 rounded-md border border-gray-300 dark:border-gray-600" data-quality="high">High</button>
</div>
</div>
<button id="convert-btn" class="w-full py-3 bg-blue-500 hover:bg-blue-600 text-white rounded-md font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fas fa-magic mr-2"></i>
Convert to 3D
</button>
</div>
</div>
<div>
<h3 class="text-lg font-semibold mb-4">3D Preview</h3>
<div id="preview-container">
<div id="loading-overlay" class="loading-overlay hidden">
<i class="fas fa-spinner fa-spin text-3xl mb-2"></i>
<p id="loading-text">Processing your image...</p>
<div class="progress-bar">
<div id="progress-fill" class="progress-fill"></div>
</div>
</div>
<div id="preview-placeholder" class="flex flex-col items-center justify-center h-full">
<i class="fas fa-cube text-4xl text-gray-400 mb-4"></i>
<p class="text-gray-500 dark:text-gray-400">Your 3D model will appear here</p>
</div>
<canvas id="preview-canvas" class="hidden"></canvas>
<img id="depth-map" alt="Depth map preview">
</div>
<div id="view-controls" class="hidden">
<div class="model-view-toggle">
<button class="view-btn active" data-view="textured">
<i class="fas fa-paint-brush mr-2"></i>
Textured
</button>
<button class="view-btn" data-view="white">
<i class="fas fa-square mr-2"></i>
White
</button>
<button class="view-btn" data-view="wireframe">
<i class="fas fa-border-none mr-2"></i>
Wireframe
</button>
</div>
<div class="flex justify-between mt-4">
<button id="download-btn" class="px-4 py-2 bg-green-500 hover:bg-green-600 text-white rounded-md">
<i class="fas fa-download mr-2"></i>
Download
</button>
<button id="reset-btn" class="px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md">
<i class="fas fa-redo mr-2"></i>
Reset
</button>
</div>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h3 class="text-lg font-semibold mb-4">How It Works</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4">
<div class="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center mb-3 mx-auto">
<i class="fas fa-upload text-white"></i>
</div>
<h4 class="font-medium text-center mb-2">1. Upload Image</h4>
<p class="text-sm text-gray-600 dark:text-gray-300 text-center">Upload any 2D image you want to convert to 3D</p>
</div>
<div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4">
<div class="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center mb-3 mx-auto">
<i class="fas fa-robot text-white"></i>
</div>
<h4 class="font-medium text-center mb-2">2. AI Processing</h4>
<p class="text-sm text-gray-600 dark:text-gray-300 text-center">Our AI analyzes the image and creates a depth map</p>
</div>
<div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4">
<div class="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center mb-3 mx-auto">
<i class="fas fa-cube text-white"></i>
</div>
<h4 class="font-medium text-center mb-2">3. 3D Model</h4>
<p class="text-sm text-gray-600 dark:text-gray-300 text-center">Download your 3D model in various formats</p>
</div>
</div>
</div>
</div>
</main>
<footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-12">
<div class="container mx-auto px-4 py-6">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center space-x-2 mb-4 md:mb-0">
<div class="w-6 h-6 bg-blue-500 rounded-md flex items-center justify-center">
<i class="fas fa-cube text-white text-xs"></i>
</div>
<span class="font-medium">Trellis3D</span>
</div>
<div class="flex space-x-6">
<a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Terms</a>
<a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Privacy</a>
<a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Help</a>
</div>
</div>
<div class="mt-4 text-center md:text-left text-sm text-gray-500 dark:text-gray-400">
© 2023 Trellis3D. All rights reserved.
</div>
</div>
</footer>
</div>
<script>
// Theme toggle
const themeToggle = document.getElementById('theme-toggle');
const html = document.documentElement;
if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
html.classList.add('dark');
}
themeToggle.addEventListener('click', () => {
html.classList.toggle('dark');
localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light');
});
// File upload and dropzone
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('file-upload');
const convertBtn = document.getElementById('convert-btn');
let selectedFile = null;
let processedImage = null;
dropzone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
if (e.target.files.length) {
handleFileSelection(e.target.files[0]);
}
});
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropzone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropzone.classList.add('active');
}
function unhighlight() {
dropzone.classList.remove('active');
}
dropzone.addEventListener('drop', (e) => {
const dt = e.dataTransfer;
const file = dt.files[0];
if (file && file.type.match('image.*')) {
handleFileSelection(file);
}
});
function handleFileSelection(file) {
selectedFile = file;
convertBtn.disabled = false;
// Show preview of selected image
const reader = new FileReader();
reader.onload = (e) => {
dropzone.innerHTML = `
<div class="flex flex-col items-center">
<img src="${e.target.result}" id="image-preview" class="rounded-md mb-2" alt="Preview">
<p class="text-sm text-gray-600 dark:text-gray-400">${file.name}</p>
</div>
`;
};
reader.readAsDataURL(file);
}
// Model quality selection
const qualityBtns = document.querySelectorAll('.quality-btn');
let selectedQuality = 'medium';
qualityBtns.forEach(btn => {
btn.addEventListener('click', () => {
qualityBtns.forEach(b => b.classList.remove('active', 'bg-blue-500', 'text-white', 'border-blue-500'));
btn.classList.add('active', 'bg-blue-500', 'text-white', 'border-blue-500');
selectedQuality = btn.dataset.quality;
});
});
// 3D Preview setup
const previewContainer = document.getElementById('preview-container');
const previewCanvas = document.getElementById('preview-canvas');
const previewPlaceholder = document.getElementById('preview-placeholder');
const loadingOverlay = document.getElementById('loading-overlay');
const loadingText = document.getElementById('loading-text');
const progressFill = document.getElementById('progress-fill');
const viewControls = document.getElementById('view-controls');
const depthMap = document.getElementById('depth-map');
let scene, camera, renderer, controls, model;
function initThreeJS() {
const width = previewContainer.clientWidth;
const height = previewContainer.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
camera.position.z = 5;
renderer = new THREE.WebGLRenderer({
canvas: previewCanvas,
antialias: true,
alpha: true
});
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
// Add lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Add a simple grid helper
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);
animate();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
const width = previewContainer.clientWidth;
const height = previewContainer.clientHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
});
// View mode toggles
const viewBtns = document.querySelectorAll('.view-btn');
viewBtns.forEach(btn => {
btn.addEventListener('click', () => {
viewBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
if (model) {
const viewMode = btn.dataset.view;
if (viewMode === 'textured') {
model.traverse(child => {
if (child.isMesh) {
child.material = child.userData.originalMaterial;
child.material.wireframe = false;
}
});
} else if (viewMode === 'white') {
const whiteMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
model.traverse(child => {
if (child.isMesh) {
child.material = whiteMaterial;
child.material.wireframe = false;
}
});
} else if (viewMode === 'wireframe') {
model.traverse(child => {
if (child.isMesh) {
child.material.wireframe = true;
}
});
}
}
});
});
// Convert button handler
convertBtn.addEventListener('click', async () => {
if (!selectedFile) return;
// Show loading state
loadingOverlay.classList.remove('hidden');
previewPlaceholder.classList.add('hidden');
previewCanvas.classList.remove('hidden');
// Initialize Three.js if not already done
if (!renderer) {
initThreeJS();
}
// Process the image
await processImage();
});
async function processImage() {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 10;
if (progress > 100) progress = 100;
progressFill.style.width = `${progress}%`;
loadingText.textContent = getLoadingMessage(progress);
if (progress === 100) {
clearInterval(interval);
setTimeout(showResult, 500);
}
}, 300);
// Load the image
const image = await loadImage(selectedFile);
// Remove background if selected
const removeBg = document.getElementById('remove-bg').checked;
if (removeBg) {
try {
// In a real app, you would use REMBG here
// For this demo, we'll simulate background removal with a simple effect
processedImage = await simulateRemoveBackground(image);
} catch (error) {
console.error("Background removal failed:", error);
processedImage = image; // Fallback to original image
}
} else {
processedImage = image;
}
// Generate depth map (simulated)
const depthMapUrl = await generateDepthMap(processedImage);
depthMap.src = depthMapUrl;
}
function loadImage(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => resolve(img);
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}
function simulateRemoveBackground(image) {
return new Promise((resolve) => {
// In a real app, you would use REMBG here
// This is just a simulation with a canvas effect
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
// Simple background removal simulation
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Make a simple assumption that edges are background
for (let i = 0; i < data.length; i += 4) {
const x = (i / 4) % canvas.width;
const y = Math.floor((i / 4) / canvas.width);
// If pixel is near the edge, make it transparent
if (x < 10 || x > canvas.width - 10 || y < 10 || y > canvas.height - 10) {
data[i + 3] = 0; // Set alpha to 0
}
}
ctx.putImageData(imageData, 0, 0);
const result = new Image();
result.onload = () => resolve(result);
result.src = canvas.toDataURL();
});
}
function generateDepthMap(image) {
return new Promise((resolve) => {
// In a real app, you would use a proper depth estimation model
// This is just a simulation with a canvas effect
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
// Create a simple depth map simulation
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const depthData = new Uint8ClampedArray(canvas.width * canvas.height * 4);
for (let i = 0; i < data.length; i += 4) {
const x = (i / 4) % canvas.width;
const y = Math.floor((i / 4) / canvas.width);
// Simple depth simulation based on brightness and position
const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3;
const depth = (brightness / 255) * 0.5 + (1 - (y / canvas.height)) * 0.5;
const depthValue = Math.floor(depth * 255);
depthData[i] = depthValue;
depthData[i + 1] = depthValue;
depthData[i + 2] = depthValue;
depthData[i + 3] = data[i + 3]; // Preserve alpha
}
const depthImageData = new ImageData(new Uint8ClampedArray(depthData), canvas.width, canvas.height);
ctx.putImageData(depthImageData, 0, 0);
resolve(canvas.toDataURL());
});
}
function getLoadingMessage(progress) {
if (progress < 30) return "Analyzing image...";
if (progress < 60) return "Generating depth map...";
if (progress < 90) return "Creating 3D model...";
return "Finalizing...";
}
function showResult() {
loadingOverlay.classList.add('hidden');
viewControls.classList.remove('hidden');
// Clear previous model if exists
if (model) {
scene.remove(model);
}
// Create a 3D model from the processed image
create3DModelFromImage(processedImage);
}
function create3DModelFromImage(image) {
const textureLoader = new THREE.TextureLoader();
textureLoader.load(image.src, (texture) => {
// Create a plane geometry with the image dimensions
const aspectRatio = image.width / image.height;
const width = 3;
const height = width / aspectRatio;
const geometry = new THREE.PlaneGeometry(width, height, 64, 64);
// Apply the depth map to create a 3D effect
applyDepthMapToGeometry(geometry, image);
// Create material with the image texture
const material = new THREE.MeshStandardMaterial({
map: texture,
side: THREE.DoubleSide,
transparent: true
});
model = new THREE.Mesh(geometry, material);
// Store original material for textured view
model.traverse(child => {
if (child.isMesh) {
child.userData.originalMaterial = child.material;
}
});
scene.add(model);
// Add some rotation animation
function animateRotation() {
requestAnimationFrame(animateRotation);
model.rotation.y += 0.005;
}
animateRotation();
});
}
function applyDepthMapToGeometry(geometry, image) {
// In a real app, you would use the actual depth map
// This is a simplified version that creates some depth variation
const positions = geometry.attributes.position.array;
const depthIntensity = selectedQuality === 'low' ? 0.3 :
selectedQuality === 'medium' ? 0.6 : 1.0;
for (let i = 0; i < positions.length; i += 3) {
const x = positions[i];
const y = positions[i + 1];
// Normalize coordinates to [0,1] range
const u = (x / geometry.parameters.width) + 0.5;
const v = (y / geometry.parameters.height) + 0.5;
if (u >= 0 && u <= 1 && v >= 0 && v <= 1) {
// Simple depth effect based on position
const depth = (Math.sin(u * Math.PI * 2) * Math.sin(v * Math.PI * 2)) * 0.5 + 0.5;
positions[i + 2] = depth * depthIntensity;
}
}
geometry.attributes.position.needsUpdate = true;
geometry.computeVertexNormals();
}
// Reset button
document.getElementById('reset-btn').addEventListener('click', () => {
selectedFile = null;
processedImage = null;
convertBtn.disabled = true;
dropzone.innerHTML = `
<div class="flex flex-col items-center justify-center">
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-4"></i>
<p class="mb-2">Drag & drop your image here</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">or</p>
<label for="file-upload" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md cursor-pointer transition-colors">
<i class="fas fa-folder-open mr-2"></i>
Browse Files
</label>
</div>
`;
fileInput.value = '';
previewPlaceholder.classList.remove('hidden');
previewCanvas.classList.add('hidden');
viewControls.classList.add('hidden');
depthMap.style.display = 'none';
if (model) {
scene.remove(model);
model = null;
}
});
// Download button (simulated)
document.getElementById('download-btn').addEventListener('click', () => {
alert('In a real application, this would download your 3D model file (GLB, OBJ, etc.).');
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=CIHIRIS/2d-to-3d-model-generator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>